Merge changes from topic "bluetooth_uri"

* changes:
  Add feature provider for bluetooth settings
  Build infra to inject slice to PreferenceFragment
This commit is contained in:
Lei Yu
2018-12-19 01:39:43 +00:00
committed by Android (Google) Code Review
18 changed files with 425 additions and 2 deletions

View File

@@ -0,0 +1,29 @@
<?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.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
<androidx.slice.widget.SliceView
android:id="@+id/slice_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</FrameLayout>

View File

@@ -52,6 +52,8 @@
<attr name="apnPreferenceStyle" format="reference" />
<attr name="slicePreferenceStyle" format="reference" />
<attr name="footerPreferenceStyle" format="reference" />
<declare-styleable name="FixedLineSummaryPreference">

View File

@@ -165,4 +165,7 @@
<!-- Email address for the homepage contextual cards feedback -->
<string name="config_contextual_card_feedback_email" translatable="false"></string>
<!-- Uri that represents extra bluetooth settings -->
<string name="config_bluetooth_device_settings_uri" translatable="false">content://com.google.android.gms.nearby.fastpair/settings_slice?addr=<xliff:g id="mac_address">%1$s</xliff:g></string>
</resources>

View File

@@ -489,4 +489,8 @@
<item name="android:textAllCaps">false</item>
</style>
<style name="Widget.SliceView.Settings">
<item name="titleSize">@*android:dimen/text_size_subhead_material</item>
</style>
</resources>

View File

@@ -21,6 +21,7 @@
<style name="PreferenceTheme" parent="@style/PreferenceThemeOverlay.SettingsBase">
<item name="apnPreferenceStyle">@style/ApnPreference</item>
<item name="slicePreferenceStyle">@style/SlicePreference</item>
<item name="seekBarPreferenceStyle">@style/SettingsSeekBarPreference</item>
<item name="twoStateButtonPreferenceStyle">@style/TwoStateButtonPreference</item>
</style>
@@ -34,6 +35,10 @@
<item name="android:layout">@layout/apn_preference_layout</item>
</style>
<style name="SlicePreference" parent="@style/Preference.Material">
<item name="android:layout">@layout/slice_preference_layout</item>
</style>
<style name="SettingsSeekBarPreference" parent="@style/Preference.Material">
<item name="android:layout">@layout/preference_widget_seekbar_settings</item>
</style>

View File

@@ -62,6 +62,9 @@
<!-- TODO(118444000): Remove colorPrimary and colorPrimaryVariant -->
<item name="colorPrimary">@*android:color/primary_device_default_settings_light</item>
<item name="colorPrimaryVariant">@android:color/white</item>
<!-- For slice view in settings -->
<item name="sliceViewStyle">@style/Widget.SliceView.Settings</item>
</style>
<!-- Variant of the settings theme with no action bar. -->

View File

@@ -26,7 +26,14 @@
settings:allowDividerBelow="true"/>
<com.android.settingslib.widget.ActionButtonsPreference
android:key="action_buttons" />
android:key="action_buttons"
settings:allowDividerBelow="true"/>
<com.android.settings.slices.SlicePreference
android:key="bt_device_slice"
settings:controller="com.android.settings.slices.SlicePreferenceController"
settings:allowDividerBelow="true"
settings:allowDividerAbove="true"/>
<PreferenceCategory
android:key="bluetooth_profiles"/>

View File

@@ -32,7 +32,6 @@ import com.android.settings.display.TapToWakePreferenceController;
import com.android.settings.display.ThemePreferenceController;
import com.android.settings.display.TimeoutPreferenceController;
import com.android.settings.display.VrDisplayPreferenceController;
import com.android.settings.display.WallpaperPreferenceController;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settingslib.core.AbstractPreferenceController;

View File

@@ -21,12 +21,16 @@ import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.os.Bundle;
import android.util.FeatureFlagUtils;
import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.core.FeatureFlags;
import com.android.settings.dashboard.RestrictedDashboardFragment;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.slices.SlicePreferenceController;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.core.AbstractPreferenceController;
@@ -98,6 +102,13 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
mManager = getLocalBluetoothManager(context);
mCachedDevice = getCachedDevice(mDeviceAddress);
super.onAttach(context);
if (FeatureFlagUtils.isEnabled(context, FeatureFlags.SLICE_INJECTION)) {
final BluetoothFeatureProvider featureProvider = FeatureFactory.getFactory(context)
.getBluetoothFeatureProvider(context);
use(SlicePreferenceController.class).setSliceUri(
featureProvider.getBluetoothDeviceSettingsUri(mDeviceAddress));
}
}
@Override

View File

@@ -0,0 +1,32 @@
/*
* 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.bluetooth;
import android.net.Uri;
/**
* Provider for bluetooth related feature
*/
public interface BluetoothFeatureProvider {
/**
* Get the {@link Uri} that represents extra settings for a specific bluetooth device
* @param macAddress Bluetooth mac address
* @return {@link Uri} for extra settings
*/
Uri getBluetoothDeviceSettingsUri(String macAddress);
}

View File

@@ -0,0 +1,41 @@
/*
* 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.bluetooth;
import android.content.Context;
import android.net.Uri;
import com.android.settings.R;
/**
* Impl of {@link BluetoothFeatureProvider}
*/
public class BluetoothFeatureProviderImpl implements BluetoothFeatureProvider {
private Context mContext;
public BluetoothFeatureProviderImpl(Context context) {
mContext = context;
}
@Override
public Uri getBluetoothDeviceSettingsUri(String macAddress) {
final String uriString = mContext.getString(R.string.config_bluetooth_device_settings_uri,
macAddress);
return Uri.parse(uriString);
}
}

View File

@@ -24,6 +24,7 @@ import com.android.settings.R;
import com.android.settings.accounts.AccountFeatureProvider;
import com.android.settings.applications.ApplicationFeatureProvider;
import com.android.settings.biometrics.face.FaceFeatureProvider;
import com.android.settings.bluetooth.BluetoothFeatureProvider;
import com.android.settings.dashboard.DashboardFeatureProvider;
import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider;
@@ -114,6 +115,8 @@ public abstract class FeatureFactory {
public abstract FaceFeatureProvider getFaceFeatureProvider();
public abstract BluetoothFeatureProvider getBluetoothFeatureProvider(Context context);
public static final class FactoryNotFoundException extends RuntimeException {
public FactoryNotFoundException(Throwable throwable) {
super("Unable to create factory. Did you misconfigure Proguard?", throwable);

View File

@@ -30,6 +30,8 @@ import com.android.settings.applications.ApplicationFeatureProvider;
import com.android.settings.applications.ApplicationFeatureProviderImpl;
import com.android.settings.biometrics.face.FaceFeatureProvider;
import com.android.settings.biometrics.face.FaceFeatureProviderImpl;
import com.android.settings.bluetooth.BluetoothFeatureProvider;
import com.android.settings.bluetooth.BluetoothFeatureProviderImpl;
import com.android.settings.connecteddevice.dock.DockUpdaterFeatureProviderImpl;
import com.android.settings.core.instrumentation.SettingsMetricsFeatureProvider;
import com.android.settings.dashboard.DashboardFeatureProvider;
@@ -81,6 +83,7 @@ public class FeatureFactoryImpl extends FeatureFactory {
private PanelFeatureProvider mPanelFeatureProvider;
private ContextualCardFeatureProvider mContextualCardFeatureProvider;
private FaceFeatureProvider mFaceFeatureProvider;
private BluetoothFeatureProvider mBluetoothFeatureProvider;
@Override
public SupportFeatureProvider getSupportFeatureProvider(Context context) {
@@ -242,4 +245,13 @@ public class FeatureFactoryImpl extends FeatureFactory {
}
return mFaceFeatureProvider;
}
@Override
public BluetoothFeatureProvider getBluetoothFeatureProvider(Context context) {
if (mBluetoothFeatureProvider == null) {
mBluetoothFeatureProvider = new BluetoothFeatureProviderImpl(
context.getApplicationContext());
}
return mBluetoothFeatureProvider;
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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.slices;
import android.content.Context;
import android.util.AttributeSet;
import androidx.slice.Slice;
import androidx.slice.widget.SliceView;
import com.android.settings.R;
import com.android.settingslib.widget.LayoutPreference;
/**
* Preference for {@link SliceView}
*/
public class SlicePreference extends LayoutPreference {
private SliceView mSliceView;
public SlicePreference(Context context, AttributeSet attrs) {
super(context, attrs, R.attr.slicePreferenceStyle);
mSliceView = findViewById(R.id.slice_view);
}
public SlicePreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, R.attr.slicePreferenceStyle);
mSliceView = findViewById(R.id.slice_view);
}
public void onSliceUpdated(Slice slice) {
mSliceView.onChanged(slice);
notifyChanged();
}
}

View File

@@ -0,0 +1,89 @@
/*
* 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.slices;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.preference.PreferenceScreen;
import androidx.slice.Slice;
import androidx.slice.widget.SliceLiveData;
import androidx.slice.widget.SliceView;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
/**
* Default {@link BasePreferenceController} for {@link SliceView}. It will take {@link Uri} for
* Slice and display what's inside this {@link Uri}
*/
public class SlicePreferenceController extends BasePreferenceController implements
LifecycleObserver, OnStart, OnStop, Observer<Slice> {
@VisibleForTesting
LiveData<Slice> mLiveData;
private SlicePreference mSlicePreference;
private Uri mUri;
public SlicePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mSlicePreference = (SlicePreference) screen.findPreference(
getPreferenceKey());
}
@Override
public int getAvailabilityStatus() {
return mUri != null ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
public void setSliceUri(Uri uri) {
mUri = uri;
mLiveData = SliceLiveData.fromUri(mContext, mUri);
//TODO(b/120803703): figure out why we need to remove observer first
mLiveData.removeObserver(this);
}
@Override
public void onStart() {
if (mLiveData != null) {
mLiveData.observeForever(this);
}
}
@Override
public void onStop() {
if (mLiveData != null) {
mLiveData.removeObserver(this);
}
}
@Override
public void onChanged(Slice slice) {
mSlicePreference.onSliceUpdated(slice);
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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.bluetooth;
import static com.google.common.truth.Truth.assertThat;
import android.net.Uri;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public class BluetoothFeatureProviderImplTest {
private static final String PARAMETER_KEY = "addr";
private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C";
private BluetoothFeatureProvider mBluetoothFeatureProvider;
@Before
public void setUp() {
mBluetoothFeatureProvider = new BluetoothFeatureProviderImpl(
RuntimeEnvironment.application);
}
@Test
public void getBluetoothDeviceSettingsUri_containCorrectMacAddress() {
final Uri uri = mBluetoothFeatureProvider.getBluetoothDeviceSettingsUri(MAC_ADDRESS);
assertThat(uri.getQueryParameterNames()).containsExactly(PARAMETER_KEY);
assertThat(uri.getQueryParameter(PARAMETER_KEY)).isEqualTo(MAC_ADDRESS);
}
}

View File

@@ -0,0 +1,81 @@
/*
* 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.slices;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.net.Uri;
import androidx.lifecycle.LiveData;
import androidx.slice.Slice;
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 SlicePreferenceControllerTest {
private static final String KEY = "slice_preference_key";
@Mock
private LiveData<Slice> mLiveData;
private Context mContext;
private SlicePreferenceController mController;
private Uri mUri;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
mController = new SlicePreferenceController(mContext, KEY);
mController.mLiveData = mLiveData;
mUri = Uri.EMPTY;
}
@Test
public void isAvailable_uriNull_returnFalse() {
assertThat(mController.isAvailable()).isFalse();
}
@Test
public void isAvailable_uriNotNull_returnTrue() {
mController.setSliceUri(mUri);
assertThat(mController.isAvailable()).isTrue();
}
@Test
public void onStart_registerObserver() {
mController.onStart();
verify(mLiveData).observeForever(mController);
}
@Test
public void onStop_unregisterObserver() {
mController.onStop();
verify(mLiveData).removeObserver(mController);
}
}

View File

@@ -24,6 +24,7 @@ import android.content.Context;
import com.android.settings.accounts.AccountFeatureProvider;
import com.android.settings.applications.ApplicationFeatureProvider;
import com.android.settings.biometrics.face.FaceFeatureProvider;
import com.android.settings.bluetooth.BluetoothFeatureProvider;
import com.android.settings.dashboard.DashboardFeatureProvider;
import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider;
@@ -66,6 +67,7 @@ public class FakeFeatureFactory extends FeatureFactory {
public final AccountFeatureProvider mAccountFeatureProvider;
public final ContextualCardFeatureProvider mContextualCardFeatureProvider;
public final FaceFeatureProvider mFaceFeatureProvider;
public final BluetoothFeatureProvider mBluetoothFeatureProvider;
public PanelFeatureProvider panelFeatureProvider;
public SlicesFeatureProvider slicesFeatureProvider;
@@ -111,6 +113,7 @@ public class FakeFeatureFactory extends FeatureFactory {
mContextualCardFeatureProvider = mock(ContextualCardFeatureProvider.class);
panelFeatureProvider = mock(PanelFeatureProvider.class);
mFaceFeatureProvider = mock(FaceFeatureProvider.class);
mBluetoothFeatureProvider = mock(BluetoothFeatureProvider.class);
}
@Override
@@ -207,4 +210,9 @@ public class FakeFeatureFactory extends FeatureFactory {
public FaceFeatureProvider getFaceFeatureProvider() {
return mFaceFeatureProvider;
}
@Override
public BluetoothFeatureProvider getBluetoothFeatureProvider(Context context) {
return mBluetoothFeatureProvider;
}
}