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
diff --git a/tests/UsbTests/Android.mk b/tests/UsbTests/Android.mk
new file mode 100644
index 0000000..a04f32a
--- /dev/null
+++ b/tests/UsbTests/Android.mk
@@ -0,0 +1,44 @@
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    frameworks-base-testutils \
+    android-support-test \
+    mockito-target-inline-minus-junit4 \
+    platform-test-annotations \
+    services.core \
+    services.net \
+    services.usb \
+    truth-prebuilt \
+
+LOCAL_JNI_SHARED_LIBRARIES := \
+    libdexmakerjvmtiagent \
+
+LOCAL_CERTIFICATE := platform
+
+LOCAL_PACKAGE_NAME := UsbTests
+
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/UsbTests/AndroidManifest.xml b/tests/UsbTests/AndroidManifest.xml
new file mode 100644
index 0000000..5d60695
--- /dev/null
+++ b/tests/UsbTests/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.server.usb" >
+
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+    <uses-permission android:name="android.permission.MANAGE_USERS" />
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.usb"
+                     android:label="UsbTests"/>
+</manifest>
diff --git a/tests/UsbTests/AndroidTest.xml b/tests/UsbTests/AndroidTest.xml
new file mode 100644
index 0000000..0b623fb
--- /dev/null
+++ b/tests/UsbTests/AndroidTest.xml
@@ -0,0 +1,29 @@
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs sample instrumentation test.">
+    <target_preparer class="com.android.tradefed.targetprep.TestFilePushSetup"/>
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="UsbTests.apk"/>
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"/>
+    <option name="test-suite-tag" value="apct"/>
+    <option name="test-tag" value="UsbTests"/>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.server.usb"/>
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
new file mode 100644
index 0000000..c491b46
--- /dev/null
+++ b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.hardware.usb.UsbManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.FgThread;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Tests for UsbHandler state changes.
+ */
+@RunWith(AndroidJUnit4.class)
+public class UsbHandlerTest {
+    private static final String TAG = UsbHandlerTest.class.getSimpleName();
+
+    @Mock
+    private UsbDeviceManager mUsbDeviceManager;
+    @Mock
+    private UsbDebuggingManager mUsbDebuggingManager;
+    @Mock
+    private UsbAlsaManager mUsbAlsaManager;
+    @Mock
+    private UsbSettingsManager mUsbSettingsManager;
+    @Mock
+    private SharedPreferences mSharedPreferences;
+    @Mock
+    private SharedPreferences.Editor mEditor;
+
+    private MockUsbHandler mUsbHandler;
+
+    private static final int MSG_UPDATE_STATE = 0;
+    private static final int MSG_ENABLE_ADB = 1;
+    private static final int MSG_SET_CURRENT_FUNCTIONS = 2;
+    private static final int MSG_SYSTEM_READY = 3;
+    private static final int MSG_BOOT_COMPLETED = 4;
+    private static final int MSG_USER_SWITCHED = 5;
+    private static final int MSG_UPDATE_USER_RESTRICTIONS = 6;
+    private static final int MSG_SET_SCREEN_UNLOCKED_FUNCTIONS = 12;
+    private static final int MSG_UPDATE_SCREEN_LOCK = 13;
+
+    private Map<String, String> mMockProperties;
+    private Map<String, Integer> mMockGlobalSettings;
+
+    private class MockUsbHandler extends UsbDeviceManager.UsbHandler {
+        boolean mIsUsbTransferAllowed;
+        Intent mBroadcastedIntent;
+
+        MockUsbHandler(Looper looper, Context context, UsbDeviceManager deviceManager,
+                UsbDebuggingManager debuggingManager, UsbAlsaManager alsaManager,
+                UsbSettingsManager settingsManager) {
+            super(looper, context, deviceManager, debuggingManager, alsaManager, settingsManager);
+            mUseUsbNotification = false;
+            mIsUsbTransferAllowed = true;
+            mCurrentUsbFunctionsReceived = true;
+        }
+
+        @Override
+        protected void setEnabledFunctions(long functions, boolean force) {
+            mCurrentFunctions = functions;
+        }
+
+        @Override
+        protected void setSystemProperty(String property, String value) {
+            mMockProperties.put(property, value);
+        }
+
+        @Override
+        protected void putGlobalSettings(ContentResolver resolver, String setting, int val) {
+            mMockGlobalSettings.put(setting, val);
+        }
+
+        @Override
+        protected String getSystemProperty(String property, String def) {
+            if (mMockProperties.containsKey(property)) {
+                return mMockProperties.get(property);
+            }
+            return def;
+        }
+
+        @Override
+        protected boolean isUsbTransferAllowed() {
+            return mIsUsbTransferAllowed;
+        }
+
+        @Override
+        protected SharedPreferences getPinnedSharedPrefs(Context context) {
+            return mSharedPreferences;
+        }
+
+        @Override
+        protected void sendStickyBroadcast(Intent intent) {
+            mBroadcastedIntent = intent;
+        }
+    }
+
+    @Before
+    public void before() {
+        MockitoAnnotations.initMocks(this);
+        mMockProperties = new HashMap<>();
+        mMockGlobalSettings = new HashMap<>();
+        when(mSharedPreferences.edit()).thenReturn(mEditor);
+
+        mUsbHandler = new MockUsbHandler(FgThread.get().getLooper(),
+                InstrumentationRegistry.getContext(), mUsbDeviceManager, mUsbDebuggingManager,
+                mUsbAlsaManager, mUsbSettingsManager);
+    }
+
+    @SmallTest
+    public void setFunctionsMtp() {
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+                UsbManager.FUNCTION_MTP));
+        assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0);
+    }
+
+    @SmallTest
+    public void setFunctionsPtp() {
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+                UsbManager.FUNCTION_PTP));
+        assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_PTP, 0);
+    }
+
+    @SmallTest
+    public void setFunctionsMidi() {
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+                UsbManager.FUNCTION_MIDI));
+        assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MIDI, 0);
+    }
+
+    @SmallTest
+    public void setFunctionsRndis() {
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+                UsbManager.FUNCTION_RNDIS));
+        assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_RNDIS, 0);
+    }
+
+    @SmallTest
+    public void enableAdb() {
+        sendBootCompleteMessages(mUsbHandler);
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_ENABLE_ADB, 1));
+        assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE);
+        assertTrue(mUsbHandler.mAdbEnabled);
+        assertEquals(mMockProperties.get(UsbDeviceManager.UsbHandler
+                .USB_PERSISTENT_CONFIG_PROPERTY), UsbManager.USB_FUNCTION_ADB);
+        verify(mUsbDebuggingManager).setAdbEnabled(true);
+
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_STATE, 1, 1));
+
+        assertTrue(mUsbHandler.mBroadcastedIntent.getBooleanExtra(UsbManager.USB_CONNECTED, false));
+        assertTrue(mUsbHandler.mBroadcastedIntent
+                .getBooleanExtra(UsbManager.USB_CONFIGURED, false));
+        assertTrue(mUsbHandler.mBroadcastedIntent
+                .getBooleanExtra(UsbManager.USB_FUNCTION_ADB, false));
+    }
+
+    @SmallTest
+    public void disableAdb() {
+        mMockProperties.put(UsbDeviceManager.UsbHandler.USB_PERSISTENT_CONFIG_PROPERTY,
+                UsbManager.USB_FUNCTION_ADB);
+        mUsbHandler = new MockUsbHandler(FgThread.get().getLooper(),
+                InstrumentationRegistry.getContext(), mUsbDeviceManager, mUsbDebuggingManager,
+                mUsbAlsaManager, mUsbSettingsManager);
+
+        sendBootCompleteMessages(mUsbHandler);
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_ENABLE_ADB, 0));
+        assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE);
+        assertFalse(mUsbHandler.mAdbEnabled);
+        assertEquals(mMockProperties.get(UsbDeviceManager.UsbHandler
+                .USB_PERSISTENT_CONFIG_PROPERTY), "");
+        verify(mUsbDebuggingManager).setAdbEnabled(false);
+    }
+
+    @SmallTest
+    public void bootCompletedCharging() {
+        sendBootCompleteMessages(mUsbHandler);
+        assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE);
+    }
+
+    @Test
+    @SmallTest
+    public void bootCompletedAdbEnabled() {
+        mMockProperties.put(UsbDeviceManager.UsbHandler.USB_PERSISTENT_CONFIG_PROPERTY, "adb");
+        mUsbHandler = new MockUsbHandler(FgThread.get().getLooper(),
+                InstrumentationRegistry.getContext(), mUsbDeviceManager, mUsbDebuggingManager,
+                mUsbAlsaManager, mUsbSettingsManager);
+
+        sendBootCompleteMessages(mUsbHandler);
+        assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE);
+        assertEquals(mMockGlobalSettings.get(Settings.Global.ADB_ENABLED).intValue(), 1);
+        assertTrue(mUsbHandler.mAdbEnabled);
+        verify(mUsbDebuggingManager).setAdbEnabled(true);
+    }
+
+    @SmallTest
+    public void userSwitchedDisablesMtp() {
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+                UsbManager.FUNCTION_MTP));
+        assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0);
+
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_USER_SWITCHED,
+                UserHandle.getCallingUserId() + 1));
+        assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE);
+    }
+
+    @SmallTest
+    public void changedRestrictionsDisablesMtp() {
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+                UsbManager.FUNCTION_MTP));
+        assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0);
+
+        mUsbHandler.mIsUsbTransferAllowed = false;
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_USER_RESTRICTIONS));
+        assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE);
+    }
+
+    @SmallTest
+    public void disconnectResetsCharging() {
+        sendBootCompleteMessages(mUsbHandler);
+
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+                UsbManager.FUNCTION_MTP));
+        assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0);
+
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_STATE, 0, 0));
+
+        assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE);
+    }
+
+    @SmallTest
+    public void configuredSendsBroadcast() {
+        sendBootCompleteMessages(mUsbHandler);
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+                UsbManager.FUNCTION_MTP));
+        assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0);
+
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_STATE, 1, 1));
+
+        assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0);
+        assertTrue(mUsbHandler.mBroadcastedIntent.getBooleanExtra(UsbManager.USB_CONNECTED, false));
+        assertTrue(mUsbHandler.mBroadcastedIntent
+                .getBooleanExtra(UsbManager.USB_CONFIGURED, false));
+        assertTrue(mUsbHandler.mBroadcastedIntent
+                .getBooleanExtra(UsbManager.USB_FUNCTION_MTP, false));
+    }
+
+    @SmallTest
+    public void setScreenUnlockedFunctions() {
+        sendBootCompleteMessages(mUsbHandler);
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_SCREEN_LOCK, 0));
+
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_SCREEN_UNLOCKED_FUNCTIONS,
+                UsbManager.FUNCTION_MTP));
+        assertNotEquals(mUsbHandler.getScreenUnlockedFunctions() & UsbManager.FUNCTION_MTP, 0);
+        assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0);
+        verify(mEditor).putString(String.format(Locale.ENGLISH,
+                UsbDeviceManager.UNLOCKED_CONFIG_PREF, mUsbHandler.mCurrentUser),
+                UsbManager.USB_FUNCTION_MTP);
+    }
+
+    @SmallTest
+    public void unlockScreen() {
+        when(mSharedPreferences.getString(String.format(Locale.ENGLISH,
+                UsbDeviceManager.UNLOCKED_CONFIG_PREF, mUsbHandler.mCurrentUser), ""))
+                .thenReturn(UsbManager.USB_FUNCTION_MTP);
+        sendBootCompleteMessages(mUsbHandler);
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_SCREEN_LOCK, 0));
+
+        assertNotEquals(mUsbHandler.getScreenUnlockedFunctions() & UsbManager.FUNCTION_MTP, 0);
+        assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0);
+    }
+
+    private static void sendBootCompleteMessages(Handler handler) {
+        handler.handleMessage(handler.obtainMessage(MSG_BOOT_COMPLETED));
+        handler.handleMessage(handler.obtainMessage(MSG_SYSTEM_READY));
+    }
+}
\ No newline at end of file
diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java
index 099cfd4..e692652 100644
--- a/tests/net/java/com/android/server/connectivity/TetheringTest.java
+++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java
@@ -311,7 +311,7 @@
         // Emulate pressing the USB tethering button in Settings UI.
         mTethering.startTethering(TETHERING_USB, null, false);
         mLooper.dispatchAll();
-        verify(mUsbManager, times(1)).setCurrentFunction(UsbManager.USB_FUNCTION_RNDIS, false);
+        verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
 
         // Pretend we receive a USB connected broadcast. Here we also pretend
         // that the RNDIS function is somehow enabled, so that we see if we