Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 327b809a authored by Jerry Zhang's avatar Jerry Zhang
Browse files

Refactor and clean up USB, add tests

Change UsbManager apis to use long instead of string, to match
usb hal. Change UsbDeviceManager internals to match as well.

Remove isFunctionEnabled and add getEnabledFunctions. Callers
would often call isFunctionEnabled for every possible function
to get the list of functions, so getEnabledFunctions reduces the
number of aidl calls.

Separate out dependencies between UsbHandler and UsbDeviceManager
and staticize the UsbHandler classes. Add unit tests with
mocked out dependencies to test state transitions for UsbHandler.

Bug: 62876645
Test: atest UsbTests
Change-Id: I785c4c24121a70e725de9742c6af50a6bf1baea0
parent 1810393c
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -676,6 +676,7 @@ java_library {
        "android.hardware.vibrator-V1.1-java-constants",
        "android.hardware.wifi-V1.0-java-constants",
        "android.hardware.radio-V1.0-java",
        "android.hardware.usb.gadget-V1.0-java",
    ],

    // Loaded with System.loadLibrary by android.view.textclassifier
+21 −22
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import android.hardware.usb.IUsbManager;
import android.hardware.usb.UsbManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;

public class UsbCommand extends Svc.Command {
    public UsbCommand() {
@@ -37,41 +36,41 @@ public class UsbCommand extends Svc.Command {
    public String longHelp() {
        return shortHelp() + "\n"
                + "\n"
                + "usage: svc usb setFunction [function] [usbDataUnlocked=false]\n"
                + "         Set the current usb function and optionally the data lock state.\n\n"
                + "usage: svc usb setFunctions [function]\n"
                + "         Set the current usb function. If function is blank, sets to charging.\n"
                + "       svc usb setScreenUnlockedFunctions [function]\n"
                + "         Sets the functions which, if the device was charging,"
                    + " become current on screen unlock.\n"
                + "       svc usb getFunction\n"
                + "          Gets the list of currently enabled functions\n";
                + "         Sets the functions which, if the device was charging, become current on"
                    + "screen unlock. If function is blank, turn off this feature.\n"
                + "       svc usb getFunctions\n"
                + "          Gets the list of currently enabled functions\n\n"
                + "possible values of [function] are any of 'mtp', 'ptp', 'rndis', 'midi'\n";
    }

    @Override
    public void run(String[] args) {
        boolean validCommand = false;
        if (args.length >= 2) {
            if ("setFunction".equals(args[1])) {
            IUsbManager usbMgr = IUsbManager.Stub.asInterface(ServiceManager.getService(
                    Context.USB_SERVICE));
                boolean unlockData = false;
                if (args.length >= 4) {
                    unlockData = Boolean.valueOf(args[3]);
                }
            if ("setFunctions".equals(args[1])) {
                try {
                    usbMgr.setCurrentFunction((args.length >=3 ? args[2] : null), unlockData);
                    usbMgr.setCurrentFunctions(UsbManager.usbFunctionsFromString(
                            args.length >= 3 ? args[2] : ""));
                } catch (RemoteException e) {
                    System.err.println("Error communicating with UsbManager: " + e);
                }
                return;
            } else if ("getFunction".equals(args[1])) {
                System.err.println(SystemProperties.get("sys.usb.config"));
            } else if ("getFunctions".equals(args[1])) {
                try {
                    System.err.println(
                            UsbManager.usbFunctionsToString(usbMgr.getCurrentFunctions()));
                } catch (RemoteException e) {
                    System.err.println("Error communicating with UsbManager: " + e);
                }
                return;
            } else if ("setScreenUnlockedFunctions".equals(args[1])) {
                IUsbManager usbMgr = IUsbManager.Stub.asInterface(ServiceManager.getService(
                        Context.USB_SERVICE));
                try {
                    usbMgr.setScreenUnlockedFunctions((args.length >= 3 ? args[2] :
                            UsbManager.USB_FUNCTION_NONE));
                    usbMgr.setScreenUnlockedFunctions(UsbManager.usbFunctionsFromString(
                            args.length >= 3 ? args[2] : ""));
                } catch (RemoteException e) {
                    System.err.println("Error communicating with UsbManager: " + e);
                }
+11 −7
Original line number Diff line number Diff line
@@ -88,18 +88,22 @@ interface IUsbManager
    /* Returns true if the specified USB function is enabled. */
    boolean isFunctionEnabled(String function);

    /* Sets the current USB function as well as whether USB data
     * (for example, MTP exposed pictures) should be made available
     * on the USB connection. Unlocking data should only be done with
     * user involvement, since exposing pictures or other data could
     * leak sensitive user information.
     */
    /* Sets the current USB function. */
    void setCurrentFunctions(long functions);

    /* Compatibility version of setCurrentFunctions(long). */
    void setCurrentFunction(String function, boolean usbDataUnlocked);

    /* Gets the current USB functions. */
    long getCurrentFunctions();

    /* Sets the screen unlocked USB function(s), which will be set automatically
     * when the screen is unlocked.
     */
    void setScreenUnlockedFunctions(String function);
    void setScreenUnlockedFunctions(long functions);

    /* Gets the current screen unlocked functions. */
    long getScreenUnlockedFunctions();

    /* Allow USB debugging from the attached host. If alwaysAllow is true, add the
     * the public key to list of host keys that the user has approved.
+195 −65
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.hardware.usb.gadget.V1_0.GadgetFunction;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.Process;
@@ -37,6 +38,8 @@ import android.util.Log;
import com.android.internal.util.Preconditions;

import java.util.HashMap;
import java.util.Map;
import java.util.StringJoiner;

/**
 * This class allows you to access the state of USB and communicate with USB devices.
@@ -70,7 +73,7 @@ public class UsbManager {
     * MTP function is enabled
     * <li> {@link #USB_FUNCTION_PTP} boolean extra indicating whether the
     * PTP function is enabled
     * <li> {@link #USB_FUNCTION_PTP} boolean extra indicating whether the
     * <li> {@link #USB_FUNCTION_ACCESSORY} boolean extra indicating whether the
     * accessory function is enabled
     * <li> {@link #USB_FUNCTION_AUDIO_SOURCE} boolean extra indicating whether the
     * audio source function is enabled
@@ -196,8 +199,7 @@ public class UsbManager {

    /**
     * A placeholder indicating that no USB function is being specified.
     * Used to distinguish between selecting no function vs. the default function in
     * {@link #setCurrentFunction(String)}.
     * Used for compatibility with old init scripts to indicate no functions vs. charging function.
     *
     * {@hide}
     */
@@ -298,6 +300,69 @@ public class UsbManager {
     */
    public static final String EXTRA_PERMISSION_GRANTED = "permission";

    /**
     * Code for the charging usb function. Passed into {@link #setCurrentFunctions(long)}
     * {@hide}
     */
    public static final long FUNCTION_NONE = 0;

    /**
     * Code for the mtp usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
     * {@hide}
     */
    public static final long FUNCTION_MTP = GadgetFunction.MTP;

    /**
     * Code for the ptp usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
     * {@hide}
     */
    public static final long FUNCTION_PTP = GadgetFunction.PTP;

    /**
     * Code for the rndis usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
     * {@hide}
     */
    public static final long FUNCTION_RNDIS = GadgetFunction.RNDIS;

    /**
     * Code for the midi usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
     * {@hide}
     */
    public static final long FUNCTION_MIDI = GadgetFunction.MIDI;

    /**
     * Code for the accessory usb function.
     * {@hide}
     */
    public static final long FUNCTION_ACCESSORY = GadgetFunction.ACCESSORY;

    /**
     * Code for the audio source usb function.
     * {@hide}
     */
    public static final long FUNCTION_AUDIO_SOURCE = GadgetFunction.AUDIO_SOURCE;

    /**
     * Code for the adb usb function.
     * {@hide}
     */
    public static final long FUNCTION_ADB = GadgetFunction.ADB;

    private static final long SETTABLE_FUNCTIONS = FUNCTION_MTP | FUNCTION_PTP | FUNCTION_RNDIS
            | FUNCTION_MIDI;

    private static final Map<String, Long> FUNCTION_NAME_TO_CODE = new HashMap<>();

    static {
        FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_MTP, FUNCTION_MTP);
        FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_PTP, FUNCTION_PTP);
        FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_RNDIS, FUNCTION_RNDIS);
        FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_MIDI, FUNCTION_MIDI);
        FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_ACCESSORY, FUNCTION_ACCESSORY);
        FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_AUDIO_SOURCE, FUNCTION_AUDIO_SOURCE);
        FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_ADB, FUNCTION_ADB);
    }

    private final Context mContext;
    private final IUsbManager mService;

@@ -548,15 +613,14 @@ public class UsbManager {
     * services offered by the device.
     * </p>
     *
     * @deprecated use getCurrentFunctions() instead.
     * @param function name of the USB function
     * @return true if the USB function is enabled
     *
     * {@hide}
     */
    @Deprecated
    public boolean isFunctionEnabled(String function) {
        if (mService == null) {
            return false;
        }
        try {
            return mService.isFunctionEnabled(function);
        } catch (RemoteException e) {
@@ -565,7 +629,7 @@ public class UsbManager {
    }

    /**
     * Sets the current USB function when in device mode.
     * Sets the current USB functions when in device mode.
     * <p>
     * USB functions represent interfaces which are published to the host to access
     * services offered by the device.
@@ -574,27 +638,59 @@ public class UsbManager {
     * automatically activate additional functions such as {@link #USB_FUNCTION_ADB}
     * or {@link #USB_FUNCTION_ACCESSORY} based on other settings and states.
     * </p><p>
     * The allowed values are: {@link #USB_FUNCTION_NONE}, {@link #USB_FUNCTION_AUDIO_SOURCE},
     * {@link #USB_FUNCTION_MIDI}, {@link #USB_FUNCTION_MTP}, {@link #USB_FUNCTION_PTP},
     * or {@link #USB_FUNCTION_RNDIS}.
     * </p><p>
     * Also sets whether USB data (for example, MTP exposed pictures) should be made available
     * on the USB connection when in device mode. Unlocking usb data should only be done with
     * user involvement, since exposing pictures or other data could leak sensitive
     * user information.
     * An argument of 0 indicates that the device is charging, and can pick any
     * appropriate function for that purpose.
     * </p><p>
     * Note: This function is asynchronous and may fail silently without applying
     * the requested changes.
     * </p>
     *
     * @param function name of the USB function, or null to restore the default function
     * @param usbDataUnlocked whether user data is accessible
     * @param functions the USB function(s) to set, as a bitwise mask.
     *                  Must satisfy {@link UsbManager#areSettableFunctions}
     *
     * {@hide}
     */
    public void setCurrentFunctions(long functions) {
        try {
            mService.setCurrentFunctions(functions);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Sets the current USB functions when in device mode.
     *
     * @deprecated use setCurrentFunctions(long) instead.
     * @param functions the USB function(s) to set.
     * @param usbDataUnlocked unused

     * {@hide}
     */
    @Deprecated
    public void setCurrentFunction(String functions, boolean usbDataUnlocked) {
        try {
            mService.setCurrentFunction(functions, usbDataUnlocked);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Returns the current USB functions in device mode.
     * <p>
     * This function returns the state of primary USB functions and can return a
     * mask containing any usb function(s) except for ADB.
     * </p>
     *
     * @return The currently enabled functions, in a bitwise mask.
     * A zero mask indicates that the current function is the charging function.
     *
     * {@hide}
     */
    public void setCurrentFunction(String function, boolean usbDataUnlocked) {
    public long getCurrentFunctions() {
        try {
            mService.setCurrentFunction(function, usbDataUnlocked);
            return mService.getCurrentFunctions();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
@@ -604,23 +700,37 @@ public class UsbManager {
     * Sets the screen unlocked functions, which are persisted and set as the current functions
     * whenever the screen is unlocked.
     * <p>
     * The allowed values are: {@link #USB_FUNCTION_NONE},
     * {@link #USB_FUNCTION_MIDI}, {@link #USB_FUNCTION_MTP}, {@link #USB_FUNCTION_PTP},
     * or {@link #USB_FUNCTION_RNDIS}.
     * {@link #USB_FUNCTION_NONE} has the effect of switching off this feature, so functions
     * A zero mask has the effect of switching off this feature, so functions
     * no longer change on screen unlock.
     * </p><p>
     * Note: When the screen is on, this method will apply given functions as current functions,
     * which is asynchronous and may fail silently without applying the requested changes.
     * </p>
     *
     * @param function function to set as default
     * @param functions functions to set, in a bitwise mask.
     *                  Must satisfy {@link UsbManager#areSettableFunctions}
     *
     * {@hide}
     */
    public void setScreenUnlockedFunctions(long functions) {
        try {
            mService.setScreenUnlockedFunctions(functions);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Gets the current screen unlocked functions.
     *
     * @return The currently set screen enabled functions.
     * A zero mask indicates that the screen unlocked functions feature is not enabled.
     *
     * {@hide}
     */
    public void setScreenUnlockedFunctions(String function) {
    public long getScreenUnlockedFunctions() {
        try {
            mService.setScreenUnlockedFunctions(function);
            return mService.getScreenUnlockedFunctions();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
@@ -719,51 +829,71 @@ public class UsbManager {
        }
    }

    /** @hide */
    public static String addFunction(String functions, String function) {
        if (USB_FUNCTION_NONE.equals(functions)) {
            return function;
    /**
     * Returns whether the given functions are valid inputs to UsbManager.
     * Currently the empty functions or any of MTP, PTP, RNDIS, MIDI are accepted.
     *
     * @return Whether the mask is settable.
     *
     * {@hide}
     */
    public static boolean areSettableFunctions(long functions) {
        return functions == FUNCTION_NONE
                || ((~SETTABLE_FUNCTIONS & functions) == 0 && Long.bitCount(functions) == 1);
    }

    /**
     * Converts the given function mask to string. Maintains ordering with respect to init scripts.
     *
     * @return String representation of given mask
     *
     * {@hide}
     */
    public static String usbFunctionsToString(long functions) {
        StringJoiner joiner = new StringJoiner(",");
        if ((functions & FUNCTION_MTP) != 0) {
            joiner.add(UsbManager.USB_FUNCTION_MTP);
        }
        if (!containsFunction(functions, function)) {
            if (functions.length() > 0) {
                functions += ",";
        if ((functions & FUNCTION_PTP) != 0) {
            joiner.add(UsbManager.USB_FUNCTION_PTP);
        }
            functions += function;
        if ((functions & FUNCTION_RNDIS) != 0) {
            joiner.add(UsbManager.USB_FUNCTION_RNDIS);
        }
        return functions;
        if ((functions & FUNCTION_MIDI) != 0) {
            joiner.add(UsbManager.USB_FUNCTION_MIDI);
        }

    /** @hide */
    public static String removeFunction(String functions, String function) {
        String[] split = functions.split(",");
        for (int i = 0; i < split.length; i++) {
            if (function.equals(split[i])) {
                split[i] = null;
        if ((functions & FUNCTION_ACCESSORY) != 0) {
            joiner.add(UsbManager.USB_FUNCTION_ACCESSORY);
        }
        if ((functions & FUNCTION_AUDIO_SOURCE) != 0) {
            joiner.add(UsbManager.USB_FUNCTION_AUDIO_SOURCE);
        }
        if (split.length == 1 && split[0] == null) {
            return USB_FUNCTION_NONE;
        if ((functions & FUNCTION_ADB) != 0) {
            joiner.add(UsbManager.USB_FUNCTION_ADB);
        }
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < split.length; i++) {
            String s = split[i];
            if (s != null) {
                if (builder.length() > 0) {
                    builder.append(",");
        return joiner.toString();
    }
                builder.append(s);

    /**
     * Parses a string of usb functions that are comma separated.
     *
     * @return A mask of all valid functions in the string
     *
     * {@hide}
     */
    public static long usbFunctionsFromString(String functions) {
        if (functions == null || functions.equals(USB_FUNCTION_NONE)) {
            return FUNCTION_NONE;
        }
        long ret = 0;
        for (String function : functions.split(",")) {
            if (FUNCTION_NAME_TO_CODE.containsKey(function)) {
                ret |= FUNCTION_NAME_TO_CODE.get(function);
            } else if (function.length() > 0) {
                throw new IllegalArgumentException("Invalid usb function " + functions);
            }
        return builder.toString();
        }

    /** @hide */
    public static boolean containsFunction(String functions, String function) {
        int index = functions.indexOf(function);
        if (index < 0) return false;
        if (index > 0 && functions.charAt(index - 1) != ',') return false;
        int charAfter = index + function.length();
        if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false;
        return true;
        return ret;
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -1095,7 +1095,8 @@ public class Tethering extends BaseNetworkObserver {
        if (VDBG) Log.d(TAG, "setUsbTethering(" + enable + ")");
        UsbManager usbManager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
        synchronized (mPublicSync) {
            usbManager.setCurrentFunction(enable ? UsbManager.USB_FUNCTION_RNDIS : null, false);
            usbManager.setCurrentFunctions(enable ? UsbManager.FUNCTION_RNDIS
                    : UsbManager.FUNCTION_NONE);
        }
        return ConnectivityManager.TETHER_ERROR_NO_ERROR;
    }
Loading