1/*
2 * Copyright (C) 2011 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 an
14 * limitations under the License.
15 */
16
17package com.android.server.usb;
18
19import static com.android.internal.usb.DumpUtils.writeDevice;
20import static com.android.internal.util.dump.DumpUtils.writeComponentName;
21
22import android.annotation.NonNull;
23import android.annotation.Nullable;
24import android.content.ComponentName;
25import android.content.Context;
26import android.hardware.usb.UsbConstants;
27import android.hardware.usb.UsbDevice;
28import android.os.Bundle;
29import android.os.ParcelFileDescriptor;
30import android.service.ServiceProtoEnums;
31import android.service.usb.UsbConnectionRecordProto;
32import android.service.usb.UsbHostManagerProto;
33import android.service.usb.UsbIsHeadsetProto;
34import android.text.TextUtils;
35import android.util.Slog;
36
37import com.android.internal.annotations.GuardedBy;
38import com.android.internal.util.IndentingPrintWriter;
39import com.android.internal.util.dump.DualDumpOutputStream;
40import com.android.server.usb.descriptors.UsbDescriptor;
41import com.android.server.usb.descriptors.UsbDescriptorParser;
42import com.android.server.usb.descriptors.UsbDeviceDescriptor;
43import com.android.server.usb.descriptors.report.TextReportCanvas;
44import com.android.server.usb.descriptors.tree.UsbDescriptorsTree;
45
46import java.text.SimpleDateFormat;
47import java.util.Date;
48import java.util.HashMap;
49import java.util.LinkedList;
50
51/**
52 * UsbHostManager manages USB state in host mode.
53 */
54public class UsbHostManager {
55    private static final String TAG = UsbHostManager.class.getSimpleName();
56    private static final boolean DEBUG = false;
57    private static final int LINUX_FOUNDATION_VID = 0x1d6b;
58
59    private final Context mContext;
60
61    // USB busses to exclude from USB host support
62    private final String[] mHostBlacklist;
63
64    private final UsbAlsaManager mUsbAlsaManager;
65    private final UsbSettingsManager mSettingsManager;
66
67    private final Object mLock = new Object();
68    @GuardedBy("mLock")
69    // contains all connected USB devices
70    private final HashMap<String, UsbDevice> mDevices = new HashMap<>();
71
72    private Object mSettingsLock = new Object();
73    @GuardedBy("mSettingsLock")
74    private UsbProfileGroupSettingsManager mCurrentSettings;
75
76    private Object mHandlerLock = new Object();
77    @GuardedBy("mHandlerLock")
78    private ComponentName mUsbDeviceConnectionHandler;
79
80    /*
81     * Member used for tracking connections & disconnections
82     */
83    static final SimpleDateFormat sFormat = new SimpleDateFormat("MM-dd HH:mm:ss:SSS");
84    private static final int MAX_CONNECT_RECORDS = 32;
85    private int mNumConnects;    // TOTAL # of connect/disconnect
86    private final LinkedList<ConnectionRecord> mConnections = new LinkedList<ConnectionRecord>();
87    private ConnectionRecord mLastConnect;
88
89    /*
90     * ConnectionRecord
91     * Stores connection/disconnection data.
92     */
93    class ConnectionRecord {
94        long mTimestamp;        // Same time-base as system log.
95        String mDeviceAddress;
96
97        static final int CONNECT = ServiceProtoEnums.USB_CONNECTION_RECORD_MODE_CONNECT; // 0
98        static final int CONNECT_BADPARSE =
99                ServiceProtoEnums.USB_CONNECTION_RECORD_MODE_CONNECT_BADPARSE; // 1
100        static final int CONNECT_BADDEVICE =
101                ServiceProtoEnums.USB_CONNECTION_RECORD_MODE_CONNECT_BADDEVICE; // 2
102        static final int DISCONNECT =
103                ServiceProtoEnums.USB_CONNECTION_RECORD_MODE_DISCONNECT; // -1
104
105        final int mMode;
106        final byte[] mDescriptors;
107
108        ConnectionRecord(String deviceAddress, int mode, byte[] descriptors) {
109            mTimestamp = System.currentTimeMillis();
110            mDeviceAddress = deviceAddress;
111            mMode = mode;
112            mDescriptors = descriptors;
113        }
114
115        private String formatTime() {
116            return (new StringBuilder(sFormat.format(new Date(mTimestamp)))).toString();
117        }
118
119        void dump(@NonNull DualDumpOutputStream dump, String idName, long id) {
120            long token = dump.start(idName, id);
121
122            dump.write("device_address", UsbConnectionRecordProto.DEVICE_ADDRESS, mDeviceAddress);
123            dump.write("mode", UsbConnectionRecordProto.MODE, mMode);
124            dump.write("timestamp", UsbConnectionRecordProto.TIMESTAMP, mTimestamp);
125
126            if (mMode != DISCONNECT) {
127                UsbDescriptorParser parser = new UsbDescriptorParser(mDeviceAddress, mDescriptors);
128
129                UsbDeviceDescriptor deviceDescriptor = parser.getDeviceDescriptor();
130
131                dump.write("manufacturer", UsbConnectionRecordProto.MANUFACTURER,
132                        deviceDescriptor.getVendorID());
133                dump.write("product", UsbConnectionRecordProto.PRODUCT,
134                        deviceDescriptor.getProductID());
135                long isHeadSetToken = dump.start("is_headset", UsbConnectionRecordProto.IS_HEADSET);
136                dump.write("in", UsbIsHeadsetProto.IN, parser.isInputHeadset());
137                dump.write("out", UsbIsHeadsetProto.OUT, parser.isOutputHeadset());
138                dump.end(isHeadSetToken);
139            }
140
141            dump.end(token);
142        }
143
144        void dumpShort(IndentingPrintWriter pw) {
145            if (mMode != DISCONNECT) {
146                pw.println(formatTime() + " Connect " + mDeviceAddress + " mode:" + mMode);
147                UsbDescriptorParser parser = new UsbDescriptorParser(mDeviceAddress, mDescriptors);
148
149                UsbDeviceDescriptor deviceDescriptor = parser.getDeviceDescriptor();
150
151                pw.println("manfacturer:0x" + Integer.toHexString(deviceDescriptor.getVendorID())
152                        + " product:" + Integer.toHexString(deviceDescriptor.getProductID()));
153                pw.println("isHeadset[in: " + parser.isInputHeadset()
154                        + " , out: " + parser.isOutputHeadset() + "]");
155            } else {
156                pw.println(formatTime() + " Disconnect " + mDeviceAddress);
157            }
158        }
159
160        void dumpTree(IndentingPrintWriter pw) {
161            if (mMode != DISCONNECT) {
162                pw.println(formatTime() + " Connect " + mDeviceAddress + " mode:" + mMode);
163                UsbDescriptorParser parser = new UsbDescriptorParser(mDeviceAddress, mDescriptors);
164                StringBuilder stringBuilder = new StringBuilder();
165                UsbDescriptorsTree descriptorTree = new UsbDescriptorsTree();
166                descriptorTree.parse(parser);
167                descriptorTree.report(new TextReportCanvas(parser, stringBuilder));
168
169                stringBuilder.append("isHeadset[in: " + parser.isInputHeadset()
170                        + " , out: " + parser.isOutputHeadset() + "]");
171                pw.println(stringBuilder.toString());
172            } else {
173                pw.println(formatTime() + " Disconnect " + mDeviceAddress);
174            }
175        }
176
177        void dumpList(IndentingPrintWriter pw) {
178            if (mMode != DISCONNECT) {
179                pw.println(formatTime() + " Connect " + mDeviceAddress + " mode:" + mMode);
180                UsbDescriptorParser parser = new UsbDescriptorParser(mDeviceAddress, mDescriptors);
181                StringBuilder stringBuilder = new StringBuilder();
182                TextReportCanvas canvas = new TextReportCanvas(parser, stringBuilder);
183                for (UsbDescriptor descriptor : parser.getDescriptors()) {
184                    descriptor.report(canvas);
185                }
186                pw.println(stringBuilder.toString());
187
188                pw.println("isHeadset[in: " + parser.isInputHeadset()
189                        + " , out: " + parser.isOutputHeadset() + "]");
190            } else {
191                pw.println(formatTime() + " Disconnect " + mDeviceAddress);
192            }
193        }
194
195        private static final int kDumpBytesPerLine = 16;
196
197        void dumpRaw(IndentingPrintWriter pw) {
198            if (mMode != DISCONNECT) {
199                pw.println(formatTime() + " Connect " + mDeviceAddress + " mode:" + mMode);
200                int length = mDescriptors.length;
201                pw.println("Raw Descriptors " + length + " bytes");
202                int dataOffset = 0;
203                for (int line = 0; line < length / kDumpBytesPerLine; line++) {
204                    StringBuilder sb = new StringBuilder();
205                    for (int offset = 0; offset < kDumpBytesPerLine; offset++) {
206                        sb.append("0x")
207                            .append(String.format("0x%02X", mDescriptors[dataOffset++]))
208                            .append(" ");
209                    }
210                    pw.println(sb.toString());
211                }
212
213                // remainder
214                StringBuilder sb = new StringBuilder();
215                while (dataOffset < length) {
216                    sb.append("0x")
217                        .append(String.format("0x%02X", mDescriptors[dataOffset++]))
218                        .append(" ");
219                }
220                pw.println(sb.toString());
221            } else {
222                pw.println(formatTime() + " Disconnect " + mDeviceAddress);
223            }
224        }
225    }
226
227    /*
228     * UsbHostManager
229     */
230    public UsbHostManager(Context context, UsbAlsaManager alsaManager,
231            UsbSettingsManager settingsManager) {
232        mContext = context;
233
234        mHostBlacklist = context.getResources().getStringArray(
235                com.android.internal.R.array.config_usbHostBlacklist);
236        mUsbAlsaManager = alsaManager;
237        mSettingsManager = settingsManager;
238        String deviceConnectionHandler = context.getResources().getString(
239                com.android.internal.R.string.config_UsbDeviceConnectionHandling_component);
240        if (!TextUtils.isEmpty(deviceConnectionHandler)) {
241            setUsbDeviceConnectionHandler(ComponentName.unflattenFromString(
242                    deviceConnectionHandler));
243        }
244    }
245
246    public void setCurrentUserSettings(UsbProfileGroupSettingsManager settings) {
247        synchronized (mSettingsLock) {
248            mCurrentSettings = settings;
249        }
250    }
251
252    private UsbProfileGroupSettingsManager getCurrentUserSettings() {
253        synchronized (mSettingsLock) {
254            return mCurrentSettings;
255        }
256    }
257
258    public void setUsbDeviceConnectionHandler(@Nullable ComponentName usbDeviceConnectionHandler) {
259        synchronized (mHandlerLock) {
260            mUsbDeviceConnectionHandler = usbDeviceConnectionHandler;
261        }
262    }
263
264    private @Nullable ComponentName getUsbDeviceConnectionHandler() {
265        synchronized (mHandlerLock) {
266            return mUsbDeviceConnectionHandler;
267        }
268    }
269
270    private boolean isBlackListed(String deviceAddress) {
271        int count = mHostBlacklist.length;
272        for (int i = 0; i < count; i++) {
273            if (deviceAddress.startsWith(mHostBlacklist[i])) {
274                return true;
275            }
276        }
277        return false;
278    }
279
280    /* returns true if the USB device should not be accessible by applications */
281    private boolean isBlackListed(int clazz, int subClass) {
282        // blacklist hubs
283        if (clazz == UsbConstants.USB_CLASS_HUB) return true;
284
285        // blacklist HID boot devices (mouse and keyboard)
286        return clazz == UsbConstants.USB_CLASS_HID
287                && subClass == UsbConstants.USB_INTERFACE_SUBCLASS_BOOT;
288
289    }
290
291    private void addConnectionRecord(String deviceAddress, int mode, byte[] rawDescriptors) {
292        mNumConnects++;
293        while (mConnections.size() >= MAX_CONNECT_RECORDS) {
294            mConnections.removeFirst();
295        }
296        ConnectionRecord rec =
297                new ConnectionRecord(deviceAddress, mode, rawDescriptors);
298        mConnections.add(rec);
299        if (mode != ConnectionRecord.DISCONNECT) {
300            mLastConnect = rec;
301        }
302    }
303
304    private void logUsbDevice(UsbDescriptorParser descriptorParser) {
305        int vid = 0;
306        int pid = 0;
307        String mfg = "<unknown>";
308        String product = "<unknown>";
309        String version = "<unknown>";
310        String serial = "<unknown>";
311
312        UsbDeviceDescriptor deviceDescriptor = descriptorParser.getDeviceDescriptor();
313        if (deviceDescriptor != null) {
314            vid = deviceDescriptor.getVendorID();
315            pid = deviceDescriptor.getProductID();
316            mfg = deviceDescriptor.getMfgString(descriptorParser);
317            product = deviceDescriptor.getProductString(descriptorParser);
318            version = deviceDescriptor.getDeviceReleaseString();
319            serial = deviceDescriptor.getSerialString(descriptorParser);
320        }
321
322        if (vid == LINUX_FOUNDATION_VID) {
323            return;  // don't care about OS-constructed virtual USB devices.
324        }
325        boolean hasAudio = descriptorParser.hasAudioInterface();
326        boolean hasHid = descriptorParser.hasHIDInterface();
327        boolean hasStorage = descriptorParser.hasStorageInterface();
328
329        String attachedString = "USB device attached: ";
330        attachedString += String.format("vidpid %04x:%04x", vid, pid);
331        attachedString += String.format(" mfg/product/ver/serial %s/%s/%s/%s",
332                                        mfg, product, version, serial);
333        attachedString += String.format(" hasAudio/HID/Storage: %b/%b/%b",
334                                        hasAudio, hasHid, hasStorage);
335        Slog.d(TAG, attachedString);
336    }
337
338    /* Called from JNI in monitorUsbHostBus() to report new USB devices
339       Returns true if successful, i.e. the USB Audio device descriptors are
340       correctly parsed and the unique device is added to the audio device list.
341     */
342    @SuppressWarnings("unused")
343    private boolean usbDeviceAdded(String deviceAddress, int deviceClass, int deviceSubclass,
344            byte[] descriptors) {
345        if (DEBUG) {
346            Slog.d(TAG, "usbDeviceAdded(" + deviceAddress + ") - start");
347        }
348
349        if (isBlackListed(deviceAddress)) {
350            if (DEBUG) {
351                Slog.d(TAG, "device address is black listed");
352            }
353            return false;
354        }
355        UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress, descriptors);
356        logUsbDevice(parser);
357
358        if (isBlackListed(deviceClass, deviceSubclass)) {
359            if (DEBUG) {
360                Slog.d(TAG, "device class is black listed");
361            }
362            return false;
363        }
364
365        synchronized (mLock) {
366            if (mDevices.get(deviceAddress) != null) {
367                Slog.w(TAG, "device already on mDevices list: " + deviceAddress);
368                //TODO If this is the same peripheral as is being connected, replace
369                // it with the new connection.
370                return false;
371            }
372
373            UsbDevice newDevice = parser.toAndroidUsbDevice();
374            if (newDevice == null) {
375                Slog.e(TAG, "Couldn't create UsbDevice object.");
376                // Tracking
377                addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT_BADDEVICE,
378                        parser.getRawDescriptors());
379            } else {
380                mDevices.put(deviceAddress, newDevice);
381                Slog.d(TAG, "Added device " + newDevice);
382
383                // It is fine to call this only for the current user as all broadcasts are
384                // sent to all profiles of the user and the dialogs should only show once.
385                ComponentName usbDeviceConnectionHandler = getUsbDeviceConnectionHandler();
386                if (usbDeviceConnectionHandler == null) {
387                    getCurrentUserSettings().deviceAttached(newDevice);
388                } else {
389                    getCurrentUserSettings().deviceAttachedForFixedHandler(newDevice,
390                            usbDeviceConnectionHandler);
391                }
392
393                mUsbAlsaManager.usbDeviceAdded(deviceAddress, newDevice, parser);
394
395                // Tracking
396                addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT,
397                        parser.getRawDescriptors());
398            }
399        }
400
401        if (DEBUG) {
402            Slog.d(TAG, "beginUsbDeviceAdded(" + deviceAddress + ") end");
403        }
404
405        return true;
406    }
407
408    /* Called from JNI in monitorUsbHostBus to report USB device removal */
409    @SuppressWarnings("unused")
410    private void usbDeviceRemoved(String deviceAddress) {
411        synchronized (mLock) {
412            UsbDevice device = mDevices.remove(deviceAddress);
413            if (device != null) {
414                Slog.d(TAG, "Removed device at " + deviceAddress + ": " + device.getProductName());
415                mUsbAlsaManager.usbDeviceRemoved(deviceAddress/*device*/);
416                mSettingsManager.usbDeviceRemoved(device);
417                getCurrentUserSettings().usbDeviceRemoved(device);
418
419                // Tracking
420                addConnectionRecord(deviceAddress, ConnectionRecord.DISCONNECT, null);
421            } else {
422                Slog.d(TAG, "Removed device at " + deviceAddress + " was already gone");
423            }
424        }
425    }
426
427    public void systemReady() {
428        synchronized (mLock) {
429            // Create a thread to call into native code to wait for USB host events.
430            // This thread will call us back on usbDeviceAdded and usbDeviceRemoved.
431            Runnable runnable = this::monitorUsbHostBus;
432            new Thread(null, runnable, "UsbService host thread").start();
433        }
434    }
435
436    /* Returns a list of all currently attached USB devices */
437    public void getDeviceList(Bundle devices) {
438        synchronized (mLock) {
439            for (String name : mDevices.keySet()) {
440                devices.putParcelable(name, mDevices.get(name));
441            }
442        }
443    }
444
445    /* Opens the specified USB device */
446    public ParcelFileDescriptor openDevice(String deviceAddress, UsbUserSettingsManager settings,
447            String packageName, int uid) {
448        synchronized (mLock) {
449            if (isBlackListed(deviceAddress)) {
450                throw new SecurityException("USB device is on a restricted bus");
451            }
452            UsbDevice device = mDevices.get(deviceAddress);
453            if (device == null) {
454                // if it is not in mDevices, it either does not exist or is blacklisted
455                throw new IllegalArgumentException(
456                        "device " + deviceAddress + " does not exist or is restricted");
457            }
458
459            settings.checkPermission(device, packageName, uid);
460            return nativeOpenDevice(deviceAddress);
461        }
462    }
463
464    /**
465     * Dump out various information about the state of USB device connections.
466     */
467    public void dump(DualDumpOutputStream dump, String idName, long id) {
468        long token = dump.start(idName, id);
469
470        synchronized (mHandlerLock) {
471            if (mUsbDeviceConnectionHandler != null) {
472                writeComponentName(dump, "default_usb_host_connection_handler",
473                        UsbHostManagerProto.DEFAULT_USB_HOST_CONNECTION_HANDLER,
474                        mUsbDeviceConnectionHandler);
475            }
476        }
477        synchronized (mLock) {
478            for (String name : mDevices.keySet()) {
479                writeDevice(dump, "devices", UsbHostManagerProto.DEVICES, mDevices.get(name));
480            }
481
482            dump.write("num_connects", UsbHostManagerProto.NUM_CONNECTS, mNumConnects);
483
484            for (ConnectionRecord rec : mConnections) {
485                rec.dump(dump, "connections", UsbHostManagerProto.CONNECTIONS);
486            }
487        }
488
489        dump.end(token);
490    }
491
492    /**
493     * Dump various descriptor data.
494     */
495    public void dumpDescriptors(IndentingPrintWriter pw, String[] args) {
496        if (mLastConnect != null) {
497            pw.println("Last Connected USB Device:");
498            if (args.length <= 1 || args[1].equals("-dump-short")) {
499                mLastConnect.dumpShort(pw);
500            } else if (args[1].equals("-dump-tree")) {
501                mLastConnect.dumpTree(pw);
502            } else if (args[1].equals("-dump-list")) {
503                mLastConnect.dumpList(pw);
504            }  else if (args[1].equals("-dump-raw")) {
505                mLastConnect.dumpRaw(pw);
506            }
507        } else {
508            pw.println("No USB Devices have been connected.");
509        }
510    }
511
512    private native void monitorUsbHostBus();
513    private native ParcelFileDescriptor nativeOpenDevice(String deviceAddress);
514}
515