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.panel; 18 19 import static com.android.settings.panel.PanelContent.VIEW_TYPE_SLIDER; 20 import static com.android.settings.panel.PanelSlicesAdapter.MAX_NUM_OF_SLICES; 21 import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI; 22 import static com.android.settings.slices.CustomSliceRegistry.VOLUME_NOTIFICATION_URI; 23 24 import static com.google.common.truth.Truth.assertThat; 25 26 import static org.mockito.ArgumentMatchers.any; 27 import static org.mockito.ArgumentMatchers.eq; 28 import static org.mockito.Mockito.doReturn; 29 import static org.mockito.Mockito.mock; 30 import static org.mockito.Mockito.spy; 31 import static org.mockito.Mockito.verify; 32 import static org.mockito.Mockito.when; 33 34 import android.content.Context; 35 import android.net.Uri; 36 import android.text.TextUtils; 37 import android.view.LayoutInflater; 38 import android.view.View; 39 import android.view.ViewGroup; 40 import android.view.accessibility.AccessibilityNodeInfo; 41 import android.widget.FrameLayout; 42 import android.widget.LinearLayout; 43 44 import androidx.lifecycle.LiveData; 45 import androidx.slice.Slice; 46 import androidx.test.core.app.ApplicationProvider; 47 48 import com.android.settings.R; 49 import com.android.settings.panel.PanelSlicesAdapter.SliceRowViewHolder; 50 import com.android.settings.testutils.FakeFeatureFactory; 51 52 import org.junit.Assert; 53 import org.junit.Before; 54 import org.junit.Rule; 55 import org.junit.Test; 56 import org.junit.runner.RunWith; 57 import org.mockito.ArgumentCaptor; 58 import org.mockito.junit.MockitoJUnit; 59 import org.mockito.junit.MockitoRule; 60 import org.robolectric.Robolectric; 61 import org.robolectric.RobolectricTestRunner; 62 import org.robolectric.android.controller.ActivityController; 63 import org.robolectric.annotation.Config; 64 import org.robolectric.annotation.Implementation; 65 import org.robolectric.annotation.Implements; 66 67 import java.util.LinkedHashMap; 68 import java.util.Map; 69 70 @Deprecated(forRemoval = true) 71 @RunWith(RobolectricTestRunner.class) 72 @Config(shadows = PanelSlicesAdapterTest.ShadowLayoutInflater.class) 73 public class PanelSlicesAdapterTest { 74 @Rule 75 public final MockitoRule mMockitoRule = MockitoJUnit.rule(); 76 77 private static LayoutInflater sLayoutInflater; 78 79 private Context mContext; 80 private PanelFragment mPanelFragment; 81 private PanelFeatureProvider mPanelFeatureProvider; 82 private FakeFeatureFactory mFakeFeatureFactory; 83 private FakePanelContent mFakePanelContent; 84 private Map<Uri, LiveData<Slice>> mData = new LinkedHashMap<>(); 85 86 @Before setUp()87 public void setUp() { 88 mContext = ApplicationProvider.getApplicationContext(); 89 90 mPanelFeatureProvider = spy(new PanelFeatureProviderImpl()); 91 mFakeFeatureFactory = FakeFeatureFactory.setupForTest(); 92 mFakeFeatureFactory.panelFeatureProvider = mPanelFeatureProvider; 93 mFakePanelContent = new FakePanelContent(); 94 doReturn(mFakePanelContent).when(mPanelFeatureProvider).getPanel(any(), any()); 95 96 ActivityController<FakeSettingsPanelActivity> activityController = 97 Robolectric.buildActivity(FakeSettingsPanelActivity.class); 98 activityController.setup(); 99 100 mPanelFragment = 101 spy((PanelFragment) 102 activityController 103 .get() 104 .getSupportFragmentManager() 105 .findFragmentById(R.id.main_content)); 106 } 107 addTestLiveData(Uri uri)108 private void addTestLiveData(Uri uri) { 109 // Create a slice to return for the LiveData 110 final Slice slice = new Slice(); 111 final LiveData<Slice> liveData = mock(LiveData.class); 112 when(liveData.getValue()).thenReturn(slice); 113 mData.put(uri, liveData); 114 } 115 116 /** 117 * Edge case where fragment context is not available. 118 */ 119 @Test withPanelFragmentContextNull_createAdapter_noExceptionThrown()120 public void withPanelFragmentContextNull_createAdapter_noExceptionThrown() { 121 when(mPanelFragment.getContext()).thenReturn(null); 122 123 final PanelSlicesAdapter adapter = spy(new PanelSlicesAdapter(mPanelFragment, mData, 0)); 124 125 Assert.assertNotNull(adapter); 126 } 127 128 /** 129 * ViewHolder should load and set the action label correctly. 130 */ 131 @Test setActionLabel_loadsActionLabel()132 public void setActionLabel_loadsActionLabel() { 133 addTestLiveData(VOLUME_NOTIFICATION_URI); 134 final PanelSlicesAdapter adapter = new PanelSlicesAdapter(mPanelFragment, mData, 0); 135 final ViewGroup view = new FrameLayout(mContext); 136 final SliceRowViewHolder viewHolder = adapter.onCreateViewHolder(view, VIEW_TYPE_SLIDER); 137 138 // now let's see if setActionLabel can load and set the label correctly. 139 LinearLayout llRow = new LinearLayout(mContext); 140 viewHolder.setActionLabel(llRow); 141 142 boolean isLabelSet = isActionLabelSet(llRow); 143 Assert.assertTrue("Action label was not set correctly.", isLabelSet); 144 } 145 146 /** 147 * @param rowView the view with id row_view 148 * @return whether the accessibility action label is set 149 */ isActionLabelSet(View rowView)150 private boolean isActionLabelSet(View rowView) { 151 View.AccessibilityDelegate delegate = rowView.getAccessibilityDelegate(); 152 if (delegate == null) { 153 return false; 154 } 155 AccessibilityNodeInfo node = new AccessibilityNodeInfo(rowView); 156 delegate.onInitializeAccessibilityNodeInfo(rowView, node); 157 158 boolean foundLabel = false; 159 final String expectedLabel = 160 mContext.getString(R.string.accessibility_action_label_panel_slice); 161 for (AccessibilityNodeInfo.AccessibilityAction action : node.getActionList()) { 162 if (action.equals(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK) 163 && TextUtils.equals(action.getLabel(), expectedLabel)) { 164 foundLabel = true; 165 break; 166 } 167 } 168 return foundLabel; 169 } 170 171 @Test sizeOfAdapter_shouldNotExceedMaxNum()172 public void sizeOfAdapter_shouldNotExceedMaxNum() { 173 for (int i = 0; i < MAX_NUM_OF_SLICES + 2; i++) { 174 addTestLiveData(Uri.parse("uri" + i)); 175 } 176 177 assertThat(mData.size()).isEqualTo(MAX_NUM_OF_SLICES + 2); 178 179 final PanelSlicesAdapter adapter = 180 new PanelSlicesAdapter(mPanelFragment, mData, 0 /* metrics category */); 181 final ViewGroup view = new FrameLayout(mContext); 182 final SliceRowViewHolder viewHolder = 183 adapter.onCreateViewHolder(view, 0); 184 185 assertThat(adapter.getItemCount()).isEqualTo(MAX_NUM_OF_SLICES); 186 assertThat(adapter.getData().size()).isEqualTo(MAX_NUM_OF_SLICES); 187 } 188 189 @Test mediaOutputIndicatorSlice_notSliderPanel_noSliderLayout()190 public void mediaOutputIndicatorSlice_notSliderPanel_noSliderLayout() { 191 addTestLiveData(MEDIA_OUTPUT_INDICATOR_SLICE_URI); 192 193 final PanelSlicesAdapter adapter = 194 new PanelSlicesAdapter(mPanelFragment, mData, 0 /* metrics category */); 195 final int position = 0; 196 final ViewGroup view = new FrameLayout(mContext); 197 final SliceRowViewHolder viewHolder = 198 adapter.onCreateViewHolder(view, 0 /* view type*/); 199 200 adapter.onBindViewHolder(viewHolder, position); 201 202 assertThat(viewHolder.mSliceSliderLayout).isNull(); 203 } 204 205 @Test onBindViewHolder_viewTypeSlider_verifyActionLabelSet()206 public void onBindViewHolder_viewTypeSlider_verifyActionLabelSet() { 207 addTestLiveData(VOLUME_NOTIFICATION_URI); 208 209 final PanelSlicesAdapter adapter = 210 new PanelSlicesAdapter(mPanelFragment, mData, 0); 211 final ViewGroup view = new FrameLayout(mContext); 212 SliceRowViewHolder viewHolder = spy(adapter.onCreateViewHolder(view, 0 /* view type*/)); 213 adapter.onBindViewHolder(viewHolder, 0); 214 215 verify(viewHolder).updateActionLabel(); 216 } 217 218 @Test onCreateViewHolder_viewTypeSlider_verifyLayout()219 public void onCreateViewHolder_viewTypeSlider_verifyLayout() { 220 final PanelSlicesAdapter adapter = 221 new PanelSlicesAdapter(mPanelFragment, mData, 0); 222 final ViewGroup view = new FrameLayout(mContext); 223 final ArgumentCaptor<Integer> intArgumentCaptor = ArgumentCaptor.forClass(Integer.class); 224 225 adapter.onCreateViewHolder(view, VIEW_TYPE_SLIDER); 226 227 verify(sLayoutInflater).inflate(intArgumentCaptor.capture(), eq(view), eq(false)); 228 assertThat(intArgumentCaptor.getValue()).isEqualTo(R.layout.panel_slice_slider_row); 229 } 230 231 @Test onCreateViewHolder_viewTypeDefault_verifyLayout()232 public void onCreateViewHolder_viewTypeDefault_verifyLayout() { 233 final PanelSlicesAdapter adapter = 234 new PanelSlicesAdapter(mPanelFragment, mData, 0); 235 final ViewGroup view = new FrameLayout(mContext); 236 final ArgumentCaptor<Integer> intArgumentCaptor = ArgumentCaptor.forClass(Integer.class); 237 238 adapter.onCreateViewHolder(view, 0); 239 240 verify(sLayoutInflater).inflate(intArgumentCaptor.capture(), eq(view), eq(false)); 241 assertThat(intArgumentCaptor.getValue()).isEqualTo(R.layout.panel_slice_row); 242 } 243 244 @Implements(LayoutInflater.class) 245 public static class ShadowLayoutInflater { 246 247 @Implementation from(Context context)248 public static LayoutInflater from(Context context) { 249 final LayoutInflater inflater = 250 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 251 if (inflater == null) { 252 throw new AssertionError("LayoutInflater not found."); 253 } 254 sLayoutInflater = spy(inflater); 255 return sLayoutInflater; 256 } 257 } 258 } 259