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