From 85c34077d3f9fc42dbb98aca68a24f8345434e6c Mon Sep 17 00:00:00 2001 From: Erik Kline Date: Thu, 5 Apr 2018 14:30:56 -0700 Subject: [PATCH] Display Private DNS status in preference summary Also: fix a bug where the actual current mode is not read correctly from settings on initialization. Test: as follows - built, flashed, booted - manual test behaves as expected - make -j50 RunSettingsRoboTests ROBOTEST_FILTER=PrivateDnsModeDialogPreferenceTest passes - make -j50 RunSettingsRoboTests ROBOTEST_FILTER=PrivateDnsPreferenceControllerTest passes Bug: 34953048 Bug: 64133961 Change-Id: I0b845b3894a47b837abf5de273d1ada642ef5a23 --- .../PrivateDnsModeDialogPreference.java | 16 +- .../PrivateDnsPreferenceController.java | 77 +++++++++- .../PrivateDnsModeDialogPreferenceTest.java | 6 +- .../PrivateDnsPreferenceControllerTest.java | 143 ++++++++++++++++++ 4 files changed, 237 insertions(+), 5 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/network/PrivateDnsPreferenceControllerTest.java diff --git a/src/com/android/settings/network/PrivateDnsModeDialogPreference.java b/src/com/android/settings/network/PrivateDnsModeDialogPreference.java index 71a3e0d70a0..178aa27c188 100644 --- a/src/com/android/settings/network/PrivateDnsModeDialogPreference.java +++ b/src/com/android/settings/network/PrivateDnsModeDialogPreference.java @@ -53,7 +53,7 @@ import java.util.HashMap; import java.util.Map; /** - * Dialog to set the private dns + * Dialog to set the Private DNS */ public class PrivateDnsModeDialogPreference extends CustomDialogPreference implements DialogInterface.OnClickListener, RadioGroup.OnCheckedChangeListener, TextWatcher { @@ -78,6 +78,15 @@ public class PrivateDnsModeDialogPreference extends CustomDialogPreference imple @VisibleForTesting static final String HOSTNAME_KEY = Settings.Global.PRIVATE_DNS_SPECIFIER; + public static String getModeFromSettings(ContentResolver cr) { + final String mode = Settings.Global.getString(cr, MODE_KEY); + return PRIVATE_DNS_MAP.containsKey(mode) ? mode : PRIVATE_DNS_MODE_OPPORTUNISTIC; + } + + public static String getHostnameFromSettings(ContentResolver cr) { + return Settings.Global.getString(cr, HOSTNAME_KEY); + } + @VisibleForTesting EditText mEditText; @VisibleForTesting @@ -121,9 +130,12 @@ public class PrivateDnsModeDialogPreference extends CustomDialogPreference imple protected void onBindDialogView(View view) { final Context context = getContext(); final ContentResolver contentResolver = context.getContentResolver(); + + mMode = getModeFromSettings(context.getContentResolver()); + mEditText = view.findViewById(R.id.private_dns_mode_provider_hostname); mEditText.addTextChangedListener(this); - mEditText.setText(Settings.Global.getString(contentResolver, HOSTNAME_KEY)); + mEditText.setText(getHostnameFromSettings(contentResolver)); mRadioGroup = view.findViewById(R.id.private_dns_radio_group); mRadioGroup.setOnCheckedChangeListener(this); diff --git a/src/com/android/settings/network/PrivateDnsPreferenceController.java b/src/com/android/settings/network/PrivateDnsPreferenceController.java index e3175300792..50224caba1a 100644 --- a/src/com/android/settings/network/PrivateDnsPreferenceController.java +++ b/src/com/android/settings/network/PrivateDnsPreferenceController.java @@ -16,19 +16,46 @@ package com.android.settings.network; +import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF; +import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; +import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; + import android.content.Context; +import android.content.ContentResolver; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.core.BasePreferenceController; import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; import com.android.settingslib.core.lifecycle.LifecycleObserver; + public class PrivateDnsPreferenceController extends BasePreferenceController - implements PreferenceControllerMixin, LifecycleObserver { + implements PreferenceControllerMixin, LifecycleObserver, OnStart, OnStop { private static final String KEY_PRIVATE_DNS_SETTINGS = "private_dns_settings"; + private static final Uri[] SETTINGS_URIS = new Uri[]{ + Settings.Global.getUriFor(Settings.Global.PRIVATE_DNS_MODE), + Settings.Global.getUriFor(Settings.Global.PRIVATE_DNS_SPECIFIER), + }; + + private final Handler mHandler; + private final ContentObserver mSettingsObserver; + private Preference mPreference; + public PrivateDnsPreferenceController(Context context) { super(context, KEY_PRIVATE_DNS_SETTINGS); + mHandler = new Handler(Looper.getMainLooper()); + mSettingsObserver = new PrivateDnsSettingsObserver(mHandler); } @Override @@ -40,4 +67,52 @@ public class PrivateDnsPreferenceController extends BasePreferenceController public int getAvailabilityStatus() { return AVAILABLE; } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + mPreference = screen.findPreference(getPreferenceKey()); + } + + @Override + public void onStart() { + for (Uri uri : SETTINGS_URIS) { + mContext.getContentResolver().registerContentObserver(uri, false, mSettingsObserver); + } + } + + @Override + public void onStop() { + mContext.getContentResolver().unregisterContentObserver(mSettingsObserver); + } + + @Override + public CharSequence getSummary() { + final Resources res = mContext.getResources(); + final ContentResolver cr = mContext.getContentResolver(); + final String mode = PrivateDnsModeDialogPreference.getModeFromSettings(cr); + switch (mode) { + case PRIVATE_DNS_MODE_OFF: + return res.getString(R.string.private_dns_mode_off); + case PRIVATE_DNS_MODE_OPPORTUNISTIC: + return res.getString(R.string.private_dns_mode_opportunistic); + case PRIVATE_DNS_MODE_PROVIDER_HOSTNAME: + return PrivateDnsModeDialogPreference.getHostnameFromSettings(cr); + } + return ""; + } + + private class PrivateDnsSettingsObserver extends ContentObserver { + public PrivateDnsSettingsObserver(Handler h) { + super(h); + } + + @Override + public void onChange(boolean selfChange) { + if (mPreference != null) { + PrivateDnsPreferenceController.this.updateState(mPreference); + } + } + } } diff --git a/tests/robotests/src/com/android/settings/network/PrivateDnsModeDialogPreferenceTest.java b/tests/robotests/src/com/android/settings/network/PrivateDnsModeDialogPreferenceTest.java index c5521be2d4e..e9ffa8ac4c6 100644 --- a/tests/robotests/src/com/android/settings/network/PrivateDnsModeDialogPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/network/PrivateDnsModeDialogPreferenceTest.java @@ -111,8 +111,10 @@ public class PrivateDnsModeDialogPreferenceTest { @Test public void testOnBindDialogView_containsCorrectData() { + // Don't set settings to the default value ("opportunistic") as that + // risks masking failure to read the mode from settings. Settings.Global.putString(mContext.getContentResolver(), - PrivateDnsModeDialogPreference.MODE_KEY, PRIVATE_DNS_MODE_OPPORTUNISTIC); + PrivateDnsModeDialogPreference.MODE_KEY, PRIVATE_DNS_MODE_OFF); Settings.Global.putString(mContext.getContentResolver(), PrivateDnsModeDialogPreference.HOSTNAME_KEY, HOST_NAME); @@ -123,7 +125,7 @@ public class PrivateDnsModeDialogPreferenceTest { assertThat(mPreference.mEditText.getText().toString()).isEqualTo(HOST_NAME); assertThat(mPreference.mRadioGroup.getCheckedRadioButtonId()).isEqualTo( - R.id.private_dns_mode_opportunistic); + R.id.private_dns_mode_off); } @Test diff --git a/tests/robotests/src/com/android/settings/network/PrivateDnsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/PrivateDnsPreferenceControllerTest.java new file mode 100644 index 00000000000..83d4bd530b7 --- /dev/null +++ b/tests/robotests/src/com/android/settings/network/PrivateDnsPreferenceControllerTest.java @@ -0,0 +1,143 @@ +/* + * 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.settings.network; + +import static android.arch.lifecycle.Lifecycle.Event.ON_START; +import static android.arch.lifecycle.Lifecycle.Event.ON_STOP; +import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF; +import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; +import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; +import static android.provider.Settings.Global.PRIVATE_DNS_MODE; +import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.arch.lifecycle.LifecycleOwner; +import android.content.Context; +import android.content.ContentResolver; +import android.database.ContentObserver; +import android.provider.Settings; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.shadow.api.Shadow; +import org.robolectric.shadows.ShadowContentResolver; + + +@RunWith(SettingsRobolectricTestRunner.class) +public class PrivateDnsPreferenceControllerTest { + + private final static String HOSTNAME = "dns.example.com"; + + @Mock + private PreferenceScreen mScreen; + @Mock + private Preference mPreference; + private PrivateDnsPreferenceController mController; + private Context mContext; + private ContentResolver mContentResolver; + private ShadowContentResolver mShadowContentResolver; + private Lifecycle mLifecycle; + private LifecycleOwner mLifecycleOwner; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + mContentResolver = mContext.getContentResolver(); + mShadowContentResolver = Shadow.extract(mContentResolver); + + when(mScreen.findPreference(anyString())).thenReturn(mPreference); + + mController = spy(new PrivateDnsPreferenceController(mContext)); + mLifecycleOwner = () -> mLifecycle; + mLifecycle = new Lifecycle(mLifecycleOwner); + mLifecycle.addObserver(mController); + } + + @Test + public void goThroughLifecycle_shouldRegisterUnregisterSettingsObserver() { + mLifecycle.handleLifecycleEvent(ON_START); + verify(mContext, atLeastOnce()).getContentResolver(); + assertThat(mShadowContentResolver.getContentObservers( + Settings.Global.getUriFor(PRIVATE_DNS_MODE))).isNotEmpty(); + assertThat(mShadowContentResolver.getContentObservers( + Settings.Global.getUriFor(PRIVATE_DNS_SPECIFIER))).isNotEmpty(); + + + mLifecycle.handleLifecycleEvent(ON_STOP); + verify(mContext, atLeastOnce()).getContentResolver(); + assertThat(mShadowContentResolver.getContentObservers( + Settings.Global.getUriFor(PRIVATE_DNS_MODE))).isEmpty(); + assertThat(mShadowContentResolver.getContentObservers( + Settings.Global.getUriFor(PRIVATE_DNS_SPECIFIER))).isEmpty(); + } + + @Test + public void getSummary_PrivateDnsModeOff() { + setPrivateDnsMode(PRIVATE_DNS_MODE_OFF); + setPrivateDnsProviderHostname(HOSTNAME); + mController.updateState(mPreference); + verify(mController, atLeastOnce()).getSummary(); + verify(mPreference).setSummary(getResourceString(R.string.private_dns_mode_off)); + } + + @Test + public void getSummary_PrivateDnsModeOpportunistic() { + setPrivateDnsMode(PRIVATE_DNS_MODE_OPPORTUNISTIC); + setPrivateDnsProviderHostname(HOSTNAME); + mController.updateState(mPreference); + verify(mController, atLeastOnce()).getSummary(); + verify(mPreference).setSummary(getResourceString(R.string.private_dns_mode_opportunistic)); + } + + @Test + public void getSummary_PrivateDnsModeProviderHostname() { + setPrivateDnsMode(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME); + setPrivateDnsProviderHostname(HOSTNAME); + mController.updateState(mPreference); + verify(mController, atLeastOnce()).getSummary(); + verify(mPreference).setSummary(HOSTNAME); + } + + private void setPrivateDnsMode(String mode) { + Settings.Global.putString(mContentResolver, PRIVATE_DNS_MODE, mode); + } + + private void setPrivateDnsProviderHostname(String name) { + Settings.Global.putString(mContentResolver, PRIVATE_DNS_SPECIFIER, name); + } + + private String getResourceString(int which) { + return mContext.getResources().getString(which); + } +}