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