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