From a5672945ae70cd1c06ebf960297c78818532859d Mon Sep 17 00:00:00 2001 From: Joy Babafemi Date: Mon, 19 Apr 2021 23:29:12 +0000 Subject: [PATCH] Add UWB Settings Screenshot 1: https://screenshot.googleplex.com/5wjKkioeehZnz74 Screenshot 2: https://screenshot.googleplex.com/AtaufsUgLgVk2Ur Test: Robotest Bug: 183254940 Change-Id: Ibd2e3f3ad9596dddbdd23d7f2ad5e03768648faf --- res/values/strings.xml | 9 + res/xml/connected_devices_advanced.xml | 7 + .../settings/uwb/UwbPreferenceController.java | 168 +++++++++++++++++ .../uwb/UwbPreferenceControllerTest.java | 176 ++++++++++++++++++ 4 files changed, 360 insertions(+) create mode 100644 src/com/android/settings/uwb/UwbPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/uwb/UwbPreferenceControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index ebe641e4657..129a02b3862 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -13186,4 +13186,13 @@ Don\u2019t allow + + + Ultra-WideBand (UWB) + + + Helps identify the relative position of nearby devices that have UWB + + + Turn off Airplane mode to use UWB. diff --git a/res/xml/connected_devices_advanced.xml b/res/xml/connected_devices_advanced.xml index 3ff7d9975f8..85e4a762429 100644 --- a/res/xml/connected_devices_advanced.xml +++ b/res/xml/connected_devices_advanced.xml @@ -65,6 +65,13 @@ android:icon="@drawable/ic_folder_vd_theme_24" android:title="@string/bluetooth_show_files_received_via_bluetooth"/> + + diff --git a/src/com/android/settings/uwb/UwbPreferenceController.java b/src/com/android/settings/uwb/UwbPreferenceController.java new file mode 100644 index 00000000000..daadb63b8dd --- /dev/null +++ b/src/com/android/settings/uwb/UwbPreferenceController.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2021 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.uwb; + +import static androidx.lifecycle.Lifecycle.Event.ON_START; +import static androidx.lifecycle.Lifecycle.Event.ON_STOP; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.provider.Settings; +import android.uwb.UwbManager; +import android.uwb.UwbManager.AdapterStateCallback; + +import androidx.lifecycle.LifecycleObserver; +import androidx.lifecycle.OnLifecycleEvent; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.R; +import com.android.settings.core.TogglePreferenceController; + +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +/** Controller for "UWB" toggle. */ +public class UwbPreferenceController extends TogglePreferenceController implements + AdapterStateCallback, LifecycleObserver { + @VisibleForTesting + static final String KEY_UWB_SETTINGS = "uwb_settings"; + @VisibleForTesting + UwbManager mUwbManager; + @VisibleForTesting + boolean mAirplaneModeOn; + @VisibleForTesting + private final BroadcastReceiver mAirplaneModeChangedReceiver; + private final Executor mExecutor; + private boolean mIsChecked = true; + boolean mRegisteredAdapterStateCallback = false; + private Preference mPreference; + + public UwbPreferenceController(Context context, String key) { + super(context, key); + mExecutor = Executors.newSingleThreadExecutor(); + mUwbManager = context.getSystemService(UwbManager.class); + mAirplaneModeOn = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0) == 1; + mAirplaneModeChangedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + updateState(mPreference); + } + }; + } + + @VisibleForTesting + boolean isUwbSupportedOnDevice() { + return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB); + } + + @Override + public int getAvailabilityStatus() { + if (!isUwbSupportedOnDevice()) { + return UNSUPPORTED_ON_DEVICE; + } else if (mAirplaneModeOn) { + return DISABLED_DEPENDENT_SETTING; + } else { + return AVAILABLE; + } + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + } + + @Override + public boolean isChecked() { + //TODO(b/186075119): Update toggle state by assigning to the real value by default. + return mIsChecked; + } + + @Override + public boolean setChecked(boolean isChecked) { + mIsChecked = isChecked; + mUwbManager.setUwbEnabled(isChecked); + return true; + } + + @Override + public void onStateChanged(int state, int reason) { + // Only update toggle state from service the first time. Otherwise toggle state is + // changed from controller. For example, UWB is disabled if airplane mode is on but we do + // not want to change the preference for the user in this case. + if (!mRegisteredAdapterStateCallback) { + mIsChecked = state == STATE_ENABLED_ACTIVE || state == STATE_ENABLED_INACTIVE; + mRegisteredAdapterStateCallback = true; + } + } + + /** Called when activity starts being displayed to user. */ + @OnLifecycleEvent(ON_START) + public void onStart() { + if (isUwbSupportedOnDevice()) { + mUwbManager.registerAdapterStateCallback(mExecutor, this); + } + if (mAirplaneModeChangedReceiver != null) { + mContext.registerReceiver(mAirplaneModeChangedReceiver, + new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED)); + } + refreshSummary(mPreference); + } + + /** Called when activity stops being displayed to user. */ + @OnLifecycleEvent(ON_STOP) + public void onStop() { + if (isUwbSupportedOnDevice()) { + mUwbManager.unregisterAdapterStateCallback(this); + } + if (mAirplaneModeChangedReceiver != null) { + mContext.unregisterReceiver(mAirplaneModeChangedReceiver); + } + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + mAirplaneModeOn = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0) == 1; + preference.setEnabled(!mAirplaneModeOn); + if (isUwbSupportedOnDevice()) { + if (mAirplaneModeOn) { + mUwbManager.setUwbEnabled(false); + } else { + mUwbManager.setUwbEnabled(mIsChecked); + } + } + refreshSummary(preference); + } + + @Override + public CharSequence getSummary() { + if (mAirplaneModeOn) { + return mContext.getResources().getString(R.string.uwb_settings_summary_airplane_mode); + } else { + return mContext.getResources().getString(R.string.uwb_settings_summary); + } + } +} + diff --git a/tests/robotests/src/com/android/settings/uwb/UwbPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/uwb/UwbPreferenceControllerTest.java new file mode 100644 index 00000000000..00dc4dff4ae --- /dev/null +++ b/tests/robotests/src/com/android/settings/uwb/UwbPreferenceControllerTest.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2021 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.uwb; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.uwb.UwbManager; + +import com.android.settings.core.BasePreferenceController; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +/** Unit tests for UWB preference toggle. */ +@RunWith(RobolectricTestRunner.class) +public class UwbPreferenceControllerTest { + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + private Context mContext; + private PackageManager mPackageManager; + private UwbPreferenceController mController; + + @Mock + private UwbManager mUwbManager; + + @Before + public void setUp() { + mContext = spy(RuntimeEnvironment.application); + mPackageManager = spy(mContext.getPackageManager()); + mController = new UwbPreferenceController(mContext, "uwb_settings"); + mController.mUwbManager = mUwbManager; + } + + @Test + public void getAvailabilityStatus_uwbDisabled_shouldReturnDisabled() { + doReturn(mPackageManager).when(mContext).getPackageManager(); + doReturn(true).when(mPackageManager) + .hasSystemFeature(PackageManager.FEATURE_UWB); + mController.mAirplaneModeOn = true; + + assertThat(mController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.DISABLED_DEPENDENT_SETTING); + } + + @Test + public void getAvailabilityStatus_uwbShown_shouldReturnAvailable() { + doReturn(mPackageManager).when(mContext).getPackageManager(); + doReturn(true).when(mPackageManager) + .hasSystemFeature(PackageManager.FEATURE_UWB); + mController.mAirplaneModeOn = false; + + assertThat(mController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.AVAILABLE); + } + + @Test + public void getAvailabilityStatus_uwbNotShown_shouldReturnUnsupported() { + doReturn(mPackageManager).when(mContext).getPackageManager(); + doReturn(false).when(mPackageManager) + .hasSystemFeature(PackageManager.FEATURE_UWB); + + assertThat(mController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.UNSUPPORTED_ON_DEVICE); + } + + @Test + public void onStateChanged_stateNotRegistered_shouldUpdate() { + mController.mRegisteredAdapterStateCallback = false; + mController.onStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, + UwbManager.AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY); + + assertThat(mController.isChecked()).isFalse(); + } + + @Test + public void onStateChanged_stateRegistered_shouldNotUpdate() { + mController.mRegisteredAdapterStateCallback = true; + mController.onStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE, + UwbManager.AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY); + + assertThat(mController.isChecked()).isTrue(); + } + + @Test + public void isChecked_uwbEnabledInactive_shouldReturnTrue() { + doReturn(mPackageManager).when(mContext).getPackageManager(); + doReturn(true).when(mPackageManager) + .hasSystemFeature(PackageManager.FEATURE_UWB); + mController.mRegisteredAdapterStateCallback = false; + mController.onStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE, + UwbManager.AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY); + + assertThat(mController.isChecked()).isTrue(); + } + + @Test + public void isChecked_uwbEnabledActive_shouldReturnTrue() { + doReturn(mPackageManager).when(mContext).getPackageManager(); + doReturn(true).when(mPackageManager) + .hasSystemFeature(PackageManager.FEATURE_UWB); + mController.mRegisteredAdapterStateCallback = false; + mController.onStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_ACTIVE, + UwbManager.AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY); + + assertThat(mController.isChecked()).isTrue(); + } + + @Test + public void isChecked_uwbDisabled_shouldReturnFalse() { + doReturn(mPackageManager).when(mContext).getPackageManager(); + doReturn(true).when(mPackageManager) + .hasSystemFeature(PackageManager.FEATURE_UWB); + mController.mRegisteredAdapterStateCallback = false; + mController.onStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, + UwbManager.AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY); + + assertThat(mController.isChecked()).isFalse(); + } + + @Test + public void setChecked_uwbDisabled_shouldEnableUwb() { + clearInvocations(mUwbManager); + doReturn(mPackageManager).when(mContext).getPackageManager(); + doReturn(true).when(mPackageManager) + .hasSystemFeature(PackageManager.FEATURE_UWB); + + mController.setChecked(true); + + verify(mUwbManager).setUwbEnabled(true); + verify(mUwbManager, never()).setUwbEnabled(false); + } + + @Test + public void setChecked_uwbEnabled_shouldDisableUwb() { + clearInvocations(mUwbManager); + doReturn(mPackageManager).when(mContext).getPackageManager(); + doReturn(true).when(mPackageManager) + .hasSystemFeature(PackageManager.FEATURE_UWB); + + mController.setChecked(false); + + verify(mUwbManager).setUwbEnabled(false); + verify(mUwbManager, never()).setUwbEnabled(true); + } +} +