blob: a7ecabb7c80e6369ca309cb17ff1092200a8f630 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.usb.hal.port;
import static android.hardware.usb.UsbManager.USB_HAL_NOT_SUPPORTED;
import static android.hardware.usb.UsbManager.USB_HAL_V1_0;
import static android.hardware.usb.UsbManager.USB_HAL_V1_1;
import static android.hardware.usb.UsbManager.USB_HAL_V1_2;
import static android.hardware.usb.UsbManager.USB_HAL_V1_3;
import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL;
import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_NOT_SUPPORTED;
import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_SUCCESS;
import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED;
import static android.hardware.usb.UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE;
import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST;
import static android.hardware.usb.UsbPortStatus.MODE_DFP;
import static android.hardware.usb.UsbPortStatus.MODE_DUAL;
import static android.hardware.usb.UsbPortStatus.MODE_UFP;
import static android.hardware.usb.UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN;
import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK;
import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE;
import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_FORCE;
import static android.hardware.usb.UsbPortStatus.DATA_STATUS_UNKNOWN;
import static android.hardware.usb.UsbPortStatus.PLUG_STATE_UNKNOWN;
import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_STATUS_UNKNOWN;
import static com.android.server.usb.UsbPortManager.logAndPrint;
import static com.android.server.usb.UsbPortManager.logAndPrintException;
import android.annotation.Nullable;
import android.hardware.usb.IUsbOperationInternal;
import android.hardware.usb.UsbManager.UsbHalVersion;
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
import android.hardware.usb.V1_0.IUsb;
import android.hardware.usb.V1_0.PortRoleType;
import android.hardware.usb.V1_0.Status;
import android.hardware.usb.V1_1.PortStatus_1_1;
import android.hardware.usb.V1_2.IUsbCallback;
import android.hardware.usb.V1_0.PortRole;
import android.hardware.usb.V1_2.PortStatus;
import android.hidl.manager.V1_0.IServiceManager;
import android.hidl.manager.V1_0.IServiceNotification;
import android.os.IHwBinder;
import android.os.RemoteException;
import android.util.Log;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.usb.UsbPortManager;
import com.android.server.usb.hal.port.RawPortInfo;
import java.util.ArrayList;
import java.util.NoSuchElementException;
import java.util.Objects;
/**
*
*/
public final class UsbPortHidl implements UsbPortHal {
private static final String TAG = UsbPortHidl.class.getSimpleName();
// Cookie sent for usb hal death notification.
private static final int USB_HAL_DEATH_COOKIE = 1000;
// Proxy object for the usb hal daemon.
@GuardedBy("mLock")
private IUsb mProxy;
private UsbPortManager mPortManager;
public IndentingPrintWriter mPw;
// Mutex for all mutable shared state.
private final Object mLock = new Object();
// Callback when the UsbPort status is changed by the kernel.
private HALCallback mHALCallback;
private boolean mSystemReady;
// Workaround since HIDL HAL versions report UsbDataEnabled status in UsbPortStatus;
private static int sUsbDataStatus = DATA_STATUS_UNKNOWN;
public @UsbHalVersion int getUsbHalVersion() throws RemoteException {
int version;
synchronized(mLock) {
if (mProxy == null) {
throw new RemoteException("IUsb not initialized yet");
}
if (android.hardware.usb.V1_3.IUsb.castFrom(mProxy) != null) {
version = USB_HAL_V1_3;
} else if (android.hardware.usb.V1_2.IUsb.castFrom(mProxy) != null) {
version = USB_HAL_V1_2;
} else if (android.hardware.usb.V1_1.IUsb.castFrom(mProxy) != null) {
version = USB_HAL_V1_1;
} else {
version = USB_HAL_V1_0;
}
logAndPrint(Log.INFO, null, "USB HAL HIDL version: " + version);
return version;
}
}
final class DeathRecipient implements IHwBinder.DeathRecipient {
public IndentingPrintWriter pw;
DeathRecipient(IndentingPrintWriter pw) {
this.pw = pw;
}
@Override
public void serviceDied(long cookie) {
if (cookie == USB_HAL_DEATH_COOKIE) {
logAndPrint(Log.ERROR, pw, "Usb hal service died cookie: " + cookie);
synchronized (mLock) {
mProxy = null;
}
}
}
}
final class ServiceNotification extends IServiceNotification.Stub {
@Override
public void onRegistration(String fqName, String name, boolean preexisting) {
logAndPrint(Log.INFO, null, "Usb hal service started " + fqName + " " + name);
connectToProxy(null);
}
}
private void connectToProxy(IndentingPrintWriter pw) {
synchronized (mLock) {
if (mProxy != null) {
return;
}
try {
mProxy = IUsb.getService();
mProxy.linkToDeath(new DeathRecipient(pw), USB_HAL_DEATH_COOKIE);
mProxy.setCallback(mHALCallback);
mProxy.queryPortStatus();
//updateUsbHalVersion();
} catch (NoSuchElementException e) {
logAndPrintException(pw, "connectToProxy: usb hal service not found."
+ " Did the service fail to start?", e);
} catch (RemoteException e) {
logAndPrintException(pw, "connectToProxy: usb hal service not responding", e);
}
}
}
@Override
public void systemReady() {
mSystemReady = true;
}
static boolean isServicePresent(IndentingPrintWriter pw) {
try {
IUsb.getService(true);
} catch (NoSuchElementException e) {
logAndPrintException(pw, "connectToProxy: usb hidl hal service not found.", e);
return false;
} catch (RemoteException e) {
logAndPrintException(pw, "IUSB hal service present but failed to get service", e);
}
return true;
}
public UsbPortHidl(UsbPortManager portManager, IndentingPrintWriter pw) {
mPortManager = Objects.requireNonNull(portManager);
mPw = pw;
mHALCallback = new HALCallback(null, mPortManager, this);
try {
ServiceNotification serviceNotification = new ServiceNotification();
boolean ret = IServiceManager.getService()
.registerForNotifications("android.hardware.usb@1.0::IUsb",
"", serviceNotification);
if (!ret) {
logAndPrint(Log.ERROR, null,
"Failed to register service start notification");
}
} catch (RemoteException e) {
logAndPrintException(null,
"Failed to register service start notification", e);
return;
}
connectToProxy(mPw);
}
@Override
public void enableContaminantPresenceDetection(String portName, boolean enable,
long transactionId) {
synchronized (mLock) {
if (mProxy == null) {
logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry !");
return;
}
try {
// Oneway call into the hal. Use the castFrom method from HIDL.
android.hardware.usb.V1_2.IUsb proxy =
android.hardware.usb.V1_2.IUsb.castFrom(mProxy);
proxy.enableContaminantPresenceDetection(portName, enable);
} catch (RemoteException e) {
logAndPrintException(mPw, "Failed to set contaminant detection", e);
} catch (ClassCastException e) {
logAndPrintException(mPw, "Method only applicable to V1.2 or above implementation",
e);
}
}
}
@Override
public void queryPortStatus(long transactionId) {
synchronized (mLock) {
if (mProxy == null) {
logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry !");
return;
}
try {
mProxy.queryPortStatus();
} catch (RemoteException e) {
logAndPrintException(null, "ServiceStart: Failed to query port status", e);
}
}
}
@Override
public void switchMode(String portId, @HalUsbPortMode int newMode, long transactionId) {
synchronized (mLock) {
if (mProxy == null) {
logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry !");
return;
}
PortRole newRole = new PortRole();
newRole.type = PortRoleType.MODE;
newRole.role = newMode;
try {
mProxy.switchRole(portId, newRole);
} catch (RemoteException e) {
logAndPrintException(mPw, "Failed to set the USB port mode: "
+ "portId=" + portId
+ ", newMode=" + UsbPort.modeToString(newRole.role), e);
}
}
}
@Override
public void switchPowerRole(String portId, @HalUsbPowerRole int newPowerRole,
long transactionId) {
synchronized (mLock) {
if (mProxy == null) {
logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry !");
return;
}
PortRole newRole = new PortRole();
newRole.type = PortRoleType.POWER_ROLE;
newRole.role = newPowerRole;
try {
mProxy.switchRole(portId, newRole);
} catch (RemoteException e) {
logAndPrintException(mPw, "Failed to set the USB power role: portId=" + portId
+ ", newPowerRole=" + UsbPort.powerRoleToString(newRole.role), e);
}
}
}
@Override
public void enableLimitPowerTransfer(String portName, boolean limit, long transactionId,
IUsbOperationInternal callback) {
/* Not supported in HIDL hals*/
try {
callback.onOperationComplete(USB_OPERATION_ERROR_NOT_SUPPORTED);
} catch (RemoteException e) {
logAndPrintException(mPw, "Failed to call onOperationComplete", e);
}
}
@Override
public void enableUsbDataWhileDocked(String portName, long transactionId,
IUsbOperationInternal callback) {
/* Not supported in HIDL hals*/
try {
callback.onOperationComplete(USB_OPERATION_ERROR_NOT_SUPPORTED);
} catch (RemoteException e) {
logAndPrintException(mPw, "Failed to call onOperationComplete", e);
}
}
@Override
public void switchDataRole(String portId, @HalUsbDataRole int newDataRole, long transactionId) {
synchronized (mLock) {
if (mProxy == null) {
logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry !");
return;
}
PortRole newRole = new PortRole();
newRole.type = PortRoleType.DATA_ROLE;
newRole.role = newDataRole;
try {
mProxy.switchRole(portId, newRole);
} catch (RemoteException e) {
logAndPrintException(mPw, "Failed to set the USB data role: portId=" + portId
+ ", newDataRole=" + UsbPort.dataRoleToString(newRole.role), e);
}
}
}
@Override
public void resetUsbPort(String portName, long transactionId,
IUsbOperationInternal callback) {
try {
callback.onOperationComplete(USB_OPERATION_ERROR_NOT_SUPPORTED);
} catch (RemoteException e) {
logAndPrintException(mPw, "Failed to call onOperationComplete. opID:"
+ transactionId
+ " portId:" + portName, e);
}
}
@Override
public boolean enableUsbData(String portName, boolean enable, long transactionId,
IUsbOperationInternal callback) {
int halVersion;
try {
halVersion = getUsbHalVersion();
} catch (RemoteException e) {
logAndPrintException(mPw, "Failed to query USB HAL version. opID:"
+ transactionId
+ " portId:" + portName, e);
return false;
}
if (halVersion != USB_HAL_V1_3) {
try {
callback.onOperationComplete(USB_OPERATION_ERROR_NOT_SUPPORTED);
} catch (RemoteException e) {
logAndPrintException(mPw, "Failed to call onOperationComplete. opID:"
+ transactionId
+ " portId:" + portName, e);
}
return false;
}
boolean success;
synchronized(mLock) {
try {
android.hardware.usb.V1_3.IUsb proxy
= android.hardware.usb.V1_3.IUsb.castFrom(mProxy);
success = proxy.enableUsbDataSignal(enable);
} catch (RemoteException e) {
logAndPrintException(mPw, "Failed enableUsbData: opId:" + transactionId
+ " portId=" + portName , e);
try {
callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
} catch (RemoteException r) {
logAndPrintException(mPw, "Failed to call onOperationComplete. opID:"
+ transactionId
+ " portId:" + portName, r);
}
return false;
}
}
if (success) {
sUsbDataStatus = enable ? DATA_STATUS_UNKNOWN : DATA_STATUS_DISABLED_FORCE;
}
try {
callback.onOperationComplete(success
? USB_OPERATION_SUCCESS
: USB_OPERATION_ERROR_INTERNAL);
} catch (RemoteException r) {
logAndPrintException(mPw, "Failed to call onOperationComplete. opID:"
+ transactionId
+ " portId:" + portName, r);
}
return false;
}
private static class HALCallback extends IUsbCallback.Stub {
public IndentingPrintWriter mPw;
public UsbPortManager mPortManager;
public UsbPortHidl mUsbPortHidl;
HALCallback(IndentingPrintWriter pw, UsbPortManager portManager, UsbPortHidl usbPortHidl) {
this.mPw = pw;
this.mPortManager = portManager;
this.mUsbPortHidl = usbPortHidl;
}
public void notifyPortStatusChange(
ArrayList<android.hardware.usb.V1_0.PortStatus> currentPortStatus, int retval) {
if (!mUsbPortHidl.mSystemReady) {
return;
}
if (retval != Status.SUCCESS) {
UsbPortManager.logAndPrint(Log.ERROR, mPw, "port status enquiry failed");
return;
}
ArrayList<RawPortInfo> newPortInfo = new ArrayList<>();
for (android.hardware.usb.V1_0.PortStatus current : currentPortStatus) {
RawPortInfo temp = new RawPortInfo(current.portName,
current.supportedModes, CONTAMINANT_PROTECTION_NONE,
current.currentMode,
current.canChangeMode, current.currentPowerRole,
current.canChangePowerRole,
current.currentDataRole, current.canChangeDataRole,
false, CONTAMINANT_PROTECTION_NONE,
false, CONTAMINANT_DETECTION_NOT_SUPPORTED, sUsbDataStatus,
false, POWER_BRICK_STATUS_UNKNOWN,
false, new int[] {},
PLUG_STATE_UNKNOWN,
0,
null);
newPortInfo.add(temp);
UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_0: "
+ current.portName);
}
mPortManager.updatePorts(newPortInfo);
}
public void notifyPortStatusChange_1_1(ArrayList<PortStatus_1_1> currentPortStatus,
int retval) {
if (!mUsbPortHidl.mSystemReady) {
return;
}
if (retval != Status.SUCCESS) {
UsbPortManager.logAndPrint(Log.ERROR, mPw, "port status enquiry failed");
return;
}
ArrayList<RawPortInfo> newPortInfo = new ArrayList<>();
int numStatus = currentPortStatus.size();
for (int i = 0; i < numStatus; i++) {
PortStatus_1_1 current = currentPortStatus.get(i);
RawPortInfo temp = new RawPortInfo(current.status.portName,
current.supportedModes, CONTAMINANT_PROTECTION_NONE,
current.currentMode,
current.status.canChangeMode, current.status.currentPowerRole,
current.status.canChangePowerRole,
current.status.currentDataRole, current.status.canChangeDataRole,
false, CONTAMINANT_PROTECTION_NONE,
false, CONTAMINANT_DETECTION_NOT_SUPPORTED, sUsbDataStatus,
false, POWER_BRICK_STATUS_UNKNOWN,
false, new int[] {},
PLUG_STATE_UNKNOWN,
0,
null);
newPortInfo.add(temp);
UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_1: "
+ current.status.portName);
}
mPortManager.updatePorts(newPortInfo);
}
public void notifyPortStatusChange_1_2(
ArrayList<PortStatus> currentPortStatus, int retval) {
if (!mUsbPortHidl.mSystemReady) {
return;
}
if (retval != Status.SUCCESS) {
UsbPortManager.logAndPrint(Log.ERROR, mPw, "port status enquiry failed");
return;
}
ArrayList<RawPortInfo> newPortInfo = new ArrayList<>();
int numStatus = currentPortStatus.size();
for (int i = 0; i < numStatus; i++) {
PortStatus current = currentPortStatus.get(i);
RawPortInfo temp = new RawPortInfo(current.status_1_1.status.portName,
current.status_1_1.supportedModes,
current.supportedContaminantProtectionModes,
current.status_1_1.currentMode,
current.status_1_1.status.canChangeMode,
current.status_1_1.status.currentPowerRole,
current.status_1_1.status.canChangePowerRole,
current.status_1_1.status.currentDataRole,
current.status_1_1.status.canChangeDataRole,
current.supportsEnableContaminantPresenceProtection,
current.contaminantProtectionStatus,
current.supportsEnableContaminantPresenceDetection,
current.contaminantDetectionStatus,
sUsbDataStatus,
false, POWER_BRICK_STATUS_UNKNOWN,
false, new int[] {},
PLUG_STATE_UNKNOWN,
0,
null);
newPortInfo.add(temp);
UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_2: "
+ current.status_1_1.status.portName);
}
mPortManager.updatePorts(newPortInfo);
}
public void notifyRoleSwitchStatus(String portName, PortRole role, int retval) {
if (retval == Status.SUCCESS) {
UsbPortManager.logAndPrint(Log.INFO, mPw, portName + " role switch successful");
} else {
UsbPortManager.logAndPrint(Log.ERROR, mPw, portName + " role switch failed");
}
}
}
}