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