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