Home | History | Annotate | Download | only in car
      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 android.car.CarProjectionManager;
     19 import android.car.ICarProjection;
     20 import android.car.ICarProjectionCallback;
     21 import android.content.ComponentName;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.ServiceConnection;
     25 import android.os.Binder;
     26 import android.os.IBinder;
     27 import android.os.RemoteException;
     28 import android.os.UserHandle;
     29 import android.util.Log;
     30 import android.view.KeyEvent;
     31 
     32 import java.io.PrintWriter;
     33 
     34 /**
     35  * Car projection service allows to bound to projected app to boost it prioirity.
     36  * It also enables proejcted applications to handle voice action requests.
     37  */
     38 class CarProjectionService extends ICarProjection.Stub implements CarServiceBase,
     39         BinderInterfaceContainer.BinderEventHandler<ICarProjectionCallback> {
     40     private final ListenerHolder mAllListeners;
     41     private final CarInputService mCarInputService;
     42     private final Context mContext;
     43 
     44     private final CarInputService.KeyEventListener mVoiceAssistantKeyListener =
     45             new CarInputService.KeyEventListener() {
     46                 @Override
     47                 public boolean onKeyEvent(KeyEvent event) {
     48                     handleVoiceAssitantRequest(false);
     49                     return true;
     50                 }
     51             };
     52 
     53     private final CarInputService.KeyEventListener mLongVoiceAssistantKeyListener =
     54             new CarInputService.KeyEventListener() {
     55                 @Override
     56                 public boolean onKeyEvent(KeyEvent event) {
     57                     handleVoiceAssitantRequest(true);
     58                     return true;
     59                 }
     60             };
     61 
     62     private final ServiceConnection mConnection = new ServiceConnection() {
     63             @Override
     64             public void onServiceConnected(ComponentName className, IBinder service) {
     65                 synchronized (CarProjectionService.this) {
     66                     mBound = true;
     67                 }
     68             }
     69 
     70             @Override
     71             public void onServiceDisconnected(ComponentName className) {
     72                 // Service has crashed.
     73                 Log.w(CarLog.TAG_PROJECTION, "Service disconnected: " + className);
     74                 synchronized (CarProjectionService.this) {
     75                     mRegisteredService = null;
     76                 }
     77                 unbindServiceIfBound();
     78             }
     79         };
     80 
     81     private boolean mBound;
     82     private Intent mRegisteredService;
     83 
     84     CarProjectionService(Context context, CarInputService carInputService) {
     85         mContext = context;
     86         mCarInputService = carInputService;
     87         mAllListeners = new ListenerHolder(this);
     88     }
     89 
     90     @Override
     91     public void registerProjectionRunner(Intent serviceIntent) {
     92         // We assume one active projection app running in the system at one time.
     93         synchronized (this) {
     94             if (serviceIntent.filterEquals(mRegisteredService)) {
     95                 return;
     96             }
     97             if (mRegisteredService != null) {
     98                 Log.w(CarLog.TAG_PROJECTION, "Registering new service[" + serviceIntent
     99                         + "] while old service[" + mRegisteredService + "] is still running");
    100             }
    101             unbindServiceIfBound();
    102         }
    103         bindToService(serviceIntent);
    104     }
    105 
    106     @Override
    107     public void unregisterProjectionRunner(Intent serviceIntent) {
    108         synchronized (this) {
    109             if (!serviceIntent.filterEquals(mRegisteredService)) {
    110                 Log.w(CarLog.TAG_PROJECTION, "Request to unbind unregistered service["
    111                         + serviceIntent + "]. Registered service[" + mRegisteredService + "]");
    112                 return;
    113             }
    114             mRegisteredService = null;
    115         }
    116         unbindServiceIfBound();
    117     }
    118 
    119     private void bindToService(Intent serviceIntent) {
    120         synchronized (this) {
    121             mRegisteredService = serviceIntent;
    122         }
    123         UserHandle userHandle = UserHandle.getUserHandleForUid(Binder.getCallingUid());
    124         mContext.bindServiceAsUser(serviceIntent, mConnection, Context.BIND_AUTO_CREATE,
    125                 userHandle);
    126     }
    127 
    128     private void unbindServiceIfBound() {
    129         synchronized (this) {
    130             if (!mBound) {
    131                 return;
    132             }
    133             mBound = false;
    134         }
    135         mContext.unbindService(mConnection);
    136     }
    137 
    138     private synchronized void handleVoiceAssitantRequest(boolean isTriggeredByLongPress) {
    139         for (BinderInterfaceContainer.BinderInterface<ICarProjectionCallback> listener :
    140                  mAllListeners.getInterfaces()) {
    141             ListenerInfo listenerInfo = (ListenerInfo) listener;
    142             if ((listenerInfo.hasFilter(CarProjectionManager.PROJECTION_LONG_PRESS_VOICE_SEARCH)
    143                     && isTriggeredByLongPress)
    144                     || (listenerInfo.hasFilter(CarProjectionManager.PROJECTION_VOICE_SEARCH)
    145                     && !isTriggeredByLongPress)) {
    146                 dispatchVoiceAssistantRequest(listenerInfo.binderInterface, isTriggeredByLongPress);
    147             }
    148         }
    149     }
    150 
    151     @Override
    152     public void registerProjectionListener(ICarProjectionCallback listener, int filter) {
    153         synchronized (this) {
    154             ListenerInfo info = (ListenerInfo) mAllListeners.getBinderInterface(listener);
    155             if (info == null) {
    156                 info = new ListenerInfo(mAllListeners, listener, filter);
    157                 mAllListeners.addBinderInterface(info);
    158             } else {
    159                 info.setFilter(filter);
    160             }
    161         }
    162         updateCarInputServiceListeners();
    163     }
    164 
    165     @Override
    166     public void unregisterProjectionListener(ICarProjectionCallback listener) {
    167         synchronized (this) {
    168             mAllListeners.removeBinder(listener);
    169         }
    170         updateCarInputServiceListeners();
    171     }
    172 
    173     private void updateCarInputServiceListeners() {
    174         boolean listenShortPress = false;
    175         boolean listenLongPress = false;
    176         synchronized (this) {
    177             for (BinderInterfaceContainer.BinderInterface<ICarProjectionCallback> listener :
    178                          mAllListeners.getInterfaces()) {
    179                 ListenerInfo listenerInfo = (ListenerInfo) listener;
    180                 listenShortPress |= listenerInfo.hasFilter(
    181                         CarProjectionManager.PROJECTION_VOICE_SEARCH);
    182                 listenLongPress |= listenerInfo.hasFilter(
    183                         CarProjectionManager.PROJECTION_LONG_PRESS_VOICE_SEARCH);
    184             }
    185         }
    186         mCarInputService.setVoiceAssistantKeyListener(listenShortPress
    187                 ? mVoiceAssistantKeyListener : null);
    188         mCarInputService.setLongVoiceAssistantKeyListener(listenLongPress
    189                 ? mLongVoiceAssistantKeyListener : null);
    190     }
    191 
    192     @Override
    193     public void init() {
    194         // nothing to do
    195     }
    196 
    197     @Override
    198     public void release() {
    199         synchronized (this) {
    200             mAllListeners.clear();
    201         }
    202     }
    203 
    204     @Override
    205     public void onBinderDeath(
    206             BinderInterfaceContainer.BinderInterface<ICarProjectionCallback> bInterface) {
    207         unregisterProjectionListener(bInterface.binderInterface);
    208     }
    209 
    210     @Override
    211     public void dump(PrintWriter writer) {
    212         writer.println("**CarProjectionService**");
    213         synchronized (this) {
    214             for (BinderInterfaceContainer.BinderInterface<ICarProjectionCallback> listener :
    215                          mAllListeners.getInterfaces()) {
    216                 ListenerInfo listenerInfo = (ListenerInfo) listener;
    217                 writer.println(listenerInfo.toString());
    218             }
    219         }
    220     }
    221 
    222     private void dispatchVoiceAssistantRequest(ICarProjectionCallback listener,
    223             boolean fromLongPress) {
    224         try {
    225             listener.onVoiceAssistantRequest(fromLongPress);
    226         } catch (RemoteException e) {
    227         }
    228     }
    229 
    230     private static class ListenerHolder extends BinderInterfaceContainer<ICarProjectionCallback> {
    231         private ListenerHolder(CarProjectionService service) {
    232             super(service);
    233         }
    234     }
    235 
    236     private static class ListenerInfo extends
    237             BinderInterfaceContainer.BinderInterface<ICarProjectionCallback> {
    238         private int mFilter;
    239 
    240         private ListenerInfo(ListenerHolder holder, ICarProjectionCallback binder, int filter) {
    241             super(holder, binder);
    242             this.mFilter = filter;
    243         }
    244 
    245         private synchronized int getFilter() {
    246             return mFilter;
    247         }
    248 
    249         private boolean hasFilter(int filter) {
    250             return (getFilter() & filter) != 0;
    251         }
    252 
    253         private synchronized void setFilter(int filter) {
    254             mFilter = filter;
    255         }
    256 
    257         @Override
    258         public String toString() {
    259             synchronized (this) {
    260                 return "ListenerInfo{filter=" + Integer.toHexString(mFilter) + "}";
    261             }
    262         }
    263     }
    264 }
    265