Home | History | Annotate | Download | only in usb
      1 /*
      2  * Copyright (C) 2010 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 
     18 package android.hardware.usb;
     19 
     20 import com.android.internal.util.Preconditions;
     21 
     22 import android.app.PendingIntent;
     23 import android.content.Context;
     24 import android.content.pm.PackageManager.NameNotFoundException;
     25 import android.os.Bundle;
     26 import android.os.ParcelFileDescriptor;
     27 import android.os.Process;
     28 import android.os.RemoteException;
     29 import android.util.Log;
     30 
     31 import java.util.HashMap;
     32 
     33 /**
     34  * This class allows you to access the state of USB and communicate with USB devices.
     35  * Currently only host mode is supported in the public API.
     36  *
     37  * <p>You can obtain an instance of this class by calling
     38  * {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
     39  *
     40  * {@samplecode
     41  * UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);}
     42  *
     43  * <div class="special reference">
     44  * <h3>Developer Guides</h3>
     45  * <p>For more information about communicating with USB hardware, read the
     46  * <a href="{@docRoot}guide/topics/connectivity/usb/index.html">USB developer guide</a>.</p>
     47  * </div>
     48  */
     49 public class UsbManager {
     50     private static final String TAG = "UsbManager";
     51 
     52    /**
     53      * Broadcast Action:  A sticky broadcast for USB state change events when in device mode.
     54      *
     55      * This is a sticky broadcast for clients that includes USB connected/disconnected state,
     56      * <ul>
     57      * <li> {@link #USB_CONNECTED} boolean indicating whether USB is connected or disconnected.
     58      * <li> {@link #USB_HOST_CONNECTED} boolean indicating whether USB is connected or
     59      *     disconnected as host.
     60      * <li> {@link #USB_CONFIGURED} boolean indicating whether USB is configured.
     61      * currently zero if not configured, one for configured.
     62      * <li> {@link #USB_FUNCTION_ADB} boolean extra indicating whether the
     63      * adb function is enabled
     64      * <li> {@link #USB_FUNCTION_RNDIS} boolean extra indicating whether the
     65      * RNDIS ethernet function is enabled
     66      * <li> {@link #USB_FUNCTION_MTP} boolean extra indicating whether the
     67      * MTP function is enabled
     68      * <li> {@link #USB_FUNCTION_PTP} boolean extra indicating whether the
     69      * PTP function is enabled
     70      * <li> {@link #USB_FUNCTION_PTP} boolean extra indicating whether the
     71      * accessory function is enabled
     72      * <li> {@link #USB_FUNCTION_AUDIO_SOURCE} boolean extra indicating whether the
     73      * audio source function is enabled
     74      * <li> {@link #USB_FUNCTION_MIDI} boolean extra indicating whether the
     75      * MIDI function is enabled
     76      * </ul>
     77      * If the sticky intent has not been found, that indicates USB is disconnected,
     78      * USB is not configued, MTP function is enabled, and all the other functions are disabled.
     79      *
     80      * {@hide}
     81      */
     82     public static final String ACTION_USB_STATE =
     83             "android.hardware.usb.action.USB_STATE";
     84 
     85     /**
     86      * Broadcast Action: A broadcast for USB port changes.
     87      *
     88      * This intent is sent when a USB port is added, removed, or changes state.
     89      * <ul>
     90      * <li> {@link #EXTRA_PORT} containing the {@link android.hardware.usb.UsbPort}
     91      * for the port.
     92      * <li> {@link #EXTRA_PORT_STATUS} containing the {@link android.hardware.usb.UsbPortStatus}
     93      * for the port, or null if the port has been removed
     94      * </ul>
     95      *
     96      * @hide
     97      */
     98     public static final String ACTION_USB_PORT_CHANGED =
     99             "android.hardware.usb.action.USB_PORT_CHANGED";
    100 
    101    /**
    102      * Broadcast Action:  A broadcast for USB device attached event.
    103      *
    104      * This intent is sent when a USB device is attached to the USB bus when in host mode.
    105      * <ul>
    106      * <li> {@link #EXTRA_DEVICE} containing the {@link android.hardware.usb.UsbDevice}
    107      * for the attached device
    108      * </ul>
    109      */
    110     public static final String ACTION_USB_DEVICE_ATTACHED =
    111             "android.hardware.usb.action.USB_DEVICE_ATTACHED";
    112 
    113    /**
    114      * Broadcast Action:  A broadcast for USB device detached event.
    115      *
    116      * This intent is sent when a USB device is detached from the USB bus when in host mode.
    117      * <ul>
    118      * <li> {@link #EXTRA_DEVICE} containing the {@link android.hardware.usb.UsbDevice}
    119      * for the detached device
    120      * </ul>
    121      */
    122     public static final String ACTION_USB_DEVICE_DETACHED =
    123             "android.hardware.usb.action.USB_DEVICE_DETACHED";
    124 
    125    /**
    126      * Broadcast Action:  A broadcast for USB accessory attached event.
    127      *
    128      * This intent is sent when a USB accessory is attached.
    129      * <ul>
    130      * <li> {@link #EXTRA_ACCESSORY} containing the {@link android.hardware.usb.UsbAccessory}
    131      * for the attached accessory
    132      * </ul>
    133      */
    134     public static final String ACTION_USB_ACCESSORY_ATTACHED =
    135             "android.hardware.usb.action.USB_ACCESSORY_ATTACHED";
    136 
    137    /**
    138      * Broadcast Action:  A broadcast for USB accessory detached event.
    139      *
    140      * This intent is sent when a USB accessory is detached.
    141      * <ul>
    142      * <li> {@link #EXTRA_ACCESSORY} containing the {@link UsbAccessory}
    143      * for the attached accessory that was detached
    144      * </ul>
    145      */
    146     public static final String ACTION_USB_ACCESSORY_DETACHED =
    147             "android.hardware.usb.action.USB_ACCESSORY_DETACHED";
    148 
    149     /**
    150      * Boolean extra indicating whether USB is connected or disconnected.
    151      * Used in extras for the {@link #ACTION_USB_STATE} broadcast.
    152      *
    153      * {@hide}
    154      */
    155     public static final String USB_CONNECTED = "connected";
    156 
    157     /**
    158      * Boolean extra indicating whether USB is connected or disconnected as host.
    159      * Used in extras for the {@link #ACTION_USB_STATE} broadcast.
    160      *
    161      * {@hide}
    162      */
    163     public static final String USB_HOST_CONNECTED = "host_connected";
    164 
    165     /**
    166      * Boolean extra indicating whether USB is configured.
    167      * Used in extras for the {@link #ACTION_USB_STATE} broadcast.
    168      *
    169      * {@hide}
    170      */
    171     public static final String USB_CONFIGURED = "configured";
    172 
    173     /**
    174      * Boolean extra indicating whether confidential user data, such as photos, should be
    175      * made available on the USB connection. This variable will only be set when the user
    176      * has explicitly asked for this data to be unlocked.
    177      * Used in extras for the {@link #ACTION_USB_STATE} broadcast.
    178      *
    179      * {@hide}
    180      */
    181     public static final String USB_DATA_UNLOCKED = "unlocked";
    182 
    183     /**
    184      * A placeholder indicating that no USB function is being specified.
    185      * Used to distinguish between selecting no function vs. the default function in
    186      * {@link #setCurrentFunction(String)}.
    187      *
    188      * {@hide}
    189      */
    190     public static final String USB_FUNCTION_NONE = "none";
    191 
    192     /**
    193      * Name of the adb USB function.
    194      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
    195      *
    196      * {@hide}
    197      */
    198     public static final String USB_FUNCTION_ADB = "adb";
    199 
    200     /**
    201      * Name of the RNDIS ethernet USB function.
    202      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
    203      *
    204      * {@hide}
    205      */
    206     public static final String USB_FUNCTION_RNDIS = "rndis";
    207 
    208     /**
    209      * Name of the MTP USB function.
    210      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
    211      *
    212      * {@hide}
    213      */
    214     public static final String USB_FUNCTION_MTP = "mtp";
    215 
    216     /**
    217      * Name of the PTP USB function.
    218      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
    219      *
    220      * {@hide}
    221      */
    222     public static final String USB_FUNCTION_PTP = "ptp";
    223 
    224     /**
    225      * Name of the audio source USB function.
    226      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
    227      *
    228      * {@hide}
    229      */
    230     public static final String USB_FUNCTION_AUDIO_SOURCE = "audio_source";
    231 
    232     /**
    233      * Name of the MIDI USB function.
    234      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
    235      *
    236      * {@hide}
    237      */
    238     public static final String USB_FUNCTION_MIDI = "midi";
    239 
    240     /**
    241      * Name of the Accessory USB function.
    242      * Used in extras for the {@link #ACTION_USB_STATE} broadcast
    243      *
    244      * {@hide}
    245      */
    246     public static final String USB_FUNCTION_ACCESSORY = "accessory";
    247 
    248     /**
    249      * Name of extra for {@link #ACTION_USB_PORT_CHANGED}
    250      * containing the {@link UsbPort} object for the port.
    251      *
    252      * @hide
    253      */
    254     public static final String EXTRA_PORT = "port";
    255 
    256     /**
    257      * Name of extra for {@link #ACTION_USB_PORT_CHANGED}
    258      * containing the {@link UsbPortStatus} object for the port, or null if the port
    259      * was removed.
    260      *
    261      * @hide
    262      */
    263     public static final String EXTRA_PORT_STATUS = "portStatus";
    264 
    265     /**
    266      * Name of extra for {@link #ACTION_USB_DEVICE_ATTACHED} and
    267      * {@link #ACTION_USB_DEVICE_DETACHED} broadcasts
    268      * containing the {@link UsbDevice} object for the device.
    269      */
    270     public static final String EXTRA_DEVICE = "device";
    271 
    272     /**
    273      * Name of extra for {@link #ACTION_USB_ACCESSORY_ATTACHED} and
    274      * {@link #ACTION_USB_ACCESSORY_DETACHED} broadcasts
    275      * containing the {@link UsbAccessory} object for the accessory.
    276      */
    277     public static final String EXTRA_ACCESSORY = "accessory";
    278 
    279     /**
    280      * Name of extra added to the {@link android.app.PendingIntent}
    281      * passed into {@link #requestPermission(UsbDevice, PendingIntent)}
    282      * or {@link #requestPermission(UsbAccessory, PendingIntent)}
    283      * containing a boolean value indicating whether the user granted permission or not.
    284      */
    285     public static final String EXTRA_PERMISSION_GRANTED = "permission";
    286 
    287     private final Context mContext;
    288     private final IUsbManager mService;
    289 
    290     /**
    291      * {@hide}
    292      */
    293     public UsbManager(Context context, IUsbManager service) {
    294         mContext = context;
    295         mService = service;
    296     }
    297 
    298     /**
    299      * Returns a HashMap containing all USB devices currently attached.
    300      * USB device name is the key for the returned HashMap.
    301      * The result will be empty if no devices are attached, or if
    302      * USB host mode is inactive or unsupported.
    303      *
    304      * @return HashMap containing all connected USB devices.
    305      */
    306     public HashMap<String,UsbDevice> getDeviceList() {
    307         Bundle bundle = new Bundle();
    308         try {
    309             mService.getDeviceList(bundle);
    310             HashMap<String,UsbDevice> result = new HashMap<String,UsbDevice>();
    311             for (String name : bundle.keySet()) {
    312                 result.put(name, (UsbDevice)bundle.get(name));
    313             }
    314             return result;
    315         } catch (RemoteException e) {
    316             throw e.rethrowFromSystemServer();
    317         }
    318     }
    319 
    320     /**
    321      * Opens the device so it can be used to send and receive
    322      * data using {@link android.hardware.usb.UsbRequest}.
    323      *
    324      * @param device the device to open
    325      * @return a {@link UsbDeviceConnection}, or {@code null} if open failed
    326      */
    327     public UsbDeviceConnection openDevice(UsbDevice device) {
    328         try {
    329             String deviceName = device.getDeviceName();
    330             ParcelFileDescriptor pfd = mService.openDevice(deviceName);
    331             if (pfd != null) {
    332                 UsbDeviceConnection connection = new UsbDeviceConnection(device);
    333                 boolean result = connection.open(deviceName, pfd, mContext);
    334                 pfd.close();
    335                 if (result) {
    336                     return connection;
    337                 }
    338             }
    339         } catch (Exception e) {
    340             Log.e(TAG, "exception in UsbManager.openDevice", e);
    341         }
    342         return null;
    343     }
    344 
    345     /**
    346      * Returns a list of currently attached USB accessories.
    347      * (in the current implementation there can be at most one)
    348      *
    349      * @return list of USB accessories, or null if none are attached.
    350      */
    351     public UsbAccessory[] getAccessoryList() {
    352         try {
    353             UsbAccessory accessory = mService.getCurrentAccessory();
    354             if (accessory == null) {
    355                 return null;
    356             } else {
    357                 return new UsbAccessory[] { accessory };
    358             }
    359         } catch (RemoteException e) {
    360             throw e.rethrowFromSystemServer();
    361         }
    362     }
    363 
    364     /**
    365      * Opens a file descriptor for reading and writing data to the USB accessory.
    366      *
    367      * @param accessory the USB accessory to open
    368      * @return file descriptor, or null if the accessor could not be opened.
    369      */
    370     public ParcelFileDescriptor openAccessory(UsbAccessory accessory) {
    371         try {
    372             return mService.openAccessory(accessory);
    373         } catch (RemoteException e) {
    374             throw e.rethrowFromSystemServer();
    375         }
    376     }
    377 
    378     /**
    379      * Returns true if the caller has permission to access the device.
    380      * Permission might have been granted temporarily via
    381      * {@link #requestPermission(UsbDevice, PendingIntent)} or
    382      * by the user choosing the caller as the default application for the device.
    383      *
    384      * @param device to check permissions for
    385      * @return true if caller has permission
    386      */
    387     public boolean hasPermission(UsbDevice device) {
    388         try {
    389             return mService.hasDevicePermission(device);
    390         } catch (RemoteException e) {
    391             throw e.rethrowFromSystemServer();
    392         }
    393     }
    394 
    395     /**
    396      * Returns true if the caller has permission to access the accessory.
    397      * Permission might have been granted temporarily via
    398      * {@link #requestPermission(UsbAccessory, PendingIntent)} or
    399      * by the user choosing the caller as the default application for the accessory.
    400      *
    401      * @param accessory to check permissions for
    402      * @return true if caller has permission
    403      */
    404     public boolean hasPermission(UsbAccessory accessory) {
    405         try {
    406             return mService.hasAccessoryPermission(accessory);
    407         } catch (RemoteException e) {
    408             throw e.rethrowFromSystemServer();
    409         }
    410     }
    411 
    412     /**
    413      * Requests temporary permission for the given package to access the device.
    414      * This may result in a system dialog being displayed to the user
    415      * if permission had not already been granted.
    416      * Success or failure is returned via the {@link android.app.PendingIntent} pi.
    417      * If successful, this grants the caller permission to access the device only
    418      * until the device is disconnected.
    419      *
    420      * The following extras will be added to pi:
    421      * <ul>
    422      * <li> {@link #EXTRA_DEVICE} containing the device passed into this call
    423      * <li> {@link #EXTRA_PERMISSION_GRANTED} containing boolean indicating whether
    424      * permission was granted by the user
    425      * </ul>
    426      *
    427      * @param device to request permissions for
    428      * @param pi PendingIntent for returning result
    429      */
    430     public void requestPermission(UsbDevice device, PendingIntent pi) {
    431         try {
    432             mService.requestDevicePermission(device, mContext.getPackageName(), pi);
    433         } catch (RemoteException e) {
    434             throw e.rethrowFromSystemServer();
    435         }
    436     }
    437 
    438     /**
    439      * Requests temporary permission for the given package to access the accessory.
    440      * This may result in a system dialog being displayed to the user
    441      * if permission had not already been granted.
    442      * Success or failure is returned via the {@link android.app.PendingIntent} pi.
    443      * If successful, this grants the caller permission to access the accessory only
    444      * until the device is disconnected.
    445      *
    446      * The following extras will be added to pi:
    447      * <ul>
    448      * <li> {@link #EXTRA_ACCESSORY} containing the accessory passed into this call
    449      * <li> {@link #EXTRA_PERMISSION_GRANTED} containing boolean indicating whether
    450      * permission was granted by the user
    451      * </ul>
    452      *
    453      * @param accessory to request permissions for
    454      * @param pi PendingIntent for returning result
    455      */
    456     public void requestPermission(UsbAccessory accessory, PendingIntent pi) {
    457         try {
    458             mService.requestAccessoryPermission(accessory, mContext.getPackageName(), pi);
    459         } catch (RemoteException e) {
    460             throw e.rethrowFromSystemServer();
    461         }
    462     }
    463 
    464     /**
    465      * Grants permission for USB device without showing system dialog.
    466      * Only system components can call this function.
    467      * @param device to request permissions for
    468      *
    469      * {@hide}
    470      */
    471     public void grantPermission(UsbDevice device) {
    472         try {
    473             mService.grantDevicePermission(device, Process.myUid());
    474         } catch (RemoteException e) {
    475             throw e.rethrowFromSystemServer();
    476         }
    477     }
    478 
    479     /**
    480      * Grants permission to specified package for USB device without showing system dialog.
    481      * Only system components can call this function, as it requires the MANAGE_USB permission.
    482      * @param device to request permissions for
    483      * @param packageName of package to grant permissions
    484      *
    485      * {@hide}
    486      */
    487     public void grantPermission(UsbDevice device, String packageName) {
    488         try {
    489             int uid = mContext.getPackageManager()
    490                 .getPackageUidAsUser(packageName, mContext.getUserId());
    491             mService.grantDevicePermission(device, uid);
    492         } catch (NameNotFoundException e) {
    493             Log.e(TAG, "Package " + packageName + " not found.", e);
    494         } catch (RemoteException e) {
    495             throw e.rethrowFromSystemServer();
    496         }
    497     }
    498 
    499     /**
    500      * Returns true if the specified USB function is currently enabled when in device mode.
    501      * <p>
    502      * USB functions represent interfaces which are published to the host to access
    503      * services offered by the device.
    504      * </p>
    505      *
    506      * @param function name of the USB function
    507      * @return true if the USB function is enabled
    508      *
    509      * {@hide}
    510      */
    511     public boolean isFunctionEnabled(String function) {
    512         try {
    513             return mService.isFunctionEnabled(function);
    514         } catch (RemoteException e) {
    515             throw e.rethrowFromSystemServer();
    516         }
    517     }
    518 
    519     /**
    520      * Sets the current USB function when in device mode.
    521      * <p>
    522      * USB functions represent interfaces which are published to the host to access
    523      * services offered by the device.
    524      * </p><p>
    525      * This method is intended to select among primary USB functions.  The system may
    526      * automatically activate additional functions such as {@link #USB_FUNCTION_ADB}
    527      * or {@link #USB_FUNCTION_ACCESSORY} based on other settings and states.
    528      * </p><p>
    529      * The allowed values are: {@link #USB_FUNCTION_NONE}, {@link #USB_FUNCTION_AUDIO_SOURCE},
    530      * {@link #USB_FUNCTION_MIDI}, {@link #USB_FUNCTION_MTP}, {@link #USB_FUNCTION_PTP},
    531      * or {@link #USB_FUNCTION_RNDIS}.
    532      * </p><p>
    533      * Note: This function is asynchronous and may fail silently without applying
    534      * the requested changes.
    535      * </p>
    536      *
    537      * @param function name of the USB function, or null to restore the default function
    538      *
    539      * {@hide}
    540      */
    541     public void setCurrentFunction(String function) {
    542         try {
    543             mService.setCurrentFunction(function);
    544         } catch (RemoteException e) {
    545             throw e.rethrowFromSystemServer();
    546         }
    547     }
    548 
    549     /**
    550      * Sets whether USB data (for example, MTP exposed pictures) should be made available
    551      * on the USB connection when in device mode. Unlocking usb data should only be done with
    552      * user involvement, since exposing pictures or other data could leak sensitive
    553      * user information.
    554      *
    555      * {@hide}
    556      */
    557     public void setUsbDataUnlocked(boolean unlocked) {
    558         try {
    559             mService.setUsbDataUnlocked(unlocked);
    560         } catch (RemoteException e) {
    561             throw e.rethrowFromSystemServer();
    562         }
    563     }
    564 
    565     /**
    566      * Returns a list of physical USB ports on the device.
    567      * <p>
    568      * This list is guaranteed to contain all dual-role USB Type C ports but it might
    569      * be missing other ports depending on whether the kernel USB drivers have been
    570      * updated to publish all of the device's ports through the new "dual_role_usb"
    571      * device class (which supports all types of ports despite its name).
    572      * </p>
    573      *
    574      * @return The list of USB ports, or null if none.
    575      *
    576      * @hide
    577      */
    578     public UsbPort[] getPorts() {
    579         try {
    580             return mService.getPorts();
    581         } catch (RemoteException e) {
    582             throw e.rethrowFromSystemServer();
    583         }
    584     }
    585 
    586     /**
    587      * Gets the status of the specified USB port.
    588      *
    589      * @param port The port to query.
    590      * @return The status of the specified USB port, or null if unknown.
    591      *
    592      * @hide
    593      */
    594     public UsbPortStatus getPortStatus(UsbPort port) {
    595         Preconditions.checkNotNull(port, "port must not be null");
    596 
    597         try {
    598             return mService.getPortStatus(port.getId());
    599         } catch (RemoteException e) {
    600             throw e.rethrowFromSystemServer();
    601         }
    602     }
    603 
    604     /**
    605      * Sets the desired role combination of the port.
    606      * <p>
    607      * The supported role combinations depend on what is connected to the port and may be
    608      * determined by consulting
    609      * {@link UsbPortStatus#isRoleCombinationSupported UsbPortStatus.isRoleCombinationSupported}.
    610      * </p><p>
    611      * Note: This function is asynchronous and may fail silently without applying
    612      * the requested changes.  If this function does cause a status change to occur then
    613      * a {@link #ACTION_USB_PORT_CHANGED} broadcast will be sent.
    614      * </p>
    615      *
    616      * @param powerRole The desired power role: {@link UsbPort#POWER_ROLE_SOURCE}
    617      * or {@link UsbPort#POWER_ROLE_SINK}, or 0 if no power role.
    618      * @param dataRole The desired data role: {@link UsbPort#DATA_ROLE_HOST}
    619      * or {@link UsbPort#DATA_ROLE_DEVICE}, or 0 if no data role.
    620      *
    621      * @hide
    622      */
    623     public void setPortRoles(UsbPort port, int powerRole, int dataRole) {
    624         Preconditions.checkNotNull(port, "port must not be null");
    625         UsbPort.checkRoles(powerRole, dataRole);
    626 
    627         try {
    628             mService.setPortRoles(port.getId(), powerRole, dataRole);
    629         } catch (RemoteException e) {
    630             throw e.rethrowFromSystemServer();
    631         }
    632     }
    633 
    634     /** @hide */
    635     public static String addFunction(String functions, String function) {
    636         if (USB_FUNCTION_NONE.equals(functions)) {
    637             return function;
    638         }
    639         if (!containsFunction(functions, function)) {
    640             if (functions.length() > 0) {
    641                 functions += ",";
    642             }
    643             functions += function;
    644         }
    645         return functions;
    646     }
    647 
    648     /** @hide */
    649     public static String removeFunction(String functions, String function) {
    650         String[] split = functions.split(",");
    651         for (int i = 0; i < split.length; i++) {
    652             if (function.equals(split[i])) {
    653                 split[i] = null;
    654             }
    655         }
    656         if (split.length == 1 && split[0] == null) {
    657             return USB_FUNCTION_NONE;
    658         }
    659         StringBuilder builder = new StringBuilder();
    660         for (int i = 0; i < split.length; i++) {
    661             String s = split[i];
    662             if (s != null) {
    663                 if (builder.length() > 0) {
    664                     builder.append(",");
    665                 }
    666                 builder.append(s);
    667             }
    668         }
    669         return builder.toString();
    670     }
    671 
    672     /** @hide */
    673     public static boolean containsFunction(String functions, String function) {
    674         int index = functions.indexOf(function);
    675         if (index < 0) return false;
    676         if (index > 0 && functions.charAt(index - 1) != ',') return false;
    677         int charAfter = index + function.length();
    678         if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false;
    679         return true;
    680     }
    681 }
    682