diff --git a/src/com/android/settings/core/InstrumentedPreferenceFragment.java b/src/com/android/settings/core/InstrumentedPreferenceFragment.java index 1724d0ce471..f651056ad90 100644 --- a/src/com/android/settings/core/InstrumentedPreferenceFragment.java +++ b/src/com/android/settings/core/InstrumentedPreferenceFragment.java @@ -17,13 +17,20 @@ package com.android.settings.core; import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; import com.android.settings.core.instrumentation.Instrumentable; import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.core.instrumentation.VisibilityLoggerMixin; import com.android.settings.core.lifecycle.ObservablePreferenceFragment; import com.android.settings.overlay.FeatureFactory; +import com.android.settings.widget.PreferenceDividerDecoration; /** * Instrumented fragment that logs visibility state. @@ -48,6 +55,9 @@ public abstract class InstrumentedPreferenceFragment extends ObservablePreferenc protected final int GESTURE_DOUBLE_TAP_SCREEN = PLACEHOLDER_METRIC + 11; protected final int GESTURE_DOUBLE_TWIST = PLACEHOLDER_METRIC + 12; + private final PreferenceDividerDecoration mDividerDecoration = + new PreferenceDividerDecoration(); + public InstrumentedPreferenceFragment() { // Mixin that logs visibility change for activity. getLifecycle().addObserver(new VisibilityLoggerMixin(getMetricsCategory())); @@ -59,7 +69,21 @@ public abstract class InstrumentedPreferenceFragment extends ObservablePreferenc mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); } + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + final View view = super.onCreateView(inflater, container, savedInstanceState); + getListView().addItemDecoration(mDividerDecoration); + return view; + } + @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { } + + @Override + public void setDivider(Drawable divider) { + mDividerDecoration.setDivider(divider); + super.setDivider(new ColorDrawable(Color.TRANSPARENT)); + } } diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java index e59b7baadbe..ab1ec13d50a 100644 --- a/src/com/android/settings/dashboard/DashboardFragment.java +++ b/src/com/android/settings/dashboard/DashboardFragment.java @@ -17,9 +17,6 @@ package com.android.settings.dashboard; import android.app.Activity; import android.content.Context; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.annotation.VisibleForTesting; import android.support.v7.preference.Preference; @@ -117,16 +114,6 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment refreshAllPreferences(getLogTag()); } - @Override - public void setDivider(Drawable divider) { - if (mDashboardFeatureProvider.isEnabled()) { - // Intercept divider and set it transparent so system divider decoration is disabled. - super.setDivider(new ColorDrawable(Color.TRANSPARENT)); - } else { - super.setDivider(divider); - } - } - @Override public void onStart() { super.onStart(); diff --git a/src/com/android/settings/widget/PreferenceDividerDecoration.java b/src/com/android/settings/widget/PreferenceDividerDecoration.java new file mode 100644 index 00000000000..4466a3dc3b8 --- /dev/null +++ b/src/com/android/settings/widget/PreferenceDividerDecoration.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2016 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.widget; + +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.support.v4.view.ViewCompat; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceCategory; +import android.support.v7.preference.PreferenceGroupAdapter; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +public class PreferenceDividerDecoration extends RecyclerView.ItemDecoration { + + private Drawable mDivider; + private int mDividerHeight; + + public void setDivider(Drawable divider) { + if (divider != null) { + mDividerHeight = divider.getIntrinsicHeight(); + } else { + mDividerHeight = 0; + } + mDivider = divider; + } + + public void setDividerHeight(int dividerHeight) { + mDividerHeight = dividerHeight; + } + + @Override + public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { + if (mDivider == null) { + return; + } + final int childCount = parent.getChildCount(); + final int width = parent.getWidth(); + for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) { + final View view = parent.getChildAt(childViewIndex); + if (shouldDrawDividerAbove(view, parent)) { + int top = (int) ViewCompat.getY(view); + mDivider.setBounds(0, top, width, top + mDividerHeight); + mDivider.draw(c); + } + } + } + + private boolean shouldDrawDividerAbove(View view, RecyclerView parent) { + final RecyclerView.Adapter adapter = parent.getAdapter(); + if (adapter == null || !(adapter instanceof PreferenceGroupAdapter)) { + return false; + } + final PreferenceGroupAdapter prefAdapter = (PreferenceGroupAdapter) adapter; + final int adapterPosition = parent.getChildAdapterPosition(view); + if (adapterPosition == RecyclerView.NO_POSITION) { + return false; + } + final Preference pref = prefAdapter.getItem(adapterPosition); + if (pref instanceof PreferenceCategory) { + return adapterPosition != 0; + } + return pref instanceof FooterPreference; + } +} diff --git a/tests/robotests/src/com/android/settings/widget/PreferenceDividerDecorationTest.java b/tests/robotests/src/com/android/settings/widget/PreferenceDividerDecorationTest.java new file mode 100644 index 00000000000..f69de54abb2 --- /dev/null +++ b/tests/robotests/src/com/android/settings/widget/PreferenceDividerDecorationTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2016 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.widget; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceCategory; +import android.support.v7.preference.PreferenceGroupAdapter; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +import com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class PreferenceDividerDecorationTest { + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Context mContext; + @Mock + private Drawable mDrawable; + @Mock + private Canvas mCanvas; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private RecyclerView mRecyclerView; + @Mock + private PreferenceGroupAdapter mAdapter; + @Mock + private PreferenceCategory mPrefCategory; + @Mock + private Preference mNormalPref; + @Mock + private FooterPreference mFooterPref; + private PreferenceDividerDecoration mDecoration; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mDecoration = new PreferenceDividerDecoration(); + mDecoration.setDivider(mDrawable); + mDecoration.setDividerHeight(3); + } + + @Test + public void drawOver_footerPreference_shouldDrawDivider() { + when(mRecyclerView.getAdapter()).thenReturn(mAdapter); + when(mRecyclerView.getChildCount()).thenReturn(1); + when(mAdapter.getItem(anyInt())).thenReturn(mFooterPref); + + mDecoration.onDrawOver(mCanvas, mRecyclerView, null /* state */); + + verify(mDrawable).draw(mCanvas); + } + + @Test + public void drawOver_preferenceCategory_shouldSkipFirst() { + when(mRecyclerView.getAdapter()).thenReturn(mAdapter); + when(mRecyclerView.getChildCount()).thenReturn(3); + when(mAdapter.getItem(anyInt())).thenReturn(mPrefCategory); + when(mRecyclerView.getChildAdapterPosition(any(View.class))) + .thenReturn(0) + .thenReturn(1) + .thenReturn(2); + + mDecoration.onDrawOver(mCanvas, mRecyclerView, null /* state */); + + // 3 prefCategory, should skip first draw + verify(mDrawable, times(2)).draw(mCanvas); + } + + @Test + public void drawOver_preference_doNotDrawDivider() { + when(mRecyclerView.getAdapter()).thenReturn(mAdapter); + when(mRecyclerView.getChildCount()).thenReturn(1); + when(mAdapter.getItem(anyInt())).thenReturn(mNormalPref); + + mDecoration.onDrawOver(mCanvas, mRecyclerView, null /* state */); + + verify(mDrawable, never()).draw(mCanvas); + } +}