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