/* * Copyright (C) 2022 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 android.hardware.usb; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.hardware.usb.IUsbOperationInternal; import android.hardware.usb.UsbPort; import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; /** * UsbOperationInternal allows UsbPort to support both synchronous and * asynchronous function irrespective of whether the underlying hal * method is synchronous or asynchronous. * * @hide */ public final class UsbOperationInternal extends IUsbOperationInternal.Stub { private static final String TAG = "UsbPortStatus"; private final int mOperationID; // Cached portId. private final String mId; // True implies operation did not timeout. private boolean mOperationComplete; private boolean mAsynchronous = false; private Executor mExecutor; private Consumer mConsumer; private int mResult = 0; private @UsbOperationStatus int mStatus; final ReentrantLock mLock = new ReentrantLock(); final Condition mOperationWait = mLock.newCondition(); // Maximum time the caller has to wait for onOperationComplete to be called. private static final int USB_OPERATION_TIMEOUT_MSECS = 5000; /** * The requested operation was successfully completed. * Returned in {@link onOperationComplete} and {@link getStatus}. */ public static final int USB_OPERATION_SUCCESS = 0; /** * The requested operation failed due to internal error. * Returned in {@link onOperationComplete} and {@link getStatus}. */ public static final int USB_OPERATION_ERROR_INTERNAL = 1; /** * The requested operation failed as it's not supported. * Returned in {@link onOperationComplete} and {@link getStatus}. */ public static final int USB_OPERATION_ERROR_NOT_SUPPORTED = 2; /** * The requested operation failed as it's not supported. * Returned in {@link onOperationComplete} and {@link getStatus}. */ public static final int USB_OPERATION_ERROR_PORT_MISMATCH = 3; @IntDef(prefix = { "USB_OPERATION_" }, value = { USB_OPERATION_SUCCESS, USB_OPERATION_ERROR_INTERNAL, USB_OPERATION_ERROR_NOT_SUPPORTED, USB_OPERATION_ERROR_PORT_MISMATCH }) @Retention(RetentionPolicy.SOURCE) @interface UsbOperationStatus{} UsbOperationInternal(int operationID, String id, Executor executor, Consumer consumer) { this.mOperationID = operationID; this.mId = id; this.mExecutor = executor; this.mConsumer = consumer; this.mAsynchronous = true; } UsbOperationInternal(int operationID, String id) { this.mOperationID = operationID; this.mId = id; } /** * Hal glue layer would directly call this function when the requested * operation is complete. */ @Override public void onOperationComplete(@UsbOperationStatus int status) { mLock.lock(); try { mOperationComplete = true; mStatus = status; Log.i(TAG, "Port:" + mId + " opID:" + mOperationID + " status:" + mStatus); if (mAsynchronous) { switch (mStatus) { case USB_OPERATION_SUCCESS: mResult = UsbPort.RESET_USB_PORT_SUCCESS; break; case USB_OPERATION_ERROR_INTERNAL: mResult = UsbPort.RESET_USB_PORT_ERROR_INTERNAL; break; case USB_OPERATION_ERROR_NOT_SUPPORTED: mResult = UsbPort.RESET_USB_PORT_ERROR_NOT_SUPPORTED; break; case USB_OPERATION_ERROR_PORT_MISMATCH: mResult = UsbPort.RESET_USB_PORT_ERROR_PORT_MISMATCH; break; default: mResult = UsbPort.RESET_USB_PORT_ERROR_OTHER; } mExecutor.execute(() -> mConsumer.accept(mResult)); } else { mOperationWait.signal(); } } finally { mLock.unlock(); } } /** * Caller invokes this function to wait for the operation to be complete. */ public void waitForOperationComplete() { mLock.lock(); try { long now = System.currentTimeMillis(); long deadline = now + USB_OPERATION_TIMEOUT_MSECS; // Wait in loop to overcome spurious wakeups. do { mOperationWait.await(deadline - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } while (!mOperationComplete && System.currentTimeMillis() < deadline); if (!mOperationComplete) { Log.e(TAG, "Port:" + mId + " opID:" + mOperationID + " operationComplete not received in " + USB_OPERATION_TIMEOUT_MSECS + "msecs"); } } catch (InterruptedException e) { Log.e(TAG, "Port:" + mId + " opID:" + mOperationID + " operationComplete interrupted"); } finally { mLock.unlock(); } } public @UsbOperationStatus int getStatus() { return mOperationComplete ? mStatus : USB_OPERATION_ERROR_INTERNAL; } }