/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.car; import android.content.Context; import android.content.Intent; import android.os.ParcelFileDescriptor; import android.os.SystemClock; import android.os.UserHandle; import android.speech.RecognizerIntent; import android.util.Log; import android.view.KeyEvent; import com.android.car.hal.InputHalService; import com.android.car.hal.VehicleHal; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; public class CarInputService implements CarServiceBase, InputHalService.InputListener { public interface KeyEventListener { void onKeyEvent(KeyEvent event); } private static final long VOICE_LONG_PRESS_TIME_MS = 1000; private final Context mContext; private KeyEventListener mVoiceAssitantKeyListener; private KeyEventListener mLongVoiceAssitantKeyListener; private long mLastVoiceKeyDownTime = 0; private KeyEventListener mInstumentClusterKeyListener; private ParcelFileDescriptor mInjectionDeviceFd; private int mKeyEventCount = 0; public CarInputService(Context context) { mContext = context; } /** * Set listener for listening voice assistant key event. Setting to null stops listening. * If listener is not set, default behavior will be done for short press. * If listener is set, short key press will lead into calling the listener. * @param listener */ public void setVoiceAssitantKeyListener(KeyEventListener listener) { synchronized (this) { mVoiceAssitantKeyListener = listener; } } /** * Set listener for listening long voice assistant key event. Setting to null stops listening. * If listener is not set, default behavior will be done for long press. * If listener is set, short long press will lead into calling the listener. * @param listener */ public void setLongVoiceAssitantKeyListener(KeyEventListener listener) { synchronized (this) { mLongVoiceAssitantKeyListener = listener; } } public void setInstrumentClusterKeyListener(KeyEventListener listener) { synchronized (this) { mInstumentClusterKeyListener = listener; } } @Override public void init() { InputHalService hal = VehicleHal.getInstance().getInputHal(); if (!hal.isKeyInputSupported()) { Log.w(CarLog.TAG_INPUT, "Hal does not support key input."); return; } String injectionDevice = mContext.getResources().getString( R.string.inputInjectionDeviceNode); ParcelFileDescriptor file = null; try { file = ParcelFileDescriptor.open(new File(injectionDevice), ParcelFileDescriptor.MODE_READ_WRITE); } catch (FileNotFoundException e) { Log.w(CarLog.TAG_INPUT, "cannot open device for input injection:" + injectionDevice); return; } synchronized (this) { mInjectionDeviceFd = file; } hal.setInputListener(this); } @Override public void release() { synchronized (this) { mVoiceAssitantKeyListener = null; mLongVoiceAssitantKeyListener = null; mInstumentClusterKeyListener = null; if (mInjectionDeviceFd != null) { try { mInjectionDeviceFd.close(); } catch (IOException e) { } } mInjectionDeviceFd = null; mKeyEventCount = 0; } } @Override public void onKeyEvent(KeyEvent event, int targetDisplay) { synchronized (this) { mKeyEventCount++; } int keyCode = event.getKeyCode(); switch (keyCode) { case KeyEvent.KEYCODE_VOICE_ASSIST: handleVoiceAssistKey(event); return; default: break; } if (targetDisplay == InputHalService.DISPLAY_INSTRUMENT_CLUSTER) { handleInstrumentClusterKey(event); } else { handleMainDisplayKey(event); } } private void handleVoiceAssistKey(KeyEvent event) { int action = event.getAction(); if (action == KeyEvent.ACTION_DOWN) { long now = SystemClock.elapsedRealtime(); synchronized (this) { mLastVoiceKeyDownTime = now; } } else if (action == KeyEvent.ACTION_UP) { // if no listener, do not handle long press KeyEventListener listener = null; KeyEventListener shortPressListener = null; KeyEventListener longPressListener = null; long downTime; synchronized (this) { shortPressListener = mVoiceAssitantKeyListener; longPressListener = mLongVoiceAssitantKeyListener; downTime = mLastVoiceKeyDownTime; } if (shortPressListener == null && longPressListener == null) { launchDefaultVoiceAssitantHandler(); } else { long duration = SystemClock.elapsedRealtime() - downTime; listener = (duration > VOICE_LONG_PRESS_TIME_MS ? longPressListener : shortPressListener); if (listener != null) { listener.onKeyEvent(event); } else { launchDefaultVoiceAssitantHandler(); } } } } private void launchDefaultVoiceAssitantHandler() { Log.i(CarLog.TAG_INPUT, "voice key, launch default intent"); Intent voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE); mContext.startActivityAsUser(voiceIntent, null, UserHandle.CURRENT_OR_SELF); } private void handleInstrumentClusterKey(KeyEvent event) { KeyEventListener listener = null; synchronized (this) { listener = mInstumentClusterKeyListener; } if (listener == null) { return; } listener.onKeyEvent(event); } private void handleMainDisplayKey(KeyEvent event) { int fd; synchronized (this) { fd = mInjectionDeviceFd.getFd(); } int action = event.getAction(); boolean isDown = (action == KeyEvent.ACTION_DOWN); int keyCode = event.getKeyCode(); int r = nativeInjectKeyEvent(fd, keyCode, isDown); if (r != 0) { Log.e(CarLog.TAG_INPUT, "cannot inject key event, failed with:" + r); } } @Override public void dump(PrintWriter writer) { writer.println("*Input Service*"); writer.println("mInjectionDeviceFd:" + mInjectionDeviceFd); writer.println("mLastVoiceKeyDownTime:" + mLastVoiceKeyDownTime + ",mKeyEventCount:" + mKeyEventCount); } private native int nativeInjectKeyEvent(int fd, int keyCode, boolean isDown); }