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