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