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.content.Context;
     19 import android.content.Intent;
     20 import android.os.ParcelFileDescriptor;
     21 import android.os.SystemClock;
     22 import android.os.UserHandle;
     23 import android.speech.RecognizerIntent;
     24 import android.util.Log;
     25 import android.view.KeyEvent;
     26 
     27 import com.android.car.hal.InputHalService;
     28 import com.android.car.hal.VehicleHal;
     29 
     30 import java.io.File;
     31 import java.io.FileNotFoundException;
     32 import java.io.IOException;
     33 import java.io.PrintWriter;
     34 
     35 public class CarInputService implements CarServiceBase, InputHalService.InputListener {
     36 
     37     public interface KeyEventListener {
     38         void onKeyEvent(KeyEvent event);
     39     }
     40 
     41     private static final long VOICE_LONG_PRESS_TIME_MS = 1000;
     42 
     43     private final Context mContext;
     44 
     45     private KeyEventListener mVoiceAssitantKeyListener;
     46     private KeyEventListener mLongVoiceAssitantKeyListener;
     47     private long mLastVoiceKeyDownTime = 0;
     48 
     49     private KeyEventListener mInstumentClusterKeyListener;
     50 
     51     private ParcelFileDescriptor mInjectionDeviceFd;
     52 
     53     private int mKeyEventCount = 0;
     54 
     55     public CarInputService(Context context) {
     56         mContext = context;
     57     }
     58 
     59     /**
     60      * Set listener for listening voice assistant key event. Setting to null stops listening.
     61      * If listener is not set, default behavior will be done for short press.
     62      * If listener is set, short key press will lead into calling the listener.
     63      * @param listener
     64      */
     65     public void setVoiceAssitantKeyListener(KeyEventListener listener) {
     66         synchronized (this) {
     67             mVoiceAssitantKeyListener = listener;
     68         }
     69     }
     70 
     71     /**
     72      * Set listener for listening long voice assistant key event. Setting to null stops listening.
     73      * If listener is not set, default behavior will be done for long press.
     74      * If listener is set, short long press will lead into calling the listener.
     75      * @param listener
     76      */
     77     public void setLongVoiceAssitantKeyListener(KeyEventListener listener) {
     78         synchronized (this) {
     79             mLongVoiceAssitantKeyListener = listener;
     80         }
     81     }
     82 
     83     public void setInstrumentClusterKeyListener(KeyEventListener listener) {
     84         synchronized (this) {
     85             mInstumentClusterKeyListener = listener;
     86         }
     87     }
     88 
     89     @Override
     90     public void init() {
     91         InputHalService hal = VehicleHal.getInstance().getInputHal();
     92         if (!hal.isKeyInputSupported()) {
     93             Log.w(CarLog.TAG_INPUT, "Hal does not support key input.");
     94             return;
     95         }
     96         String injectionDevice = mContext.getResources().getString(
     97                 R.string.inputInjectionDeviceNode);
     98         ParcelFileDescriptor file = null;
     99         try {
    100             file = ParcelFileDescriptor.open(new File(injectionDevice),
    101                     ParcelFileDescriptor.MODE_READ_WRITE);
    102         } catch (FileNotFoundException e) {
    103             Log.w(CarLog.TAG_INPUT, "cannot open device for input injection:" + injectionDevice);
    104             return;
    105         }
    106         synchronized (this) {
    107             mInjectionDeviceFd = file;
    108         }
    109         hal.setInputListener(this);
    110     }
    111 
    112     @Override
    113     public void release() {
    114         synchronized (this) {
    115             mVoiceAssitantKeyListener = null;
    116             mLongVoiceAssitantKeyListener = null;
    117             mInstumentClusterKeyListener = null;
    118             if (mInjectionDeviceFd != null) {
    119                 try {
    120                     mInjectionDeviceFd.close();
    121                 } catch (IOException e) {
    122                 }
    123             }
    124             mInjectionDeviceFd = null;
    125             mKeyEventCount = 0;
    126         }
    127     }
    128 
    129     @Override
    130     public void onKeyEvent(KeyEvent event, int targetDisplay) {
    131         synchronized (this) {
    132             mKeyEventCount++;
    133         }
    134         int keyCode = event.getKeyCode();
    135         switch (keyCode) {
    136             case KeyEvent.KEYCODE_VOICE_ASSIST:
    137                 handleVoiceAssistKey(event);
    138                 return;
    139             default:
    140                 break;
    141         }
    142         if (targetDisplay == InputHalService.DISPLAY_INSTRUMENT_CLUSTER) {
    143             handleInstrumentClusterKey(event);
    144         } else {
    145             handleMainDisplayKey(event);
    146         }
    147     }
    148 
    149     private void handleVoiceAssistKey(KeyEvent event) {
    150         int action = event.getAction();
    151         if (action == KeyEvent.ACTION_DOWN) {
    152             long now = SystemClock.elapsedRealtime();
    153             synchronized (this) {
    154                 mLastVoiceKeyDownTime = now;
    155             }
    156         } else if (action == KeyEvent.ACTION_UP) {
    157             // if no listener, do not handle long press
    158             KeyEventListener listener = null;
    159             KeyEventListener shortPressListener = null;
    160             KeyEventListener longPressListener = null;
    161             long downTime;
    162             synchronized (this) {
    163                 shortPressListener = mVoiceAssitantKeyListener;
    164                 longPressListener = mLongVoiceAssitantKeyListener;
    165                 downTime = mLastVoiceKeyDownTime;
    166             }
    167             if (shortPressListener == null && longPressListener == null) {
    168                 launchDefaultVoiceAssitantHandler();
    169             } else {
    170                 long duration = SystemClock.elapsedRealtime() - downTime;
    171                 listener = (duration > VOICE_LONG_PRESS_TIME_MS
    172                         ? longPressListener : shortPressListener);
    173                 if (listener != null) {
    174                     listener.onKeyEvent(event);
    175                 } else {
    176                     launchDefaultVoiceAssitantHandler();
    177                 }
    178             }
    179         }
    180     }
    181 
    182     private void launchDefaultVoiceAssitantHandler() {
    183         Log.i(CarLog.TAG_INPUT, "voice key, launch default intent");
    184         Intent voiceIntent =
    185                 new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
    186         mContext.startActivityAsUser(voiceIntent, null, UserHandle.CURRENT_OR_SELF);
    187     }
    188 
    189     private void handleInstrumentClusterKey(KeyEvent event) {
    190         KeyEventListener listener = null;
    191         synchronized (this) {
    192             listener = mInstumentClusterKeyListener;
    193         }
    194         if (listener == null) {
    195             return;
    196         }
    197         listener.onKeyEvent(event);
    198     }
    199 
    200     private void handleMainDisplayKey(KeyEvent event) {
    201         int fd;
    202         synchronized (this) {
    203             fd = mInjectionDeviceFd.getFd();
    204         }
    205         int action = event.getAction();
    206         boolean isDown = (action == KeyEvent.ACTION_DOWN);
    207         int keyCode = event.getKeyCode();
    208         int r = nativeInjectKeyEvent(fd, keyCode, isDown);
    209         if (r != 0) {
    210             Log.e(CarLog.TAG_INPUT, "cannot inject key event, failed with:" + r);
    211         }
    212     }
    213 
    214     @Override
    215     public void dump(PrintWriter writer) {
    216         writer.println("*Input Service*");
    217         writer.println("mInjectionDeviceFd:" + mInjectionDeviceFd);
    218         writer.println("mLastVoiceKeyDownTime:" + mLastVoiceKeyDownTime +
    219                 ",mKeyEventCount:" + mKeyEventCount);
    220     }
    221 
    222     private native int nativeInjectKeyEvent(int fd, int keyCode, boolean isDown);
    223 }
    224