1/*
2 * Copyright (C) 2015 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 */
16package com.android.settings.deviceinfo;
17
18import android.content.Context;
19import android.content.Intent;
20import android.content.IntentFilter;
21import android.content.pm.PackageManager;
22import android.hardware.usb.UsbManager;
23import android.hardware.usb.UsbPort;
24import android.hardware.usb.UsbPortStatus;
25import android.os.UserHandle;
26import android.os.UserManager;
27
28public class UsbBackend {
29
30    private static final int MODE_POWER_MASK  = 0x01;
31    public static final int MODE_POWER_SINK   = 0x00;
32    public static final int MODE_POWER_SOURCE = 0x01;
33
34    private static final int MODE_DATA_MASK  = 0x03 << 1;
35    public static final int MODE_DATA_NONE   = 0x00 << 1;
36    public static final int MODE_DATA_MTP    = 0x01 << 1;
37    public static final int MODE_DATA_PTP    = 0x02 << 1;
38    public static final int MODE_DATA_MIDI   = 0x03 << 1;
39
40    private final boolean mRestricted;
41    private final boolean mRestrictedBySystem;
42    private final boolean mMidi;
43
44    private UserManager mUserManager;
45    private UsbManager mUsbManager;
46    private UsbPort mPort;
47    private UsbPortStatus mPortStatus;
48
49    private boolean mIsUnlocked;
50
51    public UsbBackend(Context context) {
52        Intent intent = context.registerReceiver(null,
53                new IntentFilter(UsbManager.ACTION_USB_STATE));
54        mIsUnlocked = intent == null ?
55                false : intent.getBooleanExtra(UsbManager.USB_DATA_UNLOCKED, false);
56
57        mUserManager = UserManager.get(context);
58        mUsbManager = context.getSystemService(UsbManager.class);
59
60        mRestricted = mUserManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER);
61        mRestrictedBySystem = mUserManager.hasBaseUserRestriction(
62                UserManager.DISALLOW_USB_FILE_TRANSFER, UserHandle.of(UserHandle.myUserId()));
63        mMidi = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
64
65        UsbPort[] ports = mUsbManager.getPorts();
66        // For now look for a connected port, in the future we should identify port in the
67        // notification and pick based on that.
68        final int N = ports.length;
69        for (int i = 0; i < N; i++) {
70            UsbPortStatus status = mUsbManager.getPortStatus(ports[i]);
71            if (status.isConnected()) {
72                mPort = ports[i];
73                mPortStatus = status;
74                break;
75            }
76        }
77    }
78
79    public int getCurrentMode() {
80        if (mPort != null) {
81            int power = mPortStatus.getCurrentPowerRole() == UsbPort.POWER_ROLE_SOURCE
82                    ? MODE_POWER_SOURCE : MODE_POWER_SINK;
83            return power | getUsbDataMode();
84        }
85        return MODE_POWER_SINK | getUsbDataMode();
86    }
87
88    public int getUsbDataMode() {
89        if (!mIsUnlocked) {
90            return MODE_DATA_NONE;
91        } else if (mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_MTP)) {
92            return MODE_DATA_MTP;
93        } else if (mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_PTP)) {
94            return MODE_DATA_PTP;
95        } else if (mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_MIDI)) {
96            return MODE_DATA_MIDI;
97        }
98        return MODE_DATA_NONE; // ...
99    }
100
101    private void setUsbFunction(int mode) {
102        switch (mode) {
103            case MODE_DATA_MTP:
104                mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MTP);
105                mUsbManager.setUsbDataUnlocked(true);
106                break;
107            case MODE_DATA_PTP:
108                mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_PTP);
109                mUsbManager.setUsbDataUnlocked(true);
110                break;
111            case MODE_DATA_MIDI:
112                mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MIDI);
113                mUsbManager.setUsbDataUnlocked(true);
114                break;
115            default:
116                mUsbManager.setCurrentFunction(null);
117                mUsbManager.setUsbDataUnlocked(false);
118                break;
119        }
120    }
121
122    public void setMode(int mode) {
123        if (mPort != null) {
124            int powerRole = modeToPower(mode);
125            // If we aren't using any data modes and we support host mode, then go to host mode
126            // so maybe? the other device can provide data if it wants, otherwise go into device
127            // mode because we have no choice.
128            int dataRole = (mode & MODE_DATA_MASK) == MODE_DATA_NONE
129                    && mPortStatus.isRoleCombinationSupported(powerRole, UsbPort.DATA_ROLE_HOST)
130                    ? UsbPort.DATA_ROLE_HOST : UsbPort.DATA_ROLE_DEVICE;
131            mUsbManager.setPortRoles(mPort, powerRole, dataRole);
132        }
133        setUsbFunction(mode & MODE_DATA_MASK);
134    }
135
136    private int modeToPower(int mode) {
137        return (mode & MODE_POWER_MASK) == MODE_POWER_SOURCE
138                    ? UsbPort.POWER_ROLE_SOURCE : UsbPort.POWER_ROLE_SINK;
139    }
140
141    public boolean isModeDisallowed(int mode) {
142        if (mRestricted && (mode & MODE_DATA_MASK) != MODE_DATA_NONE
143                && (mode & MODE_DATA_MASK) != MODE_DATA_MIDI) {
144            // No USB data modes are supported.
145            return true;
146        }
147        return false;
148    }
149
150    public boolean isModeDisallowedBySystem(int mode) {
151        if (mRestrictedBySystem && (mode & MODE_DATA_MASK) != MODE_DATA_NONE
152                && (mode & MODE_DATA_MASK) != MODE_DATA_MIDI) {
153            // No USB data modes are supported.
154            return true;
155        }
156        return false;
157    }
158
159    public boolean isModeSupported(int mode) {
160        if (!mMidi && (mode & MODE_DATA_MASK) == MODE_DATA_MIDI) {
161            return false;
162        }
163
164        if (mPort != null) {
165            int power = modeToPower(mode);
166            if ((mode & MODE_DATA_MASK) != 0) {
167                // We have a port and data, need to be in device mode.
168                return mPortStatus.isRoleCombinationSupported(power,
169                        UsbPort.DATA_ROLE_DEVICE);
170            } else {
171                // No data needed, we can do this power mode in either device or host.
172                return mPortStatus.isRoleCombinationSupported(power, UsbPort.DATA_ROLE_DEVICE)
173                        || mPortStatus.isRoleCombinationSupported(power, UsbPort.DATA_ROLE_HOST);
174            }
175        }
176        // No port, support sink modes only.
177        return (mode & MODE_POWER_MASK) != MODE_POWER_SOURCE;
178    }
179}
180