1 /*
2  * Copyright (C) 2016 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.eq;
24 import static org.mockito.Mockito.mock;
25 import static org.mockito.Mockito.never;
26 import static org.mockito.Mockito.verify;
27 import static org.mockito.Mockito.when;
28 
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.pm.ActivityInfo;
32 import android.content.pm.PackageInfo;
33 import android.content.pm.ResolveInfo;
34 import android.os.UserHandle;
35 import android.view.LayoutInflater;
36 import android.view.View;
37 import android.widget.ImageButton;
38 import android.widget.TextView;
39 
40 import androidx.fragment.app.Fragment;
41 import androidx.fragment.app.FragmentActivity;
42 import androidx.preference.Preference;
43 
44 import com.android.settings.R;
45 import com.android.settingslib.widget.LayoutPreference;
46 
47 import org.junit.Before;
48 import org.junit.Ignore;
49 import org.junit.Test;
50 import org.junit.runner.RunWith;
51 import org.mockito.Answers;
52 import org.mockito.Mock;
53 import org.mockito.MockitoAnnotations;
54 import org.robolectric.RobolectricTestRunner;
55 import org.robolectric.RuntimeEnvironment;
56 import org.robolectric.annotation.Config;
57 
58 @RunWith(RobolectricTestRunner.class)
59 @Config(shadows = {
60         com.android.settings.testutils.shadow.ShadowFragment.class,
61 })
62 public class EntityHeaderControllerTest {
63 
64     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
65     private Context mContext;
66     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
67     private FragmentActivity mActivity;
68     @Mock
69     private Fragment mFragment;
70 
71     private Context mShadowContext;
72     private LayoutInflater mLayoutInflater;
73     private PackageInfo mInfo;
74     private EntityHeaderController mController;
75 
76     @Before
setUp()77     public void setUp() {
78         MockitoAnnotations.initMocks(this);
79         mShadowContext = RuntimeEnvironment.application;
80         when(mActivity.getApplicationContext()).thenReturn(mShadowContext);
81         when(mContext.getApplicationContext()).thenReturn(mContext);
82         when(mFragment.getContext()).thenReturn(mShadowContext);
83         mLayoutInflater = LayoutInflater.from(mShadowContext);
84         mInfo = new PackageInfo();
85         mInfo.versionName = "1234";
86     }
87 
88     @Test
testBuildView_constructedWithoutView_shouldCreateNewView()89     public void testBuildView_constructedWithoutView_shouldCreateNewView() {
90         mController = EntityHeaderController.newInstance(mActivity, mFragment, null);
91         View view = mController.done();
92 
93         assertThat(view).isNotNull();
94     }
95 
96     @Test
testBuildView_withContext_shouldBuildPreferenceAllowedBelowDivider()97     public void testBuildView_withContext_shouldBuildPreferenceAllowedBelowDivider() {
98         mController = EntityHeaderController.newInstance(mActivity, mFragment, null);
99         Preference preference = mController.done(mShadowContext);
100 
101         assertThat(preference instanceof LayoutPreference).isTrue();
102         assertThat(((LayoutPreference)preference).isAllowDividerBelow()).isTrue();
103     }
104 
105     @Test
testBuildView_constructedWithView_shouldReturnSameView()106     public void testBuildView_constructedWithView_shouldReturnSameView() {
107         View inputView = mLayoutInflater.inflate(
108                 com.android.settingslib.widget.preference.layout.R.layout.settings_entity_header, null /* root */);
109         mController = EntityHeaderController.newInstance(mActivity, mFragment, inputView);
110         View view = mController.done();
111 
112         assertThat(view).isSameInstanceAs(inputView);
113     }
114 
115     @Test
bindViews_shouldBindAllData()116     public void bindViews_shouldBindAllData() {
117         final String testString = "test";
118         final View header = mLayoutInflater.inflate(
119                 com.android.settingslib.widget.preference.layout.R.layout.settings_entity_header, null /* root */);
120         final TextView label = header.findViewById(R.id.entity_header_title);
121         final TextView summary = header.findViewById(R.id.entity_header_summary);
122         final TextView secondSummary = header.findViewById(
123                 com.android.settingslib.widget.preference.layout.R.id.entity_header_second_summary);
124 
125         mController = EntityHeaderController.newInstance(mActivity, mFragment, header);
126         mController.setLabel(testString);
127         mController.setSummary(testString);
128         mController.setSecondSummary(testString);
129         mController.setIcon(mShadowContext.getDrawable(R.drawable.ic_add_24dp));
130         mController.done();
131 
132         assertThat(label).isNotNull();
133         assertThat(label.getText()).isEqualTo(testString);
134         assertThat(summary).isNotNull();
135         assertThat(summary.getText()).isEqualTo(testString);
136         assertThat(secondSummary).isNotNull();
137         assertThat(secondSummary.getText()).isEqualTo(testString);
138     }
139 
140     @Test
bindButton_hasEditClickListener_shouldShowButton()141     public void bindButton_hasEditClickListener_shouldShowButton() {
142         final ResolveInfo info = new ResolveInfo();
143         info.activityInfo = new ActivityInfo();
144         info.activityInfo.packageName = "123";
145         info.activityInfo.name = "321";
146         final View view = mLayoutInflater.inflate(
147                 com.android.settingslib.widget.preference.layout.R.layout.settings_entity_header, null /* root */);
148         when(mActivity.getApplicationContext()).thenReturn(mContext);
149 
150         mController = EntityHeaderController.newInstance(mActivity, mFragment, view);
151         mController.setEditListener(v -> {/* do nothing */});
152         mController.setButtonActions(
153                 EntityHeaderController.ActionType.ACTION_EDIT_PREFERENCE,
154                 EntityHeaderController.ActionType.ACTION_NONE);
155         mController.done();
156 
157         final ImageButton button1 = view.findViewById(android.R.id.button1);
158         assertThat(button1).isNotNull();
159         assertThat(button1.getVisibility()).isEqualTo(View.VISIBLE);
160         assertThat(button1.getDrawable()).isNotNull();
161         assertThat(view.findViewById(android.R.id.button2).getVisibility()).isEqualTo(View.GONE);
162     }
163 
164     @Test
bindButton_noEditClickListener_shouldNotShowButton()165     public void bindButton_noEditClickListener_shouldNotShowButton() {
166         final ResolveInfo info = new ResolveInfo();
167         info.activityInfo = new ActivityInfo();
168         info.activityInfo.packageName = "123";
169         info.activityInfo.name = "321";
170         final View view = mLayoutInflater.inflate(
171                 com.android.settingslib.widget.preference.layout.R.layout.settings_entity_header, null /* root */);
172         when(mActivity.getApplicationContext()).thenReturn(mContext);
173 
174         mController = EntityHeaderController.newInstance(mActivity, mFragment, view);
175         mController.setButtonActions(
176                 EntityHeaderController.ActionType.ACTION_EDIT_PREFERENCE,
177                 EntityHeaderController.ActionType.ACTION_NONE);
178         mController.done();
179 
180         assertThat(view.findViewById(android.R.id.button1).getVisibility()).isEqualTo(View.GONE);
181         assertThat(view.findViewById(android.R.id.button2).getVisibility()).isEqualTo(View.GONE);
182     }
183 
184 
185     @Test
bindButton_noAppInfo_shouldNotAttachClickListener()186     public void bindButton_noAppInfo_shouldNotAttachClickListener() {
187         final View appLinks = mLayoutInflater.inflate(
188                 com.android.settingslib.widget.preference.layout.R.layout.settings_entity_header, null /* root */);
189         final FragmentActivity activity = mock(FragmentActivity.class);
190         when(mFragment.getActivity()).thenReturn(activity);
191 
192         mController = EntityHeaderController.newInstance(mActivity, mFragment, appLinks);
193         mController.setPackageName(null)
194                 .setHasAppInfoLink(true)
195                 .setButtonActions(
196                         EntityHeaderController.ActionType.ACTION_NONE,
197                         EntityHeaderController.ActionType.ACTION_NONE);
198         mController.done();
199 
200         assertThat(appLinks.findViewById(android.R.id.button1).getVisibility())
201                 .isEqualTo(View.GONE);
202         assertThat(appLinks.findViewById(android.R.id.button2).getVisibility())
203                 .isEqualTo(View.GONE);
204 
205         appLinks.findViewById(com.android.settingslib.widget.preference.layout.R.id.entity_header_content)
206                 .performClick();
207         verify(mFragment, never()).getActivity();
208         verify(activity, never()).startActivity(any(Intent.class));
209     }
210 
211     @Ignore("b/313616350")
212     @Test
bindButton_hasAppInfo_shouldAttachClickListener()213     public void bindButton_hasAppInfo_shouldAttachClickListener() {
214         final View appLinks = mLayoutInflater.inflate(
215                 com.android.settingslib.widget.preference.layout.R.layout.settings_entity_header, null /* root */);
216         final FragmentActivity activity = mock(FragmentActivity.class);
217         when(mFragment.getActivity()).thenReturn(activity);
218         when(mContext.getString(eq(R.string.application_info_label))).thenReturn("App Info");
219 
220         mController = EntityHeaderController.newInstance(mActivity, mFragment, appLinks);
221         mController.setPackageName("123")
222                 .setUid(123321)
223                 .setHasAppInfoLink(true)
224                 .setButtonActions(
225                         EntityHeaderController.ActionType.ACTION_NOTIF_PREFERENCE,
226                         EntityHeaderController.ActionType.ACTION_NONE);
227         mController.done();
228 
229         appLinks.findViewById(com.android.settingslib.widget.preference.layout.R.id.entity_header_content)
230                 .performClick();
231         verify(activity)
232                 .startActivityForResultAsUser(any(Intent.class), anyInt(), any(UserHandle.class));
233     }
234 
235     @Test
iconContentDescription_shouldWorkWithSetIcon()236     public void iconContentDescription_shouldWorkWithSetIcon() {
237         final View view = mLayoutInflater.inflate(
238                 com.android.settingslib.widget.preference.layout.R.layout.settings_entity_header, null /* root */);
239         when(mFragment.getActivity()).thenReturn(mock(FragmentActivity.class));
240         mController = EntityHeaderController.newInstance(mActivity, mFragment, view);
241         String description = "Fake Description";
242         mController.setIcon(mShadowContext.getDrawable(R.drawable.ic_add_24dp));
243         mController.setIconContentDescription(description);
244         mController.done();
245         assertThat(view.findViewById(R.id.entity_header_icon).getContentDescription().toString())
246                 .isEqualTo(description);
247     }
248 
249     @Test
iconContentDescription_shouldWorkWithoutSetIcon()250     public void iconContentDescription_shouldWorkWithoutSetIcon() {
251         final View view = mLayoutInflater.inflate(
252                 com.android.settingslib.widget.preference.layout.R.layout.settings_entity_header, null /* root */);
253         when(mFragment.getActivity()).thenReturn(mock(FragmentActivity.class));
254         mController = EntityHeaderController.newInstance(mActivity, mFragment, view);
255         String description = "Fake Description";
256         mController.setIconContentDescription(description);
257         mController.done();
258         assertThat(view.findViewById(R.id.entity_header_icon).getContentDescription().toString())
259                 .isEqualTo(description);
260     }
261 
262     @Test
bindButton_hasAppNotifIntent_shouldShowButton()263     public void bindButton_hasAppNotifIntent_shouldShowButton() {
264         final View appLinks = mLayoutInflater.inflate(
265                 com.android.settingslib.widget.preference.layout.R.layout.settings_entity_header, null /* root */);
266 
267         mController = EntityHeaderController.newInstance(mActivity, mFragment, appLinks);
268         mController.setAppNotifPrefIntent(new Intent())
269                 .setButtonActions(
270                         EntityHeaderController.ActionType.ACTION_NOTIF_PREFERENCE,
271                         EntityHeaderController.ActionType.ACTION_NONE);
272         mController.done();
273 
274         assertThat(appLinks.findViewById(android.R.id.button1).getVisibility())
275                 .isEqualTo(View.VISIBLE);
276         assertThat(appLinks.findViewById(android.R.id.button2).getVisibility())
277                 .isEqualTo(View.GONE);
278     }
279 
280     // Ensure that the instant app label does not show up when we haven't told the controller the
281     // app is instant.
282     @Test
instantApps_normalAppsDontGetLabel()283     public void instantApps_normalAppsDontGetLabel() {
284         final View header = mLayoutInflater.inflate(
285                 com.android.settingslib.widget.preference.layout.R.layout.settings_entity_header, null /* root */);
286         mController = EntityHeaderController.newInstance(mActivity, mFragment, header);
287         mController.done();
288 
289         View installType = header.findViewById(com.android.settingslib.widget.preference.layout.R.id.install_type);
290         assertThat(installType.getVisibility()).isEqualTo(View.GONE);
291     }
292 
293     // Test that the "instant apps" label is present in the header when we have an instant app.
294     @Test
instantApps_expectedHeaderItem()295     public void instantApps_expectedHeaderItem() {
296         final View header = mLayoutInflater.inflate(
297                 com.android.settingslib.widget.preference.layout.R.layout.settings_entity_header, null /* root */);
298         mController = EntityHeaderController.newInstance(mActivity, mFragment, header);
299         mController.setIsInstantApp(true);
300         mController.done();
301         TextView label = header.findViewById(com.android.settingslib.widget.preference.layout.R.id.install_type);
302 
303         assertThat(label.getVisibility()).isEqualTo(View.VISIBLE);
304         assertThat(label.getText()).isEqualTo(
305                 header.getResources().getString(R.string.install_type_instant));
306         assertThat(header.findViewById(R.id.entity_header_summary).getVisibility())
307                 .isEqualTo(View.GONE);
308     }
309 
310     @Test
initAppHeaderController_appHeaderNull_useFragmentContext()311     public void initAppHeaderController_appHeaderNull_useFragmentContext() {
312         mController = EntityHeaderController.newInstance(mActivity, mFragment, null);
313 
314         // Fragment.getContext() is invoked to inflate the view
315         verify(mFragment).getContext();
316     }
317 }
318