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 android.car.input; 17 18 import android.annotation.CallSuper; 19 import android.annotation.MainThread; 20 import android.annotation.SystemApi; 21 import android.app.Service; 22 import android.car.Car; 23 import android.car.CarLibLog; 24 import android.content.Intent; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.os.IBinder; 28 import android.os.Message; 29 import android.os.Parcel; 30 import android.os.Parcelable; 31 import android.os.RemoteException; 32 import android.util.Log; 33 import android.view.KeyEvent; 34 35 import java.io.FileDescriptor; 36 import java.io.PrintWriter; 37 import java.lang.ref.WeakReference; 38 39 /** 40 * A service that is used for handling of input events. 41 * 42 * <p>To extend this class, you must declare the service in your manifest file with 43 * the {@code android.car.permission.BIND_CAR_INPUT_SERVICE} permission 44 * <pre> 45 * <service android:name=".MyCarInputService" 46 * android:permission="android.car.permission.BIND_CAR_INPUT_SERVICE"> 47 * </service></pre> 48 * <p>Also, you will need to register this service in the following configuration file: 49 * {@code packages/services/Car/service/res/values/config.xml} 50 * 51 * @hide 52 */ 53 @SystemApi 54 public abstract class CarInputHandlingService extends Service { 55 private static final String TAG = CarLibLog.TAG_INPUT; 56 private static final boolean DBG = false; 57 58 public static final String INPUT_CALLBACK_BINDER_KEY = "callback_binder"; 59 public static final int INPUT_CALLBACK_BINDER_CODE = IBinder.FIRST_CALL_TRANSACTION; 60 61 private final InputFilter[] mHandledKeys; 62 63 private InputBinder mInputBinder; 64 CarInputHandlingService(InputFilter[] handledKeys)65 protected CarInputHandlingService(InputFilter[] handledKeys) { 66 if (handledKeys == null) { 67 throw new IllegalArgumentException("handledKeys is null"); 68 } 69 70 mHandledKeys = new InputFilter[handledKeys.length]; 71 System.arraycopy(handledKeys, 0, mHandledKeys, 0, handledKeys.length); 72 } 73 74 @Override 75 @CallSuper onBind(Intent intent)76 public IBinder onBind(Intent intent) { 77 if (DBG) { 78 Log.d(TAG, "onBind, intent: " + intent); 79 } 80 81 doCallbackIfPossible(intent.getExtras()); 82 83 if (mInputBinder == null) { 84 mInputBinder = new InputBinder(); 85 } 86 87 return mInputBinder; 88 } 89 doCallbackIfPossible(Bundle extras)90 private void doCallbackIfPossible(Bundle extras) { 91 if (extras == null) { 92 Log.i(TAG, "doCallbackIfPossible: extras are null"); 93 return; 94 } 95 IBinder callbackBinder = extras.getBinder(INPUT_CALLBACK_BINDER_KEY); 96 if (callbackBinder == null) { 97 Log.i(TAG, "doCallbackIfPossible: callback IBinder is null"); 98 return; 99 } 100 Parcel dataIn = Parcel.obtain(); 101 dataIn.writeTypedArray(mHandledKeys, 0); 102 try { 103 callbackBinder.transact(INPUT_CALLBACK_BINDER_CODE, dataIn, null, IBinder.FLAG_ONEWAY); 104 } catch (RemoteException e) { 105 Car.handleRemoteExceptionFromCarService(this, e); 106 } 107 } 108 109 /** 110 * Called when key event has been received. 111 */ 112 @MainThread onKeyEvent(KeyEvent keyEvent, int targetDisplay)113 protected abstract void onKeyEvent(KeyEvent keyEvent, int targetDisplay); 114 115 @Override dump(FileDescriptor fd, PrintWriter writer, String[] args)116 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 117 writer.println("**" + getClass().getSimpleName() + "**"); 118 writer.println("input binder: " + mInputBinder); 119 } 120 121 private class InputBinder extends ICarInputListener.Stub { 122 private final EventHandler mEventHandler; 123 InputBinder()124 InputBinder() { 125 mEventHandler = new EventHandler(CarInputHandlingService.this); 126 } 127 128 @Override onKeyEvent(KeyEvent keyEvent, int targetDisplay)129 public void onKeyEvent(KeyEvent keyEvent, int targetDisplay) throws RemoteException { 130 mEventHandler.doKeyEvent(keyEvent, targetDisplay); 131 } 132 } 133 134 private static class EventHandler extends Handler { 135 private static final int KEY_EVENT = 0; 136 private final WeakReference<CarInputHandlingService> mRefService; 137 EventHandler(CarInputHandlingService service)138 EventHandler(CarInputHandlingService service) { 139 mRefService = new WeakReference<>(service); 140 } 141 142 @Override handleMessage(Message msg)143 public void handleMessage(Message msg) { 144 CarInputHandlingService service = mRefService.get(); 145 if (service == null) { 146 return; 147 } 148 149 if (msg.what == KEY_EVENT) { 150 service.onKeyEvent((KeyEvent) msg.obj, msg.arg1); 151 } else { 152 throw new IllegalArgumentException("Unexpected message: " + msg); 153 } 154 } 155 doKeyEvent(KeyEvent event, int targetDisplay)156 void doKeyEvent(KeyEvent event, int targetDisplay) { 157 sendMessage(obtainMessage(KEY_EVENT, targetDisplay, 0, event)); 158 } 159 } 160 161 /** 162 * Filter for input events that are handled by custom service. 163 */ 164 public static final class InputFilter implements Parcelable { 165 public final int mKeyCode; 166 public final int mTargetDisplay; 167 InputFilter(int keyCode, int targetDisplay)168 public InputFilter(int keyCode, int targetDisplay) { 169 mKeyCode = keyCode; 170 mTargetDisplay = targetDisplay; 171 } 172 173 // Parcelling part InputFilter(Parcel in)174 InputFilter(Parcel in) { 175 mKeyCode = in.readInt(); 176 mTargetDisplay = in.readInt(); 177 } 178 179 @Override describeContents()180 public int describeContents() { 181 return 0; 182 } 183 184 @Override writeToParcel(Parcel dest, int flags)185 public void writeToParcel(Parcel dest, int flags) { 186 dest.writeInt(mKeyCode); 187 dest.writeInt(mTargetDisplay); 188 } 189 190 public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 191 public InputFilter createFromParcel(Parcel in) { 192 return new InputFilter(in); 193 } 194 195 public InputFilter[] newArray(int size) { 196 return new InputFilter[size]; 197 } 198 }; 199 } 200 } 201