Add Developer option to enable vendor logging in Settings app

Bug: 148822215
Test: make && make RunSettingsRoboTests
Change-Id: Ia8d9002c7f85b607a8d87a69d04d3be1e59abcab
diff --git a/Android.bp b/Android.bp
index cc273fb..4112d04 100644
--- a/Android.bp
+++ b/Android.bp
@@ -54,6 +54,8 @@
         "settings-logtags",
         "statslog-settings",
         "zxing-core-1.7",
+        "android.hardware.dumpstate-V1.0-java",
+        "android.hardware.dumpstate-V1.1-java",
     ],
 
     libs: [
diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml
index 940cb59..c6f0631 100644
--- a/res/xml/development_settings.xml
+++ b/res/xml/development_settings.xml
@@ -152,6 +152,12 @@
             android:summary="@string/bugreport_in_power_summary" />
 
         <SwitchPreference
+            android:key="enable_verbose_vendor_logging"
+            android:title="@string/enable_verbose_vendor_logging"
+            android:summary="@string/enable_verbose_vendor_logging_summary"
+            />
+
+        <SwitchPreference
             android:key="automatic_system_server_heap_dumps"
             android:title="@string/automatic_system_heap_dump_title"
             android:summary="@string/automatic_system_heap_dump_summary" />
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index 1c08131..07e2ecd 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -451,6 +451,7 @@
         controllers.add(new SelectDebugAppPreferenceController(context, fragment));
         controllers.add(new WaitForDebuggerPreferenceController(context));
         controllers.add(new EnableGpuDebugLayersPreferenceController(context));
+        controllers.add(new EnableVerboseVendorLoggingPreferenceController(context));
         controllers.add(new VerifyAppsOverUsbPreferenceController(context));
         controllers.add(new ArtVerifierPreferenceController(context));
         controllers.add(new LogdSizePreferenceController(context));
diff --git a/src/com/android/settings/development/EnableVerboseVendorLoggingPreferenceController.java b/src/com/android/settings/development/EnableVerboseVendorLoggingPreferenceController.java
new file mode 100644
index 0000000..17e091e
--- /dev/null
+++ b/src/com/android/settings/development/EnableVerboseVendorLoggingPreferenceController.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2020 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.settings.development;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.dumpstate.V1_0.IDumpstateDevice;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.SwitchPreference;
+
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settingslib.development.DeveloperOptionsPreferenceController;
+
+import java.util.NoSuchElementException;
+
+public class EnableVerboseVendorLoggingPreferenceController
+        extends DeveloperOptionsPreferenceController
+        implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin {
+
+    private static final String TAG = "EnableVerboseVendorLoggingPreferenceController";
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private static final String ENABLE_VERBOSE_VENDOR_LOGGING_KEY = "enable_verbose_vendor_logging";
+    private static final int DUMPSTATE_HAL_VERSION_UNKNOWN = -1;
+    private static final int DUMPSTATE_HAL_VERSION_1_0 = 0;
+    private static final int DUMPSTATE_HAL_VERSION_1_1 = 1;
+
+    private int mDumpstateHalVersion;
+
+    public EnableVerboseVendorLoggingPreferenceController(Context context) {
+        super(context);
+        mDumpstateHalVersion = DUMPSTATE_HAL_VERSION_UNKNOWN;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return ENABLE_VERBOSE_VENDOR_LOGGING_KEY;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        // Only show preference when IDumpstateDevice v1.1 is avalaible
+        // This is temperary strategy that may change later.
+        return isIDumpstateDeviceV1_1ServiceAvailable();
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        final boolean isEnabled = (Boolean) newValue;
+        setVerboseLoggingEnabled(isEnabled);
+        return true;
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        final boolean enabled = getVerboseLoggingEnabled();
+        ((SwitchPreference) mPreference).setChecked(enabled);
+    }
+
+    @Override
+    protected void onDeveloperOptionsSwitchDisabled() {
+        super.onDeveloperOptionsSwitchDisabled();
+        setVerboseLoggingEnabled(false);
+        ((SwitchPreference) mPreference).setChecked(false);
+    }
+
+    @VisibleForTesting
+    boolean isIDumpstateDeviceV1_1ServiceAvailable() {
+        IDumpstateDevice service = getDumpstateDeviceService();
+        if (service == null) {
+            if (DBG) Log.d(TAG, "IDumpstateDevice service is not available.");
+        }
+        return service != null && mDumpstateHalVersion >= DUMPSTATE_HAL_VERSION_1_1;
+    }
+
+    @VisibleForTesting
+    void setVerboseLoggingEnabled(boolean enable) {
+        IDumpstateDevice service = getDumpstateDeviceService();
+
+        if (service == null || mDumpstateHalVersion < DUMPSTATE_HAL_VERSION_1_1) {
+            if (DBG) Log.d(TAG, "setVerboseLoggingEnabled not supported.");
+            return;
+        }
+
+        try {
+            android.hardware.dumpstate.V1_1.IDumpstateDevice service11 =
+                    (android.hardware.dumpstate.V1_1.IDumpstateDevice) service;
+            if (service11 != null) {
+                service11.setVerboseLoggingEnabled(enable);
+            }
+        } catch (RemoteException | RuntimeException e) {
+            if (DBG) Log.e(TAG, "setVerboseLoggingEnabled fail: " + e);
+        }
+    }
+
+    @VisibleForTesting
+    boolean getVerboseLoggingEnabled() {
+        IDumpstateDevice service = getDumpstateDeviceService();
+
+        if (service == null || mDumpstateHalVersion < DUMPSTATE_HAL_VERSION_1_1) {
+            if (DBG) Log.d(TAG, "getVerboseLoggingEnabled not supported.");
+            return false;
+        }
+
+        try {
+            android.hardware.dumpstate.V1_1.IDumpstateDevice service11 =
+                    (android.hardware.dumpstate.V1_1.IDumpstateDevice) service;
+            if (service11 != null) {
+                return service11.getVerboseLoggingEnabled();
+            }
+        } catch (RemoteException | RuntimeException e) {
+            if (DBG) Log.e(TAG, "getVerboseLoggingEnabled fail: " + e);
+        }
+        return false;
+    }
+
+    /** Return a {@IDumpstateDevice} instance or null if service is not available. */
+    private @Nullable IDumpstateDevice getDumpstateDeviceService() {
+        IDumpstateDevice service = null;
+        try {
+            service = android.hardware.dumpstate.V1_1.IDumpstateDevice
+                    .getService(true /* retry */);
+            mDumpstateHalVersion = DUMPSTATE_HAL_VERSION_1_1;
+        } catch (NoSuchElementException | RemoteException e) {
+        }
+
+        if (service == null) {
+            try {
+                service = android.hardware.dumpstate.V1_0.IDumpstateDevice
+                        .getService(true /* retry */);
+                mDumpstateHalVersion = DUMPSTATE_HAL_VERSION_1_0;
+            } catch (NoSuchElementException | RemoteException e) {
+            }
+        }
+
+        if (service == null) {
+            mDumpstateHalVersion = DUMPSTATE_HAL_VERSION_UNKNOWN;
+        }
+        return service;
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/development/EnableVerboseVendorLoggingPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/EnableVerboseVendorLoggingPreferenceControllerTest.java
new file mode 100644
index 0000000..96f06fe
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/development/EnableVerboseVendorLoggingPreferenceControllerTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2020 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.settings.development;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreference;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public final class EnableVerboseVendorLoggingPreferenceControllerTest {
+    @Mock
+    private SwitchPreference mPreference;
+    @Mock
+    private PreferenceScreen mPreferenceScreen;
+
+    private Context mContext;
+    private EnableVerboseVendorLoggingPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mController = new EnableVerboseVendorLoggingPreferenceController(mContext);
+
+        // bypass if IDumpstateDevice service not avalaible at all
+        org.junit.Assume.assumeTrue(mController.isIDumpstateDeviceV1_1ServiceAvailable());
+
+        when(mPreferenceScreen.findPreference(mController.getPreferenceKey()))
+                .thenReturn(mPreference);
+        mController.displayPreference(mPreferenceScreen);
+    }
+
+    @Test
+    public void onPreferenceChange_settingEnable_enableVendorLoggingShouldBeOn() {
+        mController.onPreferenceChange(mPreference, true /* new value */);
+
+        final boolean enabled = mController.getVerboseLoggingEnabled();
+        assertTrue(enabled);
+    }
+
+    @Test
+    public void onPreferenceChange_settingDisable_enableVendorLoggingShouldBeOff() {
+        mController.onPreferenceChange(mPreference,  false /* new value */);
+
+        final boolean enabled = mController.getVerboseLoggingEnabled();
+        assertFalse(enabled);
+    }
+
+    @Test
+    public void updateState_settingDisabled_preferenceShouldNotBeChecked() {
+        mController.setVerboseLoggingEnabled(false);
+        mController.updateState(mPreference);
+
+        verify(mPreference).setChecked(false);
+    }
+
+    @Test
+    public void updateState_settingEnabled_preferenceShouldBeChecked() {
+        mController.setVerboseLoggingEnabled(true);
+        mController.updateState(mPreference);
+
+        verify(mPreference).setChecked(true);
+    }
+
+    @Test
+    public void onDeveloperOptionDisabled_shouldDisablePreference() {
+        mController.onDeveloperOptionsSwitchDisabled();
+
+        final boolean enabled = mController.getVerboseLoggingEnabled();
+        assertFalse(enabled);
+        verify(mPreference).setChecked(false);
+        verify(mPreference).setEnabled(false);
+    }
+}