Add developer option to disable automatic adb grant revocation

Android 10 introduced a security feature to automatically revoke
adb authorizations for systems that have not reconnected to the device
within 7 days. While this is helpful for consumers that enable adb for
a one time task and mistakenly select the 'always allow' option,
feedback has indicated having a developer option to disable this feature
would be beneficial.

Bug: 119510647
Test: make RunSettingsRoboTests ROBOTEST_FILTER=AdbAuthorizationTimeoutPreferenceControllerTest
Change-Id: I7eb123e8c69956aa02bb679784ac79650baf5dcb
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 94972f7..de83bf0 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -10454,6 +10454,11 @@
     <!-- [CHAR LIMIT=60] Name of dev option to enable extra quick settings tiles -->
     <string name="quick_settings_developer_tiles">Quick settings developer tiles</string>
 
+    <!-- [CHAR LIMIT=50] Setting title to disable the adb authorization timeout feature. -->
+    <string name="adb_authorization_timeout_title">Disable adb authorization timeout</string>
+    <!-- [CHAR LIMIT=NONE] Setting summary explaining the disablement of the automatic adb authorization timeout. -->
+    <string name="adb_authorization_timeout_summary">Disable automatic revocation of adb authorizations for systems that have not reconnected within the default (7 days) or user-configured (minimum 1 day) amount of time.</string>
+
     <!-- [CHAR LIMIT=25] Title of developer tile to toggle winscope trace -->
     <string name="winscope_trace_quick_settings_title">Winscope Trace</string>
 
diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml
index b4d0bba..48a0850 100644
--- a/res/xml/development_settings.xml
+++ b/res/xml/development_settings.xml
@@ -149,6 +149,11 @@
             settings:keywords="@string/keywords_adb_wireless" />
 
         <SwitchPreference
+            android:key="adb_authorization_timeout"
+            android:title="@string/adb_authorization_timeout_title"
+            android:summary="@string/adb_authorization_timeout_summary" />
+
+        <SwitchPreference
             android:key="enable_terminal"
             android:title="@string/enable_terminal_title"
             android:summary="@string/enable_terminal_summary" />
diff --git a/src/com/android/settings/development/AdbAuthorizationTimeoutPreferenceController.java b/src/com/android/settings/development/AdbAuthorizationTimeoutPreferenceController.java
new file mode 100644
index 0000000..79aa8e6
--- /dev/null
+++ b/src/com/android/settings/development/AdbAuthorizationTimeoutPreferenceController.java
@@ -0,0 +1,79 @@
+/*
+ * 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.content.Context;
+import android.provider.Settings;
+
+import androidx.preference.Preference;
+import androidx.preference.SwitchPreference;
+
+import com.android.settingslib.development.DeveloperOptionsPreferenceController;
+
+/**
+ * Preference controller for the developer option to disable the automatic revocation of adb
+ * authorizations.
+ */
+public class AdbAuthorizationTimeoutPreferenceController extends
+        DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener {
+    private static final String ADB_AUTHORIZATION_TIMEOUT_KEY = "adb_authorization_timeout";
+
+    private final Context mContext;
+
+    public AdbAuthorizationTimeoutPreferenceController(Context context) {
+        super(context);
+        mContext = context;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return ADB_AUTHORIZATION_TIMEOUT_KEY;
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        final long authTimeout = Settings.Global.getLong(mContext.getContentResolver(),
+                Settings.Global.ADB_ALLOWED_CONNECTION_TIME,
+                Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME);
+        // An authTimeout of 0 indicates this preference is enabled and adb authorizations will not
+        // be automatically revoked.
+        ((SwitchPreference) mPreference).setChecked(authTimeout == 0);
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        writeSetting((boolean) newValue);
+        return true;
+    }
+
+    @Override
+    public void onDeveloperOptionsSwitchDisabled() {
+        super.onDeveloperOptionsSwitchDisabled();
+        writeSetting(false);
+        ((SwitchPreference) mPreference).setChecked(false);
+    }
+
+    private void writeSetting(boolean isEnabled) {
+        long authTimeout = 0;
+        if (!isEnabled) {
+            authTimeout = Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME;
+        }
+        Settings.Global.putLong(mContext.getContentResolver(),
+                Settings.Global.ADB_ALLOWED_CONNECTION_TIME,
+                authTimeout);
+    }
+}
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index 8c79f2a..c349de9 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -444,6 +444,7 @@
         controllers.add(new AdbPreferenceController(context, fragment));
         controllers.add(new ClearAdbKeysPreferenceController(context, fragment));
         controllers.add(new WirelessDebuggingPreferenceController(context, lifecycle));
+        controllers.add(new AdbAuthorizationTimeoutPreferenceController(context));
         controllers.add(new LocalTerminalPreferenceController(context));
         controllers.add(new BugReportInPowerPreferenceController(context));
         controllers.add(new AutomaticSystemServerHeapDumpPreferenceController(context));
diff --git a/tests/robotests/src/com/android/settings/development/AdbAuthorizationTimeoutPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/AdbAuthorizationTimeoutPreferenceControllerTest.java
new file mode 100644
index 0000000..bc3544d
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/development/AdbAuthorizationTimeoutPreferenceControllerTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.assertEquals;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreference;
+
+import org.junit.After;
+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 class AdbAuthorizationTimeoutPreferenceControllerTest {
+    @Mock
+    private PreferenceScreen mPreferenceScreen;
+    @Mock
+    private DevelopmentSettingsDashboardFragment mFragment;
+
+    private Context mContext;
+    private ContentResolver mContentResolver;
+    private SwitchPreference mPreference;
+    private AdbAuthorizationTimeoutPreferenceController mPreferenceController;
+    private long mInitialAuthTimeout;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mContentResolver = mContext.getContentResolver();
+
+        mPreferenceController = new AdbAuthorizationTimeoutPreferenceController(mContext);
+        mPreference = spy(new SwitchPreference(mContext));
+        when(mPreferenceScreen.findPreference(mPreferenceController.getPreferenceKey())).thenReturn(
+                mPreference);
+        mPreferenceController.displayPreference(mPreferenceScreen);
+
+        mInitialAuthTimeout = Settings.Global.getLong(mContext.getContentResolver(),
+                Settings.Global.ADB_ALLOWED_CONNECTION_TIME,
+                Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        Settings.Global.putLong(mContext.getContentResolver(),
+                Settings.Global.ADB_ALLOWED_CONNECTION_TIME, mInitialAuthTimeout);
+    }
+
+    @Test
+    public void onPreferenceChange_enableSetting_timeoutSetToZero() throws Exception {
+        // This developer option disables the automatic adb authorization revocation by setting
+        // the timeout value to 0 when enabled.
+        mPreferenceController.onPreferenceChange(mPreference, true);
+        long authTimeout = Settings.Global.getLong(mContentResolver,
+                Settings.Global.ADB_ALLOWED_CONNECTION_TIME);
+
+        assertEquals(0, authTimeout);
+    }
+
+    @Test
+    public void onPreferenceChange_enableAndDisableSetting_timeoutSetToDefault()
+            throws Exception {
+        // A non-default setting value is not saved when this developer option is enabled and the
+        // setting value is set to 0. If the user subsequently disables the option the setting
+        // value is restored to the default value.
+        Settings.Global.putLong(mContentResolver, Settings.Global.ADB_ALLOWED_CONNECTION_TIME, 1);
+
+        mPreferenceController.onPreferenceChange(mPreference, true);
+        mPreferenceController.onPreferenceChange(mPreference, false);
+        long authTimeout = Settings.Global.getLong(mContentResolver,
+                Settings.Global.ADB_ALLOWED_CONNECTION_TIME);
+
+        assertEquals(Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME, authTimeout);
+    }
+
+    @Test
+    public void updateState_timeoutSetToZero_preferenceDisplayedEnabled() throws Exception {
+        Settings.Global.putLong(mContentResolver, Settings.Global.ADB_ALLOWED_CONNECTION_TIME, 0);
+
+        mPreferenceController.updateState(mPreference);
+
+        verify(mPreference).setChecked(true);
+    }
+
+    @Test
+    public void updateState_timeoutSetToDefault_preferenceDisplayedDisabled() throws Exception {
+        Settings.Global.putLong(mContentResolver, Settings.Global.ADB_ALLOWED_CONNECTION_TIME,
+                Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME);
+
+        mPreferenceController.updateState(mPreference);
+
+        verify(mPreference).setChecked(false);
+    }
+
+    @Test
+    public void onDeveloperOptionsSwitchDisabled_preferenceAndTimeoutDisabled() throws Exception {
+        mPreferenceController.onDeveloperOptionsSwitchDisabled();
+
+        long authTimeout = Settings.Global.getLong(mContentResolver,
+                Settings.Global.ADB_ALLOWED_CONNECTION_TIME);
+
+        assertEquals(Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME, authTimeout);
+        verify(mPreference).setChecked(false);
+    }
+}
+