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 import static android.net.wifi.WifiManager.WIFI_FREQUENCY_BAND_5GHZ;
30 
31 import android.annotation.Nullable;
32 import android.app.ActivityOptions;
33 import android.bluetooth.BluetoothDevice;
34 import android.car.CarProjectionManager;
35 import android.car.CarProjectionManager.ProjectionAccessPointCallback;
36 import android.car.CarProjectionManager.ProjectionKeyEventHandler;
37 import android.car.ICarProjection;
38 import android.car.ICarProjectionKeyEventHandler;
39 import android.car.ICarProjectionStatusListener;
40 import android.car.projection.ProjectionOptions;
41 import android.car.projection.ProjectionStatus;
42 import android.car.projection.ProjectionStatus.ProjectionState;
43 import android.content.BroadcastReceiver;
44 import android.content.ComponentName;
45 import android.content.Context;
46 import android.content.Intent;
47 import android.content.IntentFilter;
48 import android.content.ServiceConnection;
49 import android.content.pm.PackageManager;
50 import android.content.res.Resources;
51 import android.graphics.Rect;
52 import android.net.wifi.WifiClient;
53 import android.net.wifi.WifiConfiguration;
54 import android.net.wifi.WifiManager;
55 import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
56 import android.net.wifi.WifiManager.LocalOnlyHotspotReservation;
57 import android.net.wifi.WifiScanner;
58 import android.os.Binder;
59 import android.os.Bundle;
60 import android.os.Handler;
61 import android.os.HandlerExecutor;
62 import android.os.IBinder;
63 import android.os.Message;
64 import android.os.Messenger;
65 import android.os.RemoteException;
66 import android.os.UserHandle;
67 import android.text.TextUtils;
68 import android.util.Log;
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.io.PrintWriter;
75 import java.lang.ref.WeakReference;
76 import java.net.NetworkInterface;
77 import java.net.SocketException;
78 import java.util.ArrayList;
79 import java.util.BitSet;
80 import java.util.HashMap;
81 import java.util.List;
82 
83 /**
84  * Car projection service allows to bound to projected app to boost it priority.
85  * It also enables projected applications to handle voice action requests.
86  */
87 class CarProjectionService extends ICarProjection.Stub implements CarServiceBase,
88         BinderInterfaceContainer.BinderEventHandler<ICarProjectionKeyEventHandler>,
89         CarProjectionManager.ProjectionKeyEventHandler {
90     private static final String TAG = CarLog.TAG_PROJECTION;
91     private static final boolean DBG = true;
92 
93     private final CarInputService mCarInputService;
94     private final CarBluetoothService mCarBluetoothService;
95     private final Context mContext;
96     private final WifiManager mWifiManager;
97     private final Handler mHandler;
98     private final Object mLock = new Object();
99 
100     @GuardedBy("mLock")
101     private final HashMap<IBinder, WirelessClient> mWirelessClients = new HashMap<>();
102 
103     @GuardedBy("mLock")
104     private @Nullable LocalOnlyHotspotReservation mLocalOnlyHotspotReservation;
105 
106 
107     @GuardedBy("mLock")
108     private @Nullable ProjectionSoftApCallback mSoftApCallback;
109 
110     @GuardedBy("mLock")
111     private final HashMap<IBinder, ProjectionReceiverClient> mProjectionReceiverClients =
112             new HashMap<>();
113 
114     @Nullable
115     private String mApBssid;
116 
117     @GuardedBy("mLock")
118     private @Nullable WifiScanner mWifiScanner;
119 
120     @GuardedBy("mLock")
121     private @ProjectionState int mCurrentProjectionState = PROJECTION_STATE_INACTIVE;
122 
123     @GuardedBy("mLock")
124     private ProjectionOptions mProjectionOptions;
125 
126     @GuardedBy("mLock")
127     private @Nullable String mCurrentProjectionPackage;
128 
129     private final BinderInterfaceContainer<ICarProjectionStatusListener>
130             mProjectionStatusListeners = new BinderInterfaceContainer<>();
131 
132     @GuardedBy("mLock")
133     private final ProjectionKeyEventHandlerContainer mKeyEventHandlers;
134 
135     private static final int WIFI_MODE_TETHERED = 1;
136     private static final int WIFI_MODE_LOCALONLY = 2;
137 
138     // Could be one of the WIFI_MODE_* constants.
139     // TODO: read this from user settings, support runtime switch
140     private int mWifiMode;
141 
142     private final ServiceConnection mConnection = new ServiceConnection() {
143             @Override
144             public void onServiceConnected(ComponentName className, IBinder service) {
145                 synchronized (mLock) {
146                     mBound = true;
147                 }
148             }
149 
150             @Override
151             public void onServiceDisconnected(ComponentName className) {
152                 // Service has crashed.
153                 Log.w(CarLog.TAG_PROJECTION, "Service disconnected: " + className);
154                 synchronized (mLock) {
155                     mRegisteredService = null;
156                 }
157                 unbindServiceIfBound();
158             }
159         };
160 
161     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
162         @Override
163         public void onReceive(Context context, Intent intent) {
164             int currState = intent.getIntExtra(EXTRA_WIFI_AP_STATE, WIFI_AP_STATE_DISABLED);
165             int prevState = intent.getIntExtra(EXTRA_PREVIOUS_WIFI_AP_STATE,
166                     WIFI_AP_STATE_DISABLED);
167             int errorCode = intent.getIntExtra(EXTRA_WIFI_AP_FAILURE_REASON, 0);
168             String ifaceName = intent.getStringExtra(EXTRA_WIFI_AP_INTERFACE_NAME);
169             int mode = intent.getIntExtra(EXTRA_WIFI_AP_MODE,
170                     WifiManager.IFACE_IP_MODE_UNSPECIFIED);
171             handleWifiApStateChange(currState, prevState, errorCode, ifaceName, mode);
172         }
173     };
174 
175     private boolean mBound;
176     private Intent mRegisteredService;
177 
CarProjectionService(Context context, @Nullable Handler handler, CarInputService carInputService, CarBluetoothService carBluetoothService)178     CarProjectionService(Context context, @Nullable Handler handler,
179             CarInputService carInputService, CarBluetoothService carBluetoothService) {
180         mContext = context;
181         mHandler = handler == null ? new Handler() : handler;
182         mCarInputService = carInputService;
183         mCarBluetoothService = carBluetoothService;
184         mKeyEventHandlers = new ProjectionKeyEventHandlerContainer(this);
185         mWifiManager = context.getSystemService(WifiManager.class);
186 
187         final Resources res = mContext.getResources();
188         setAccessPointTethering(res.getBoolean(R.bool.config_projectionAccessPointTethering));
189     }
190 
191     @Override
registerProjectionRunner(Intent serviceIntent)192     public void registerProjectionRunner(Intent serviceIntent) {
193         ICarImpl.assertProjectionPermission(mContext);
194         // We assume one active projection app running in the system at one time.
195         synchronized (mLock) {
196             if (serviceIntent.filterEquals(mRegisteredService) && mBound) {
197                 return;
198             }
199             if (mRegisteredService != null) {
200                 Log.w(CarLog.TAG_PROJECTION, "Registering new service[" + serviceIntent
201                         + "] while old service[" + mRegisteredService + "] is still running");
202             }
203             unbindServiceIfBound();
204         }
205         bindToService(serviceIntent);
206     }
207 
208     @Override
unregisterProjectionRunner(Intent serviceIntent)209     public void unregisterProjectionRunner(Intent serviceIntent) {
210         ICarImpl.assertProjectionPermission(mContext);
211         synchronized (mLock) {
212             if (!serviceIntent.filterEquals(mRegisteredService)) {
213                 Log.w(CarLog.TAG_PROJECTION, "Request to unbind unregistered service["
214                         + serviceIntent + "]. Registered service[" + mRegisteredService + "]");
215                 return;
216             }
217             mRegisteredService = null;
218         }
219         unbindServiceIfBound();
220     }
221 
bindToService(Intent serviceIntent)222     private void bindToService(Intent serviceIntent) {
223         synchronized (mLock) {
224             mRegisteredService = serviceIntent;
225         }
226         UserHandle userHandle = UserHandle.getUserHandleForUid(Binder.getCallingUid());
227         mContext.bindServiceAsUser(serviceIntent, mConnection, Context.BIND_AUTO_CREATE,
228                 userHandle);
229     }
230 
unbindServiceIfBound()231     private void unbindServiceIfBound() {
232         synchronized (mLock) {
233             if (!mBound) {
234                 return;
235             }
236             mBound = false;
237             mRegisteredService = null;
238         }
239         mContext.unbindService(mConnection);
240     }
241 
242     @Override
registerKeyEventHandler( ICarProjectionKeyEventHandler eventHandler, byte[] eventMask)243     public void registerKeyEventHandler(
244             ICarProjectionKeyEventHandler eventHandler, byte[] eventMask) {
245         ICarImpl.assertProjectionPermission(mContext);
246         BitSet events = BitSet.valueOf(eventMask);
247         Preconditions.checkArgument(
248                 events.length() <= CarProjectionManager.NUM_KEY_EVENTS,
249                 "Unknown handled event");
250         synchronized (mLock) {
251             ProjectionKeyEventHandler info = mKeyEventHandlers.get(eventHandler);
252             if (info == null) {
253                 info = new ProjectionKeyEventHandler(mKeyEventHandlers, eventHandler, events);
254                 mKeyEventHandlers.addBinderInterface(info);
255             } else {
256                 info.setHandledEvents(events);
257             }
258 
259             updateInputServiceHandlerLocked();
260         }
261     }
262 
263     @Override
unregisterKeyEventHandler(ICarProjectionKeyEventHandler eventHandler)264     public void unregisterKeyEventHandler(ICarProjectionKeyEventHandler eventHandler) {
265         ICarImpl.assertProjectionPermission(mContext);
266         synchronized (mLock) {
267             mKeyEventHandlers.removeBinder(eventHandler);
268             updateInputServiceHandlerLocked();
269         }
270     }
271 
272     @Override
startProjectionAccessPoint(final Messenger messenger, IBinder binder)273     public void startProjectionAccessPoint(final Messenger messenger, IBinder binder)
274             throws RemoteException {
275         ICarImpl.assertProjectionPermission(mContext);
276         //TODO: check if access point already started with the desired configuration.
277         registerWirelessClient(WirelessClient.of(messenger, binder));
278         startAccessPoint();
279     }
280 
281     @Override
stopProjectionAccessPoint(IBinder token)282     public void stopProjectionAccessPoint(IBinder token) {
283         ICarImpl.assertProjectionPermission(mContext);
284         Log.i(TAG, "Received stop access point request from " + token);
285 
286         boolean shouldReleaseAp;
287         synchronized (mLock) {
288             if (!unregisterWirelessClientLocked(token)) {
289                 Log.w(TAG, "Client " + token + " was not registered");
290                 return;
291             }
292             shouldReleaseAp = mWirelessClients.isEmpty();
293         }
294 
295         if (shouldReleaseAp) {
296             stopAccessPoint();
297         }
298     }
299 
300     @Override
getAvailableWifiChannels(int band)301     public int[] getAvailableWifiChannels(int band) {
302         ICarImpl.assertProjectionPermission(mContext);
303         WifiScanner scanner;
304         synchronized (mLock) {
305             // Lazy initialization
306             if (mWifiScanner == null) {
307                 mWifiScanner = mContext.getSystemService(WifiScanner.class);
308             }
309             scanner = mWifiScanner;
310         }
311         if (scanner == null) {
312             Log.w(TAG, "Unable to get WifiScanner");
313             return new int[0];
314         }
315 
316         List<Integer> channels = scanner.getAvailableChannels(band);
317         if (channels == null || channels.isEmpty()) {
318             Log.w(TAG, "WifiScanner reported no available channels");
319             return new int[0];
320         }
321 
322         int[] array = new int[channels.size()];
323         for (int i = 0; i < channels.size(); i++) {
324             array[i] = channels.get(i);
325         }
326         return array;
327     }
328 
329     /**
330      * Request to disconnect the given profile on the given device, and prevent it from reconnecting
331      * until either the request is released, or the process owning the given token dies.
332      *
333      * @param device  The device on which to inhibit a profile.
334      * @param profile The {@link android.bluetooth.BluetoothProfile} to inhibit.
335      * @param token   A {@link IBinder} to be used as an identity for the request. If the process
336      *                owning the token dies, the request will automatically be released.
337      * @return True if the profile was successfully inhibited, false if an error occurred.
338      */
339     @Override
requestBluetoothProfileInhibit( BluetoothDevice device, int profile, IBinder token)340     public boolean requestBluetoothProfileInhibit(
341             BluetoothDevice device, int profile, IBinder token) {
342         if (DBG) {
343             Log.d(TAG, "requestBluetoothProfileInhibit device=" + device + " profile=" + profile
344                     + " from uid " + Binder.getCallingUid());
345         }
346         ICarImpl.assertProjectionPermission(mContext);
347         try {
348             if (device == null) {
349                 // Will be caught by AIDL and thrown to caller.
350                 throw new NullPointerException("Device must not be null");
351             }
352             if (token == null) {
353                 throw new NullPointerException("Token must not be null");
354             }
355             return mCarBluetoothService.requestProfileInhibit(device, profile, token);
356         } catch (RuntimeException e) {
357             Log.e(TAG, "Error in requestBluetoothProfileInhibit", e);
358             throw e;
359         }
360     }
361 
362     /**
363      * Release an inhibit request made by {@link #requestBluetoothProfileInhibit}, and reconnect the
364      * profile if no other inhibit requests are active.
365      *
366      * @param device  The device on which to release the inhibit request.
367      * @param profile The profile on which to release the inhibit request.
368      * @param token   The token provided in the original call to
369      *                {@link #requestBluetoothProfileInhibit}.
370      * @return True if the request was released, false if an error occurred.
371      */
372     @Override
releaseBluetoothProfileInhibit( BluetoothDevice device, int profile, IBinder token)373     public boolean releaseBluetoothProfileInhibit(
374             BluetoothDevice device, int profile, IBinder token) {
375         if (DBG) {
376             Log.d(TAG, "releaseBluetoothProfileInhibit device=" + device + " profile=" + profile
377                     + " from uid " + Binder.getCallingUid());
378         }
379         ICarImpl.assertProjectionPermission(mContext);
380         try {
381             if (device == null) {
382                 // Will be caught by AIDL and thrown to caller.
383                 throw new NullPointerException("Device must not be null");
384             }
385             if (token == null) {
386                 throw new NullPointerException("Token must not be null");
387             }
388             return mCarBluetoothService.releaseProfileInhibit(device, profile, token);
389         } catch (RuntimeException e) {
390             Log.e(TAG, "Error in releaseBluetoothProfileInhibit", e);
391             throw e;
392         }
393     }
394 
395     @Override
updateProjectionStatus(ProjectionStatus status, IBinder token)396     public void updateProjectionStatus(ProjectionStatus status, IBinder token)
397             throws RemoteException {
398         if (DBG) {
399             Log.d(TAG, "updateProjectionStatus, status: " + status + ", token: " + token);
400         }
401         ICarImpl.assertProjectionPermission(mContext);
402         final String packageName = status.getPackageName();
403         final int callingUid = Binder.getCallingUid();
404         final int userHandleId = Binder.getCallingUserHandle().getIdentifier();
405         final int packageUid;
406 
407         try {
408             packageUid =
409                     mContext.getPackageManager().getPackageUidAsUser(packageName, userHandleId);
410         } catch (PackageManager.NameNotFoundException e) {
411             throw new SecurityException("Package " + packageName + " does not exist", e);
412         }
413 
414         if (callingUid != packageUid) {
415             throw new SecurityException(
416                     "UID " + callingUid + " cannot update status for package " + packageName);
417         }
418 
419         synchronized (mLock) {
420             ProjectionReceiverClient client = getOrCreateProjectionReceiverClientLocked(token);
421             client.mProjectionStatus = status;
422 
423             // If the projection package that's reporting its projection state is the currently
424             // active projection package, update the state. If it is a different package, update the
425             // current projection state if the new package is reporting that it is projecting or if
426             // it is reporting that it's ready to project, and the current package has an inactive
427             // projection state.
428             if (status.isActive()
429                     || (status.getState() == PROJECTION_STATE_READY_TO_PROJECT
430                             && mCurrentProjectionState == PROJECTION_STATE_INACTIVE)
431                     || TextUtils.equals(packageName, mCurrentProjectionPackage)) {
432                 mCurrentProjectionState = status.getState();
433                 mCurrentProjectionPackage = packageName;
434             }
435         }
436         notifyProjectionStatusChanged(null /* notify all listeners */);
437     }
438 
439     @Override
registerProjectionStatusListener(ICarProjectionStatusListener listener)440     public void registerProjectionStatusListener(ICarProjectionStatusListener listener)
441             throws RemoteException {
442         ICarImpl.assertProjectionStatusPermission(mContext);
443         mProjectionStatusListeners.addBinder(listener);
444 
445         // Immediately notify listener with the current status.
446         notifyProjectionStatusChanged(listener);
447     }
448 
449     @Override
unregisterProjectionStatusListener(ICarProjectionStatusListener listener)450     public void unregisterProjectionStatusListener(ICarProjectionStatusListener listener)
451             throws RemoteException {
452         ICarImpl.assertProjectionStatusPermission(mContext);
453         mProjectionStatusListeners.removeBinder(listener);
454     }
455 
getOrCreateProjectionReceiverClientLocked( IBinder token)456     private ProjectionReceiverClient getOrCreateProjectionReceiverClientLocked(
457             IBinder token) throws RemoteException {
458         ProjectionReceiverClient client;
459         client = mProjectionReceiverClients.get(token);
460         if (client == null) {
461             client = new ProjectionReceiverClient(() -> unregisterProjectionReceiverClient(token));
462             token.linkToDeath(client.mDeathRecipient, 0 /* flags */);
463             mProjectionReceiverClients.put(token, client);
464         }
465         return client;
466     }
467 
unregisterProjectionReceiverClient(IBinder token)468     private void unregisterProjectionReceiverClient(IBinder token) {
469         synchronized (mLock) {
470             ProjectionReceiverClient client = mProjectionReceiverClients.remove(token);
471             if (client == null) {
472                 Log.w(TAG, "Projection receiver client for token " + token + " doesn't exist");
473                 return;
474             }
475             token.unlinkToDeath(client.mDeathRecipient, 0);
476             if (TextUtils.equals(
477                     client.mProjectionStatus.getPackageName(), mCurrentProjectionPackage)) {
478                 mCurrentProjectionPackage = null;
479                 mCurrentProjectionState = PROJECTION_STATE_INACTIVE;
480             }
481         }
482     }
483 
notifyProjectionStatusChanged( @ullable ICarProjectionStatusListener singleListenerToNotify)484     private void notifyProjectionStatusChanged(
485             @Nullable ICarProjectionStatusListener singleListenerToNotify)
486             throws RemoteException {
487         int currentState;
488         String currentPackage;
489         List<ProjectionStatus> statuses = new ArrayList<>();
490         synchronized (mLock) {
491             for (ProjectionReceiverClient client : mProjectionReceiverClients.values()) {
492                 statuses.add(client.mProjectionStatus);
493             }
494             currentState = mCurrentProjectionState;
495             currentPackage = mCurrentProjectionPackage;
496         }
497 
498         if (DBG) {
499             Log.d(TAG, "Notify projection status change, state: " + currentState + ", pkg: "
500                     + currentPackage + ", listeners: " + mProjectionStatusListeners.size()
501                     + ", listenerToNotify: " + singleListenerToNotify);
502         }
503 
504         if (singleListenerToNotify == null) {
505             for (BinderInterface<ICarProjectionStatusListener> listener :
506                     mProjectionStatusListeners.getInterfaces()) {
507                 try {
508                     listener.binderInterface.onProjectionStatusChanged(
509                             currentState, currentPackage, statuses);
510                 } catch (RemoteException ex) {
511                     Log.e(TAG, "Error calling to projection status listener", ex);
512                 }
513             }
514         } else {
515             singleListenerToNotify.onProjectionStatusChanged(
516                     currentState, currentPackage, statuses);
517         }
518     }
519 
520     @Override
getProjectionOptions()521     public Bundle getProjectionOptions() {
522         ICarImpl.assertProjectionPermission(mContext);
523         synchronized (mLock) {
524             if (mProjectionOptions == null) {
525                 mProjectionOptions = createProjectionOptionsBuilder()
526                         .build();
527             }
528         }
529         return mProjectionOptions.toBundle();
530     }
531 
createProjectionOptionsBuilder()532     private ProjectionOptions.Builder createProjectionOptionsBuilder() {
533         Resources res = mContext.getResources();
534 
535         ProjectionOptions.Builder builder = ProjectionOptions.builder();
536 
537         ActivityOptions activityOptions = createActivityOptions(res);
538         if (activityOptions != null) {
539             builder.setProjectionActivityOptions(activityOptions);
540         }
541 
542         String consentActivity = res.getString(R.string.config_projectionConsentActivity);
543         if (!TextUtils.isEmpty(consentActivity)) {
544             builder.setConsentActivity(ComponentName.unflattenFromString(consentActivity));
545         }
546 
547         builder.setUiMode(res.getInteger(R.integer.config_projectionUiMode));
548         return builder;
549     }
550 
551     @Nullable
createActivityOptions(Resources res)552     private static ActivityOptions createActivityOptions(Resources res) {
553         ActivityOptions activityOptions = ActivityOptions.makeBasic();
554         boolean changed = false;
555         int displayId = res.getInteger(R.integer.config_projectionActivityDisplayId);
556         if (displayId != -1) {
557             activityOptions.setLaunchDisplayId(displayId);
558             changed = true;
559         }
560         int[] rawBounds = res.getIntArray(R.array.config_projectionActivityLaunchBounds);
561         if (rawBounds != null && rawBounds.length == 4) {
562             Rect bounds = new Rect(rawBounds[0], rawBounds[1], rawBounds[2], rawBounds[3]);
563             activityOptions.setLaunchBounds(bounds);
564             changed = true;
565         }
566         return changed ? activityOptions : null;
567     }
568 
startAccessPoint()569     private void startAccessPoint() {
570         synchronized (mLock) {
571             switch (mWifiMode) {
572                 case WIFI_MODE_LOCALONLY: {
573                     startLocalOnlyApLocked();
574                     break;
575                 }
576                 case WIFI_MODE_TETHERED: {
577                     startTetheredApLocked();
578                     break;
579                 }
580                 default: {
581                     Log.wtf(TAG, "Unexpected Access Point mode during starting: " + mWifiMode);
582                     break;
583                 }
584             }
585         }
586     }
587 
stopAccessPoint()588     private void stopAccessPoint() {
589         sendApStopped();
590 
591         synchronized (mLock) {
592             switch (mWifiMode) {
593                 case WIFI_MODE_LOCALONLY: {
594                     stopLocalOnlyApLocked();
595                     break;
596                 }
597                 case WIFI_MODE_TETHERED: {
598                     stopTetheredApLocked();
599                     break;
600                 }
601                 default: {
602                     Log.wtf(TAG, "Unexpected Access Point mode during stopping : " + mWifiMode);
603                 }
604             }
605         }
606     }
607 
startTetheredApLocked()608     private void startTetheredApLocked() {
609         Log.d(TAG, "startTetheredApLocked");
610 
611         if (mSoftApCallback == null) {
612             mSoftApCallback = new ProjectionSoftApCallback();
613             mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback);
614             ensureApConfiguration();
615         }
616 
617         if (!mWifiManager.startSoftAp(null /* use existing config*/)) {
618             // The indicates that AP might be already started.
619             if (mWifiManager.getWifiApState() == WIFI_AP_STATE_ENABLED) {
620                 sendApStarted(mWifiManager.getWifiApConfiguration());
621             } else {
622                 Log.e(TAG, "Failed to start soft AP");
623                 sendApFailed(ERROR_GENERIC);
624             }
625         }
626     }
627 
stopTetheredApLocked()628     private void stopTetheredApLocked() {
629         Log.d(TAG, "stopTetheredAp");
630 
631         if (mSoftApCallback != null) {
632             mWifiManager.unregisterSoftApCallback(mSoftApCallback);
633             mSoftApCallback = null;
634             if (!mWifiManager.stopSoftAp()) {
635                 Log.w(TAG, "Failed to request soft AP to stop.");
636             }
637         }
638     }
639 
startLocalOnlyApLocked()640     private void startLocalOnlyApLocked() {
641         if (mLocalOnlyHotspotReservation != null) {
642             Log.i(TAG, "Local-only hotspot is already registered.");
643             sendApStarted(mLocalOnlyHotspotReservation.getWifiConfiguration());
644             return;
645         }
646 
647         Log.i(TAG, "Requesting to start local-only hotspot.");
648         mWifiManager.startLocalOnlyHotspot(new LocalOnlyHotspotCallback() {
649             @Override
650             public void onStarted(LocalOnlyHotspotReservation reservation) {
651                 Log.d(TAG, "Local-only hotspot started");
652                 synchronized (mLock) {
653                     mLocalOnlyHotspotReservation = reservation;
654                 }
655                 sendApStarted(reservation.getWifiConfiguration());
656             }
657 
658             @Override
659             public void onStopped() {
660                 Log.i(TAG, "Local-only hotspot stopped.");
661                 synchronized (mLock) {
662                     if (mLocalOnlyHotspotReservation != null) {
663                         // We must explicitly released old reservation object, otherwise it may
664                         // unexpectedly stop LOHS later because it overrode finalize() method.
665                         mLocalOnlyHotspotReservation.close();
666                     }
667                     mLocalOnlyHotspotReservation = null;
668                 }
669                 sendApStopped();
670             }
671 
672             @Override
673             public void onFailed(int localonlyHostspotFailureReason) {
674                 Log.w(TAG, "Local-only hotspot failed, reason: "
675                         + localonlyHostspotFailureReason);
676                 synchronized (mLock) {
677                     mLocalOnlyHotspotReservation = null;
678                 }
679                 int reason;
680                 switch (localonlyHostspotFailureReason) {
681                     case LocalOnlyHotspotCallback.ERROR_NO_CHANNEL:
682                         reason = ProjectionAccessPointCallback.ERROR_NO_CHANNEL;
683                         break;
684                     case LocalOnlyHotspotCallback.ERROR_TETHERING_DISALLOWED:
685                         reason = ProjectionAccessPointCallback.ERROR_TETHERING_DISALLOWED;
686                         break;
687                     case LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE:
688                         reason = ProjectionAccessPointCallback.ERROR_INCOMPATIBLE_MODE;
689                         break;
690                     default:
691                         reason = ERROR_GENERIC;
692 
693                 }
694                 sendApFailed(reason);
695             }
696         }, mHandler);
697     }
698 
stopLocalOnlyApLocked()699     private void stopLocalOnlyApLocked() {
700         Log.i(TAG, "stopLocalOnlyApLocked");
701 
702         if (mLocalOnlyHotspotReservation == null) {
703             Log.w(TAG, "Requested to stop local-only hotspot which was already stopped.");
704             return;
705         }
706 
707         mLocalOnlyHotspotReservation.close();
708         mLocalOnlyHotspotReservation = null;
709     }
710 
sendApStarted(WifiConfiguration wifiConfiguration)711     private void sendApStarted(WifiConfiguration wifiConfiguration) {
712         WifiConfiguration localWifiConfig = new WifiConfiguration(wifiConfiguration);
713         localWifiConfig.BSSID = mApBssid;
714 
715         Message message = Message.obtain();
716         message.what = CarProjectionManager.PROJECTION_AP_STARTED;
717         message.obj = localWifiConfig;
718         Log.i(TAG, "Sending PROJECTION_AP_STARTED, ssid: "
719                 + localWifiConfig.getPrintableSsid()
720                 + ", apBand: " + localWifiConfig.apBand
721                 + ", apChannel: " + localWifiConfig.apChannel
722                 + ", bssid: " + localWifiConfig.BSSID);
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             Log.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                 Log.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(PrintWriter writer)791     public void dump(PrintWriter 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         Log.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                         Log.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                 Log.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         WifiConfiguration apConfig = mWifiManager.getWifiApConfiguration();
942         if (apConfig != null && apConfig.apBand != WIFI_FREQUENCY_BAND_5GHZ
943                 && mWifiManager.is5GHzBandSupported()) {
944             apConfig.apBand = WIFI_FREQUENCY_BAND_5GHZ;
945             mWifiManager.setWifiApConfiguration(apConfig);
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             Log.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.getWifiApConfiguration());
968                     break;
969                 }
970                 case WifiManager.WIFI_AP_STATE_DISABLED: {
971                     sendApStopped();
972                     break;
973                 }
974                 case WifiManager.WIFI_AP_STATE_FAILED: {
975                     Log.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                 Log.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                 Log.d(TAG, "Sending message " + message.what + " to " + this);
1016                 messenger.send(message);
1017             } catch (RemoteException e) {
1018                 Log.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             Log.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