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