1 /*
2  * Copyright (C) 2019 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 
17 package android.car;
18 
19 import android.annotation.IntDef;
20 import android.annotation.MainThread;
21 import android.annotation.NonNull;
22 import android.annotation.SystemApi;
23 import android.app.Service;
24 import android.content.Intent;
25 import android.hardware.usb.UsbDevice;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.os.IBinder;
29 import android.os.Looper;
30 import android.os.Message;
31 import android.os.Messenger;
32 import android.os.RemoteException;
33 import android.util.Log;
34 
35 import java.io.FileDescriptor;
36 import java.io.PrintWriter;
37 import java.lang.annotation.Retention;
38 import java.lang.annotation.RetentionPolicy;
39 import java.lang.ref.WeakReference;
40 import java.util.Objects;
41 
42 /**
43  * The service that must be implemented by USB AOAP handler system apps. The app must hold the
44  * following permission: {@code android.car.permission.CAR_HANDLE_USB_AOAP_DEVICE}.
45  *
46  * <p>This service gets bound by the framework and the service needs to be protected by
47  * {@code android.permission.MANAGE_USB} permission to ensure nobody else can
48  * bind to the service. At most only one client should be bound at a time.
49  *
50  * @hide
51  */
52 @SystemApi
53 public abstract class AoapService extends Service {
54     private static final String TAG = AoapService.class.getSimpleName();
55 
56     /** Indicates success or confirmation. */
57     public static final int RESULT_OK = 0;
58 
59     /**
60      * Indicates that the device is not supported by this service and system shouldn't associate
61      * given device with this service.
62      */
63     public static final int RESULT_DEVICE_NOT_SUPPORTED = 1;
64 
65     /**
66      * Indicates that device shouldn't be switch to AOAP mode at this time.
67      */
68     public static final int RESULT_DO_NOT_SWITCH_TO_AOAP = 2;
69 
70     /** @hide */
71     @IntDef(value = {
72             RESULT_OK, RESULT_DEVICE_NOT_SUPPORTED, RESULT_DO_NOT_SWITCH_TO_AOAP
73     })
74     @Retention(RetentionPolicy.SOURCE)
75     public @interface Result {}
76 
77 
78     /**
79      * A message sent from the system USB handler service to AOAP handler service to check if the
80      * device is supported. The message must have {@link #KEY_DEVICE} with {@link UsbDevice} object.
81      *
82      * @hide
83      */
84     public static final int MSG_NEW_DEVICE_ATTACHED = 1;
85 
86     /**
87      * A response message for {@link #MSG_NEW_DEVICE_ATTACHED}. Must contain {@link #KEY_RESULT}
88      * with one of the {@code RESULT_*} constant.
89      *
90      * @hide */
91     public static final int MSG_NEW_DEVICE_ATTACHED_RESPONSE = 2;
92 
93     /**
94      * A message sent from the system USB handler service to AOAP handler service to check if the
95      * device can be switched to AOAP mode. The message must have {@link #KEY_DEVICE} with
96      * {@link UsbDevice} object.
97      *
98      * @hide
99      */
100     public static final int MSG_CAN_SWITCH_TO_AOAP = 3;
101 
102     /**
103      * A response message for {@link #MSG_CAN_SWITCH_TO_AOAP}. Must contain {@link #KEY_RESULT}
104      * with one of the {@code RESULT_*} constant.
105      *
106      * @hide */
107     public static final int MSG_CAN_SWITCH_TO_AOAP_RESPONSE = 4;
108 
109     /** @hide */
110     public static final String KEY_DEVICE = "usb-device";
111 
112     /** @hide */
113     public static final String KEY_RESULT = "result";
114 
115 
116     /**
117      * Returns {@code true} if the given USB device is supported by this service.
118      *
119      * <p>The device is not expected to be in AOAP mode when this method is called. The purpose of
120      * this method is just to give the service a chance to tell whether based on the information
121      * provided in {@link UsbDevice} class (e.g. PID/VID) this service supports or doesn't support
122      * given device.
123      *
124      * <p>The method must return one of the following status: {@link #RESULT_OK} or
125      * {@link #RESULT_DEVICE_NOT_SUPPORTED}
126      */
127     @MainThread
isDeviceSupported(@onNull UsbDevice device)128     public abstract @Result int isDeviceSupported(@NonNull UsbDevice device);
129 
130     /**
131      * This method will be called at least once per connection session before switching device into
132      * AOAP mode.
133      *
134      * <p>The device is connected, but not in AOAP mode yet. Implementors of this method may ask
135      * the framework to ignore this device for now and do not switch to AOAP. This may make sense if
136      * a connection to the device has been established through other means, and switching the device
137      * to AOAP would break that connection.
138      *
139      * <p>Note: the method may be called only if this device was claimed to be supported in
140      * {@link #isDeviceSupported(UsbDevice)} method, and this app has been chosen to handle the
141      * device.
142      *
143      * <p>The method must return one of the following status: {@link #RESULT_OK},
144      * {@link #RESULT_DEVICE_NOT_SUPPORTED} or {@link #RESULT_DO_NOT_SWITCH_TO_AOAP}
145      */
146     @MainThread
canSwitchToAoap(@onNull UsbDevice device)147     public @Result int canSwitchToAoap(@NonNull UsbDevice device) {
148         return RESULT_OK;
149     }
150 
151     private Messenger mMessenger;
152     private boolean mBound;
153 
154     @Override
onCreate()155     public void onCreate() {
156         super.onCreate();
157         mMessenger = new Messenger(new IncomingHandler(this));
158     }
159 
160     @Override
onBind(Intent intent)161     public IBinder onBind(Intent intent) {
162         if (mBound) {
163             Log.w(TAG, "Received onBind event when the service was already bound");
164         }
165         mBound = true;
166         return mMessenger.getBinder();
167     }
168 
169     @Override
onUnbind(Intent intent)170     public boolean onUnbind(Intent intent) {
171         mBound = false;
172         return super.onUnbind(intent);
173     }
174 
175     @Override
dump(FileDescriptor fd, PrintWriter writer, String[] args)176     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
177         writer.write("Bound: " + mBound);
178     }
179 
180     private static class IncomingHandler extends Handler {
181         private final WeakReference<AoapService> mServiceRef;
182 
IncomingHandler(AoapService service)183         IncomingHandler(AoapService service) {
184             super(Looper.getMainLooper());
185             mServiceRef = new WeakReference<>(service);
186         }
187 
188         @Override
handleMessage(Message msg)189         public void handleMessage(Message msg) {
190             AoapService service = mServiceRef.get();
191             if (service == null) {
192                 return;
193             }
194             Bundle data = msg.getData();
195             if (data == null) {
196                 Log.e(TAG, "Ignoring message " + msg.what + " without data");
197                 return;
198             }
199 
200             Log.i(TAG, "Message received: " + msg.what);
201 
202             switch (msg.what) {
203                 case MSG_NEW_DEVICE_ATTACHED: {
204                     int res = service.isDeviceSupported(
205                             Objects.requireNonNull(data.getParcelable(KEY_DEVICE)));
206                     if (res != RESULT_OK && res != RESULT_DEVICE_NOT_SUPPORTED) {
207                         throw new IllegalArgumentException("Result can not be " + res);
208                     }
209                     sendResponse(msg.replyTo, MSG_NEW_DEVICE_ATTACHED_RESPONSE, res);
210                     break;
211                 }
212 
213                 case MSG_CAN_SWITCH_TO_AOAP: {
214                     int res = service.canSwitchToAoap(
215                             Objects.requireNonNull(data.getParcelable(KEY_DEVICE)));
216                     if (res != RESULT_OK && res != RESULT_DEVICE_NOT_SUPPORTED
217                             && res != RESULT_DO_NOT_SWITCH_TO_AOAP) {
218                         throw new IllegalArgumentException("Result can not be " + res);
219                     }
220                     sendResponse(msg.replyTo, MSG_CAN_SWITCH_TO_AOAP_RESPONSE, res);
221                     break;
222                 }
223 
224                 default:
225                     Log.e(TAG, "Unknown message received: " + msg.what);
226                     break;
227             }
228         }
229 
sendResponse(Messenger messenger, int msg, int result)230         private void sendResponse(Messenger messenger, int msg, int result) {
231             try {
232                 messenger.send(createResponseMessage(msg, result));
233             } catch (RemoteException e) {
234                 Log.e(TAG, "Failed to send message", e);
235             }
236         }
237 
createResponseMessage(int msg, int result)238         private Message createResponseMessage(int msg, int result) {
239             Message response = Message.obtain(null, msg);
240             Bundle data = new Bundle();
241             data.putInt(KEY_RESULT, result);
242             response.setData(data);
243             return response;
244         }
245     }
246 }
247