1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.widget; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static org.mockito.ArgumentMatchers.any; 22 import static org.mockito.ArgumentMatchers.anyInt; 23 import static org.mockito.ArgumentMatchers.anyString; 24 import static org.mockito.ArgumentMatchers.eq; 25 import static org.mockito.Mockito.mock; 26 import static org.mockito.Mockito.spy; 27 import static org.mockito.Mockito.times; 28 import static org.mockito.Mockito.verify; 29 import static org.mockito.Mockito.verifyNoInteractions; 30 import static org.mockito.Mockito.when; 31 32 import android.content.Context; 33 import android.graphics.drawable.ColorDrawable; 34 import android.os.Bundle; 35 import android.view.View; 36 import android.view.accessibility.AccessibilityManager; 37 38 import androidx.preference.Preference; 39 import androidx.preference.PreferenceCategory; 40 import androidx.preference.PreferenceScreen; 41 import androidx.preference.PreferenceViewHolder; 42 import androidx.recyclerview.widget.RecyclerView; 43 44 import com.android.settings.R; 45 import com.android.settings.SettingsActivity; 46 import com.android.settings.SettingsPreferenceFragment; 47 48 import com.google.android.material.appbar.AppBarLayout; 49 50 import org.junit.Before; 51 import org.junit.Test; 52 import org.junit.runner.RunWith; 53 import org.mockito.Mock; 54 import org.mockito.MockitoAnnotations; 55 import org.robolectric.RobolectricTestRunner; 56 import org.robolectric.RuntimeEnvironment; 57 import org.robolectric.annotation.Config; 58 import org.robolectric.shadow.api.Shadow; 59 import org.robolectric.shadows.ShadowAccessibilityManager; 60 import org.robolectric.util.ReflectionHelpers; 61 62 @RunWith(RobolectricTestRunner.class) 63 @Config(shadows = { 64 com.android.settings.testutils.shadow.ShadowFragment.class, 65 }) 66 public class HighlightablePreferenceGroupAdapterTest { 67 68 private static final String TEST_KEY = "key"; 69 70 @Mock 71 private View mRoot; 72 @Mock 73 private PreferenceCategory mPreferenceCategory; 74 @Mock 75 private SettingsPreferenceFragment mFragment; 76 77 private Context mContext; 78 private HighlightablePreferenceGroupAdapter mAdapter; 79 private PreferenceViewHolder mViewHolder; 80 private Preference mPreference; 81 82 @Before setUp()83 public void setUp() { 84 MockitoAnnotations.initMocks(this); 85 mContext = RuntimeEnvironment.application; 86 mPreference = new Preference(mContext); 87 mPreference.setKey(TEST_KEY); 88 when(mPreferenceCategory.getContext()).thenReturn(mContext); 89 mAdapter = spy(new HighlightablePreferenceGroupAdapter(mPreferenceCategory, TEST_KEY, 90 false /* highlighted*/)); 91 when(mAdapter.getItem(anyInt())).thenReturn(mPreference); 92 mViewHolder = PreferenceViewHolder.createInstanceForTests( 93 View.inflate(mContext, androidx.preference.R.layout.preference, null)); 94 } 95 96 @Test requestHighlight_hasKey_notHighlightedBefore_shouldRequest()97 public void requestHighlight_hasKey_notHighlightedBefore_shouldRequest() { 98 when(mAdapter.getPreferenceAdapterPosition(anyString())).thenReturn(1); 99 mAdapter.requestHighlight(mRoot, mock(RecyclerView.class), mock(AppBarLayout.class)); 100 101 verify(mRoot).postDelayed(any(), 102 eq(HighlightablePreferenceGroupAdapter.DELAY_COLLAPSE_DURATION_MILLIS)); 103 verify(mRoot).postDelayed(any(), 104 eq(HighlightablePreferenceGroupAdapter.DELAY_HIGHLIGHT_DURATION_MILLIS)); 105 } 106 107 @Test requestHighlight_enableTouchExploration_shouldHaveA11yHighlightDelay()108 public void requestHighlight_enableTouchExploration_shouldHaveA11yHighlightDelay() { 109 ShadowAccessibilityManager am = Shadow.extract(AccessibilityManager.getInstance(mContext)); 110 am.setTouchExplorationEnabled(true); 111 when(mAdapter.getPreferenceAdapterPosition(anyString())).thenReturn(1); 112 mAdapter.requestHighlight(mRoot, mock(RecyclerView.class), mock(AppBarLayout.class)); 113 114 // DELAY_HIGHLIGHT_DURATION_MILLIS_A11Y = DELAY_COLLAPSE_DURATION_MILLIS 115 verify(mRoot, times(2)).postDelayed(any(), 116 eq(HighlightablePreferenceGroupAdapter.DELAY_HIGHLIGHT_DURATION_MILLIS_A11Y)); 117 } 118 119 @Test requestHighlight_noKey_highlightedBefore_noRecyclerView_shouldNotRequest()120 public void requestHighlight_noKey_highlightedBefore_noRecyclerView_shouldNotRequest() { 121 ReflectionHelpers.setField(mAdapter, "mHighlightKey", null); 122 ReflectionHelpers.setField(mAdapter, "mHighlightRequested", false); 123 mAdapter.requestHighlight(mRoot, mock(RecyclerView.class), mock(AppBarLayout.class)); 124 125 ReflectionHelpers.setField(mAdapter, "mHighlightKey", TEST_KEY); 126 ReflectionHelpers.setField(mAdapter, "mHighlightRequested", true); 127 mAdapter.requestHighlight(mRoot, mock(RecyclerView.class), mock(AppBarLayout.class)); 128 129 ReflectionHelpers.setField(mAdapter, "mHighlightKey", TEST_KEY); 130 ReflectionHelpers.setField(mAdapter, "mHighlightRequested", false); 131 mAdapter.requestHighlight(mRoot, null /* recyclerView */, mock(AppBarLayout.class)); 132 133 verifyNoInteractions(mRoot); 134 } 135 136 @Test adjustInitialExpandedChildCount_invalidInput_shouldNotAdjust()137 public void adjustInitialExpandedChildCount_invalidInput_shouldNotAdjust() { 138 HighlightablePreferenceGroupAdapter.adjustInitialExpandedChildCount(null /* host */); 139 HighlightablePreferenceGroupAdapter.adjustInitialExpandedChildCount(mFragment); 140 final Bundle args = new Bundle(); 141 when(mFragment.getArguments()).thenReturn(args); 142 HighlightablePreferenceGroupAdapter.adjustInitialExpandedChildCount(mFragment); 143 final PreferenceScreen screen = mock(PreferenceScreen.class); 144 when(mFragment.getArguments()).thenReturn(null); 145 when(mFragment.getPreferenceScreen()).thenReturn(screen); 146 HighlightablePreferenceGroupAdapter.adjustInitialExpandedChildCount(mFragment); 147 verifyNoInteractions(screen); 148 } 149 150 @Test adjustInitialExpandedChildCount_hasHighlightKey_shouldExpandAllChildren()151 public void adjustInitialExpandedChildCount_hasHighlightKey_shouldExpandAllChildren() { 152 final Bundle args = new Bundle(); 153 when(mFragment.getArguments()).thenReturn(args); 154 args.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, "testkey"); 155 final PreferenceScreen screen = mock(PreferenceScreen.class); 156 when(mFragment.getPreferenceScreen()).thenReturn(screen); 157 HighlightablePreferenceGroupAdapter.adjustInitialExpandedChildCount(mFragment); 158 159 verify(screen).setInitialExpandedChildrenCount(Integer.MAX_VALUE); 160 } 161 162 @Test adjustInitialExpandedChildCount_noKeyOrChildCountOverride_shouldDoNothing()163 public void adjustInitialExpandedChildCount_noKeyOrChildCountOverride_shouldDoNothing() { 164 final Bundle args = new Bundle(); 165 when(mFragment.getArguments()).thenReturn(args); 166 when(mFragment.getInitialExpandedChildCount()).thenReturn(-1); 167 final PreferenceScreen screen = mock(PreferenceScreen.class); 168 when(mFragment.getPreferenceScreen()).thenReturn(screen); 169 HighlightablePreferenceGroupAdapter.adjustInitialExpandedChildCount(mFragment); 170 171 verify(mFragment).getInitialExpandedChildCount(); 172 verifyNoInteractions(screen); 173 } 174 175 @Test adjustInitialExpandedChildCount_hasCountOverride_shouldDoNothing()176 public void adjustInitialExpandedChildCount_hasCountOverride_shouldDoNothing() { 177 when(mFragment.getInitialExpandedChildCount()).thenReturn(10); 178 final PreferenceScreen screen = mock(PreferenceScreen.class); 179 when(mFragment.getPreferenceScreen()).thenReturn(screen); 180 HighlightablePreferenceGroupAdapter.adjustInitialExpandedChildCount(mFragment); 181 182 verify(mFragment).getInitialExpandedChildCount(); 183 184 verify(screen).setInitialExpandedChildrenCount(10); 185 } 186 187 @Test updateBackground_notHighlightedRow_shouldNotSetHighlightedTag()188 public void updateBackground_notHighlightedRow_shouldNotSetHighlightedTag() { 189 ReflectionHelpers.setField(mAdapter, "mHighlightPosition", 10); 190 191 mAdapter.updateBackground(mViewHolder, 0); 192 193 assertThat(mViewHolder.itemView.getTag(R.id.preference_highlighted)).isNull(); 194 } 195 196 @Test updateBackground_itemViewIsInvisible_shouldNotSetHighlightedTag()197 public void updateBackground_itemViewIsInvisible_shouldNotSetHighlightedTag() { 198 ReflectionHelpers.setField(mAdapter, "mHighlightPosition", 10); 199 ReflectionHelpers.setField(mViewHolder, "itemView", spy(mViewHolder.itemView)); 200 when(mViewHolder.itemView.isShown()).thenReturn(false); 201 202 mAdapter.updateBackground(mViewHolder, 0); 203 204 assertThat(mViewHolder.itemView.getTag(R.id.preference_highlighted)).isNull(); 205 } 206 207 /** 208 * When background is being updated, we also request the a11y focus on the preference 209 */ 210 @Test updateBackground_shouldRequestAccessibilityFocus()211 public void updateBackground_shouldRequestAccessibilityFocus() { 212 View viewItem = mock(View.class); 213 when(viewItem.isShown()).thenReturn(true); 214 mViewHolder = PreferenceViewHolder.createInstanceForTests(viewItem); 215 ReflectionHelpers.setField(mAdapter, "mHighlightPosition", 10); 216 217 mAdapter.updateBackground(mViewHolder, 10); 218 219 verify(viewItem).requestAccessibilityFocus(); 220 } 221 222 @Test updateBackground_highlight_shouldAnimateBackgroundAndSetHighlightedTag()223 public void updateBackground_highlight_shouldAnimateBackgroundAndSetHighlightedTag() { 224 ReflectionHelpers.setField(mAdapter, "mHighlightPosition", 10); 225 ReflectionHelpers.setField(mViewHolder, "itemView", spy(mViewHolder.itemView)); 226 when(mViewHolder.itemView.isShown()).thenReturn(true); 227 assertThat(mAdapter.mFadeInAnimated).isFalse(); 228 229 mAdapter.updateBackground(mViewHolder, 10); 230 231 assertThat(mAdapter.mFadeInAnimated).isTrue(); 232 assertThat(mViewHolder.itemView.getBackground()).isInstanceOf(ColorDrawable.class); 233 assertThat(mViewHolder.itemView.getTag(R.id.preference_highlighted)).isEqualTo(true); 234 verify(mAdapter).requestRemoveHighlightDelayed(mViewHolder); 235 } 236 237 @Test updateBackground_highlight_itemViewIsInvisible_shouldNotAnimate()238 public void updateBackground_highlight_itemViewIsInvisible_shouldNotAnimate() { 239 ReflectionHelpers.setField(mAdapter, "mHighlightPosition", 10); 240 ReflectionHelpers.setField(mViewHolder, "itemView", spy(mViewHolder.itemView)); 241 when(mViewHolder.itemView.isShown()).thenReturn(false); 242 243 mAdapter.updateBackground(mViewHolder, 10); 244 245 assertThat(mAdapter.mFadeInAnimated).isFalse(); 246 } 247 248 @Test updateBackgroundTwice_highlight_shouldAnimateOnce()249 public void updateBackgroundTwice_highlight_shouldAnimateOnce() { 250 ReflectionHelpers.setField(mAdapter, "mHighlightPosition", 10); 251 ReflectionHelpers.setField(mViewHolder, "itemView", spy(mViewHolder.itemView)); 252 when(mViewHolder.itemView.isShown()).thenReturn(true); 253 assertThat(mAdapter.mFadeInAnimated).isFalse(); 254 mAdapter.updateBackground(mViewHolder, 10); 255 // mFadeInAnimated change from false to true - indicating background change is scheduled 256 // through animation. 257 assertThat(mAdapter.mFadeInAnimated).isTrue(); 258 // remove highlight should be requested. 259 verify(mAdapter).requestRemoveHighlightDelayed(mViewHolder); 260 261 ReflectionHelpers.setField(mAdapter, "mHighlightPosition", 10); 262 mAdapter.updateBackground(mViewHolder, 10); 263 // only sets background color once - if it's animation this would be called many times 264 verify(mViewHolder.itemView).setBackgroundColor(mAdapter.mHighlightColor); 265 // remove highlight should be requested. 266 verify(mAdapter, times(2)).requestRemoveHighlightDelayed(mViewHolder); 267 } 268 269 @Test updateBackground_reuseHighlightedRowForNormalRow_shouldResetBackgroundAndTag()270 public void updateBackground_reuseHighlightedRowForNormalRow_shouldResetBackgroundAndTag() { 271 ReflectionHelpers.setField(mAdapter, "mHighlightPosition", 10); 272 mViewHolder.itemView.setTag(R.id.preference_highlighted, true); 273 274 mAdapter.updateBackground(mViewHolder, 0); 275 276 assertThat(mViewHolder.itemView.getBackground()).isNotInstanceOf(ColorDrawable.class); 277 assertThat(mViewHolder.itemView.getTag(R.id.preference_highlighted)).isEqualTo(false); 278 } 279 } 280