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