Build infra to inject slice to PreferenceFragment

Reuse the PreferenceController and LayoutPreference however create
specific one for slice:
1. SlicePreference: container to inject slice view
2. SlicePreferenceController: handle updates for slice

Also add styles for it with default layout.

Bug: 120803703
Test: RunSettingsRoboTests

Change-Id: I6ab083ad57117e6198dcba37702a25213da78719
This commit is contained in:
jackqdyulei
2018-12-10 13:27:25 -08:00
parent 3aac42568c
commit cf6374e427
11 changed files with 283 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="apnPreferenceStyle" format="reference" />
<attr name="slicePreferenceStyle" format="reference" />
<attr name="footerPreferenceStyle" format="reference" /> <attr name="footerPreferenceStyle" format="reference" />
<declare-styleable name="FixedLineSummaryPreference"> <declare-styleable name="FixedLineSummaryPreference">

View File

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

View File

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

View File

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

View File

@@ -26,7 +26,14 @@
settings:allowDividerBelow="true"/> settings:allowDividerBelow="true"/>
<com.android.settingslib.widget.ActionButtonsPreference <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 <PreferenceCategory
android:key="bluetooth_profiles"/> 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.ThemePreferenceController;
import com.android.settings.display.TimeoutPreferenceController; import com.android.settings.display.TimeoutPreferenceController;
import com.android.settings.display.VrDisplayPreferenceController; import com.android.settings.display.VrDisplayPreferenceController;
import com.android.settings.display.WallpaperPreferenceController;
import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable; import com.android.settings.search.Indexable;
import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.AbstractPreferenceController;

View File

@@ -19,14 +19,19 @@ package com.android.settings.bluetooth;
import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH; import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.util.FeatureFlagUtils;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.nano.MetricsProto; import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.core.FeatureFlags;
import com.android.settings.dashboard.RestrictedDashboardFragment; import com.android.settings.dashboard.RestrictedDashboardFragment;
import com.android.settings.slices.SlicePreferenceController;
import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.AbstractPreferenceController;
@@ -98,6 +103,15 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
mManager = getLocalBluetoothManager(context); mManager = getLocalBluetoothManager(context);
mCachedDevice = getCachedDevice(mDeviceAddress); mCachedDevice = getCachedDevice(mDeviceAddress);
super.onAttach(context); super.onAttach(context);
if (FeatureFlagUtils.isEnabled(context, FeatureFlags.SLICE_INJECTION)) {
//TODO(b/120803703): update it to get data from feature provider
use(SlicePreferenceController.class).setSliceUri(new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority("com.google.android.apps.wearables.maestro.companion")
.appendPath("setting_slice")
.build());
}
} }
@Override @Override

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,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);
}
}