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 static android.hardware.input.InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
     19 
     20 import android.car.input.CarInputHandlingService;
     21 import android.car.input.CarInputHandlingService.InputFilter;
     22 import android.car.input.ICarInputListener;
     23 import android.content.ComponentName;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.ServiceConnection;
     27 import android.hardware.input.InputManager;
     28 import android.net.Uri;
     29 import android.os.Binder;
     30 import android.os.Bundle;
     31 import android.os.IBinder;
     32 import android.os.Parcel;
     33 import android.os.ParcelFileDescriptor;
     34 import android.os.RemoteException;
     35 import android.os.SystemClock;
     36 import android.os.UserHandle;
     37 import android.provider.CallLog.Calls;
     38 import android.speech.RecognizerIntent;
     39 import android.telecom.TelecomManager;
     40 import android.text.TextUtils;
     41 import android.util.Log;
     42 import android.view.KeyEvent;
     43 
     44 import com.android.car.hal.InputHalService;
     45 import com.android.car.hal.VehicleHal;
     46 
     47 import java.io.PrintWriter;
     48 import java.util.HashMap;
     49 import java.util.HashSet;
     50 import java.util.Map;
     51 import java.util.Set;
     52 
     53 public class CarInputService implements CarServiceBase, InputHalService.InputListener {
     54 
     55     public interface KeyEventListener {
     56         boolean onKeyEvent(KeyEvent event);
     57     }
     58 
     59     private static final long LONG_PRESS_TIME_MS = 1000;
     60     private static final boolean DBG = false;
     61 
     62     private final Context mContext;
     63     private final InputHalService mInputHalService;
     64     private final TelecomManager mTelecomManager;
     65     private final InputManager mInputManager;
     66 
     67     private KeyEventListener mVoiceAssistantKeyListener;
     68     private KeyEventListener mLongVoiceAssistantKeyListener;
     69     private long mLastVoiceKeyDownTime = 0;
     70 
     71     private long mLastCallKeyDownTime = 0;
     72 
     73     private KeyEventListener mInstrumentClusterKeyListener;
     74 
     75     private KeyEventListener mVolumeKeyListener;
     76 
     77     private ICarInputListener mCarInputListener;
     78     private boolean mCarInputListenerBound = false;
     79     private final Map<Integer, Set<Integer>> mHandledKeys = new HashMap<>();
     80 
     81     private int mKeyEventCount = 0;
     82 
     83     private final Binder mCallback = new Binder() {
     84         @Override
     85         protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) {
     86             if (code == CarInputHandlingService.INPUT_CALLBACK_BINDER_CODE) {
     87                 data.setDataPosition(0);
     88                 InputFilter[] handledKeys = (InputFilter[]) data.createTypedArray(
     89                         InputFilter.CREATOR);
     90                 if (handledKeys != null) {
     91                     setHandledKeys(handledKeys);
     92                 }
     93                 return true;
     94             }
     95             return false;
     96         }
     97     };
     98 
     99     private final ServiceConnection mInputServiceConnection = new ServiceConnection() {
    100         @Override
    101         public void onServiceConnected(ComponentName name, IBinder binder) {
    102             if (DBG) {
    103                 Log.d(CarLog.TAG_INPUT, "onServiceConnected, name: "
    104                         + name + ", binder: " + binder);
    105             }
    106             mCarInputListener = ICarInputListener.Stub.asInterface(binder);
    107 
    108             try {
    109                 binder.linkToDeath(() -> CarServiceUtils.runOnMainSync(() -> {
    110                     Log.w(CarLog.TAG_INPUT, "Input service died. Trying to rebind...");
    111                     mCarInputListener = null;
    112                     // Try to rebind with input service.
    113                     mCarInputListenerBound = bindCarInputService();
    114                 }), 0);
    115             } catch (RemoteException e) {
    116                 Log.e(CarLog.TAG_INPUT, e.getMessage(), e);
    117             }
    118         }
    119 
    120         @Override
    121         public void onServiceDisconnected(ComponentName name) {
    122             Log.d(CarLog.TAG_INPUT, "onServiceDisconnected, name: " + name);
    123             mCarInputListener = null;
    124             // Try to rebind with input service.
    125             mCarInputListenerBound = bindCarInputService();
    126         }
    127     };
    128 
    129     public CarInputService(Context context, InputHalService inputHalService) {
    130         mContext = context;
    131         mInputHalService = inputHalService;
    132         mTelecomManager = context.getSystemService(TelecomManager.class);
    133         mInputManager = context.getSystemService(InputManager.class);
    134     }
    135 
    136     private synchronized void setHandledKeys(InputFilter[] handledKeys) {
    137         mHandledKeys.clear();
    138         for (InputFilter handledKey : handledKeys) {
    139             Set<Integer> displaySet = mHandledKeys.get(handledKey.mTargetDisplay);
    140             if (displaySet == null) {
    141                 displaySet = new HashSet<Integer>();
    142                 mHandledKeys.put(handledKey.mTargetDisplay, displaySet);
    143             }
    144             displaySet.add(handledKey.mKeyCode);
    145         }
    146     }
    147 
    148     /**
    149      * Set listener for listening voice assistant key event. Setting to null stops listening.
    150      * If listener is not set, default behavior will be done for short press.
    151      * If listener is set, short key press will lead into calling the listener.
    152      * @param listener
    153      */
    154     public void setVoiceAssistantKeyListener(KeyEventListener listener) {
    155         synchronized (this) {
    156             mVoiceAssistantKeyListener = listener;
    157         }
    158     }
    159 
    160     /**
    161      * Set listener for listening long voice assistant key event. Setting to null stops listening.
    162      * If listener is not set, default behavior will be done for long press.
    163      * If listener is set, short long press will lead into calling the listener.
    164      * @param listener
    165      */
    166     public void setLongVoiceAssistantKeyListener(KeyEventListener listener) {
    167         synchronized (this) {
    168             mLongVoiceAssistantKeyListener = listener;
    169         }
    170     }
    171 
    172     public void setInstrumentClusterKeyListener(KeyEventListener listener) {
    173         synchronized (this) {
    174             mInstrumentClusterKeyListener = listener;
    175         }
    176     }
    177 
    178     public void setVolumeKeyListener(KeyEventListener listener) {
    179         synchronized (this) {
    180             mVolumeKeyListener = listener;
    181         }
    182     }
    183 
    184     @Override
    185     public void init() {
    186         if (!mInputHalService.isKeyInputSupported()) {
    187             Log.w(CarLog.TAG_INPUT, "Hal does not support key input.");
    188             return;
    189         }
    190 
    191 
    192         mInputHalService.setInputListener(this);
    193         mCarInputListenerBound = bindCarInputService();
    194     }
    195 
    196     @Override
    197     public void release() {
    198         synchronized (this) {
    199             mVoiceAssistantKeyListener = null;
    200             mLongVoiceAssistantKeyListener = null;
    201             mInstrumentClusterKeyListener = null;
    202             mKeyEventCount = 0;
    203             if (mCarInputListenerBound) {
    204                 mContext.unbindService(mInputServiceConnection);
    205                 mCarInputListenerBound = false;
    206             }
    207         }
    208     }
    209 
    210     @Override
    211     public void onKeyEvent(KeyEvent event, int targetDisplay) {
    212         synchronized (this) {
    213             mKeyEventCount++;
    214         }
    215         if (handleSystemEvent(event)) {
    216             // System event handled, nothing more to do here.
    217             return;
    218         }
    219         if (mCarInputListener != null && isCustomEventHandler(event, targetDisplay)) {
    220             try {
    221                 mCarInputListener.onKeyEvent(event, targetDisplay);
    222             } catch (RemoteException e) {
    223                 Log.e(CarLog.TAG_INPUT, "Error while calling car input service", e);
    224             }
    225             // Custom input service handled the event, nothing more to do here.
    226             return;
    227         }
    228 
    229         switch (event.getKeyCode()) {
    230             case KeyEvent.KEYCODE_VOICE_ASSIST:
    231                 handleVoiceAssistKey(event);
    232                 return;
    233             case KeyEvent.KEYCODE_CALL:
    234                 handleCallKey(event);
    235                 return;
    236             default:
    237                 break;
    238         }
    239         if (targetDisplay == InputHalService.DISPLAY_INSTRUMENT_CLUSTER) {
    240             handleInstrumentClusterKey(event);
    241         } else {
    242             handleMainDisplayKey(event);
    243         }
    244     }
    245 
    246     private synchronized boolean isCustomEventHandler(KeyEvent event, int targetDisplay) {
    247         Set<Integer> displaySet = mHandledKeys.get(targetDisplay);
    248         if (displaySet == null) {
    249             return false;
    250         }
    251         return displaySet.contains(event.getKeyCode());
    252     }
    253 
    254     private boolean handleSystemEvent(KeyEvent event) {
    255         switch (event.getKeyCode()) {
    256             case KeyEvent.KEYCODE_VOLUME_UP:
    257             case KeyEvent.KEYCODE_VOLUME_DOWN:
    258                 handleVolumeKey(event);
    259                 return true;
    260             default:
    261                 return false;
    262         }
    263     }
    264 
    265     private void handleVoiceAssistKey(KeyEvent event) {
    266         int action = event.getAction();
    267         if (action == KeyEvent.ACTION_DOWN) {
    268             long now = SystemClock.elapsedRealtime();
    269             synchronized (this) {
    270                 mLastVoiceKeyDownTime = now;
    271             }
    272         } else if (action == KeyEvent.ACTION_UP) {
    273             // if no listener, do not handle long press
    274             KeyEventListener listener = null;
    275             KeyEventListener shortPressListener = null;
    276             KeyEventListener longPressListener = null;
    277             long downTime;
    278             synchronized (this) {
    279                 shortPressListener = mVoiceAssistantKeyListener;
    280                 longPressListener = mLongVoiceAssistantKeyListener;
    281                 downTime = mLastVoiceKeyDownTime;
    282             }
    283             if (shortPressListener == null && longPressListener == null) {
    284                 launchDefaultVoiceAssistantHandler();
    285             } else {
    286                 long duration = SystemClock.elapsedRealtime() - downTime;
    287                 listener = (duration > LONG_PRESS_TIME_MS
    288                         ? longPressListener : shortPressListener);
    289                 if (listener != null) {
    290                     listener.onKeyEvent(event);
    291                 } else {
    292                     launchDefaultVoiceAssistantHandler();
    293                 }
    294             }
    295         }
    296     }
    297 
    298     private void handleCallKey(KeyEvent event) {
    299         int action = event.getAction();
    300         if (action == KeyEvent.ACTION_DOWN) {
    301             // Only handle if it's ringing when button down.
    302             if (mTelecomManager != null && mTelecomManager.isRinging()) {
    303                 Log.i(CarLog.TAG_INPUT, "call key while rinning. Answer the call!");
    304                 mTelecomManager.acceptRingingCall();
    305                 return;
    306             }
    307 
    308             long now = SystemClock.elapsedRealtime();
    309             synchronized (this) {
    310                 mLastCallKeyDownTime = now;
    311             }
    312         } else if (action == KeyEvent.ACTION_UP) {
    313             long downTime;
    314             synchronized (this) {
    315                 downTime = mLastCallKeyDownTime;
    316             }
    317             long duration = SystemClock.elapsedRealtime() - downTime;
    318             if (duration > LONG_PRESS_TIME_MS) {
    319                 dialLastCallHandler();
    320             } else {
    321                 launchDialerHandler();
    322             }
    323         }
    324     }
    325 
    326     private void launchDialerHandler() {
    327         Log.i(CarLog.TAG_INPUT, "call key, launch dialer intent");
    328         Intent dialerIntent = new Intent(Intent.ACTION_DIAL);
    329         mContext.startActivityAsUser(dialerIntent, null, UserHandle.CURRENT_OR_SELF);
    330     }
    331 
    332     private void dialLastCallHandler() {
    333         Log.i(CarLog.TAG_INPUT, "call key, dialing last call");
    334 
    335         String lastNumber = Calls.getLastOutgoingCall(mContext);
    336         if (lastNumber != null && !lastNumber.isEmpty()) {
    337             Intent callLastNumberIntent = new Intent(Intent.ACTION_CALL)
    338                     .setData(Uri.fromParts("tel", lastNumber, null))
    339                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    340             mContext.startActivityAsUser(callLastNumberIntent, null, UserHandle.CURRENT_OR_SELF);
    341         }
    342     }
    343 
    344     private void launchDefaultVoiceAssistantHandler() {
    345         Log.i(CarLog.TAG_INPUT, "voice key, launch default intent");
    346         Intent voiceIntent =
    347                 new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
    348         mContext.startActivityAsUser(voiceIntent, null, UserHandle.CURRENT_OR_SELF);
    349     }
    350 
    351     private void handleInstrumentClusterKey(KeyEvent event) {
    352         KeyEventListener listener = null;
    353         synchronized (this) {
    354             listener = mInstrumentClusterKeyListener;
    355         }
    356         if (listener == null) {
    357             return;
    358         }
    359         listener.onKeyEvent(event);
    360     }
    361 
    362     private void handleVolumeKey(KeyEvent event) {
    363         KeyEventListener listener;
    364         synchronized (this) {
    365             listener = mVolumeKeyListener;
    366         }
    367         if (listener != null) {
    368             listener.onKeyEvent(event);
    369         }
    370     }
    371 
    372     private void handleMainDisplayKey(KeyEvent event) {
    373         mInputManager.injectInputEvent(event, INJECT_INPUT_EVENT_MODE_ASYNC);
    374     }
    375 
    376     @Override
    377     public void dump(PrintWriter writer) {
    378         writer.println("*Input Service*");
    379         writer.println("mCarInputListenerBound:" + mCarInputListenerBound);
    380         writer.println("mCarInputListener:" + mCarInputListener);
    381         writer.println("mLastVoiceKeyDownTime:" + mLastVoiceKeyDownTime +
    382                 ",mKeyEventCount:" + mKeyEventCount);
    383     }
    384 
    385     private boolean bindCarInputService() {
    386         String carInputService = mContext.getString(R.string.inputService);
    387         if (TextUtils.isEmpty(carInputService)) {
    388             Log.i(CarLog.TAG_INPUT, "Custom input service was not configured");
    389             return false;
    390         }
    391 
    392         Log.d(CarLog.TAG_INPUT, "bindCarInputService, component: " + carInputService);
    393 
    394         Intent intent = new Intent();
    395         Bundle extras = new Bundle();
    396         extras.putBinder(CarInputHandlingService.INPUT_CALLBACK_BINDER_KEY, mCallback);
    397         intent.putExtras(extras);
    398         intent.setComponent(ComponentName.unflattenFromString(carInputService));
    399         return mContext.bindService(intent, mInputServiceConnection, Context.BIND_AUTO_CREATE);
    400     }
    401 }
    402