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  * &lt;service android:name=".MyCarInputService"
46  *          android:permission="android.car.permission.BIND_CAR_INPUT_SERVICE">
47  * &lt;/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