1 /*
2  * Copyright (C) 2021 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.car.settings.common;
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.never;
27 import static org.mockito.Mockito.verify;
28 import static org.mockito.Mockito.withSettings;
29 import static org.testng.Assert.assertThrows;
30 
31 import android.car.drivingstate.CarUxRestrictions;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.content.IntentSender;
35 import android.widget.Toast;
36 
37 import androidx.fragment.app.DialogFragment;
38 import androidx.fragment.app.Fragment;
39 import androidx.fragment.app.FragmentManager;
40 import androidx.preference.Preference;
41 import androidx.test.annotation.UiThreadTest;
42 import androidx.test.core.app.ApplicationProvider;
43 import androidx.test.ext.junit.runners.AndroidJUnit4;
44 import androidx.test.platform.app.InstrumentationRegistry;
45 import androidx.test.rule.ActivityTestRule;
46 
47 import com.android.car.settings.R;
48 import com.android.car.settings.testutils.BaseCarSettingsTestActivity;
49 import com.android.car.settings.testutils.TestFinishActivity;
50 import com.android.dx.mockito.inline.extended.ExtendedMockito;
51 
52 import org.junit.Before;
53 import org.junit.Test;
54 import org.junit.runner.RunWith;
55 import org.mockito.MockitoAnnotations;
56 import org.mockito.MockitoSession;
57 
58 import java.util.concurrent.atomic.AtomicReference;
59 
60 @RunWith(AndroidJUnit4.class)
61 public abstract class SettingsFragmentTestCase<T extends BaseCarSettingsTestActivity> {
62     protected static final String TEST_TAG = "test_tag";
63 
64     protected Context mContext = ApplicationProvider.getApplicationContext();
65     protected BaseCarSettingsTestActivity mActivity;
66     protected FragmentManager mFragmentManager;
67     protected SettingsFragment mFragment;
68 
getActivityTestRule()69     abstract ActivityTestRule<T> getActivityTestRule();
70 
71     @Before
setUp()72     public void setUp() throws Throwable {
73         MockitoAnnotations.initMocks(this);
74         mActivity = getActivityTestRule().getActivity();
75         mFragmentManager = mActivity.getSupportFragmentManager();
76         setUpFragment();
77     }
78 
79     @Test
80     @UiThreadTest
use_returnsController()81     public void use_returnsController() {
82         mFragment.onCreate(null);
83         assertThat(mFragment.use(FakePreferenceController.class,
84                 R.string.tpk_fake_controller)).isNotNull();
85     }
86 
87     @Test
onAttach_registersLifecycleObservers()88     public void onAttach_registersLifecycleObservers() throws Throwable {
89         AtomicReference<FakePreferenceController> controller = new AtomicReference<>();
90         getActivityTestRule().runOnUiThread(() -> {
91             mFragment.onCreate(null);
92             controller.set(mFragment.use(FakePreferenceController.class,
93                     R.string.tpk_fake_controller));
94             assertThat(controller.get().getOnCreateInternalCallCount()).isEqualTo(1);
95         });
96         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
97         getActivityTestRule().finishActivity();
98         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
99         assertThat(controller.get().getOnCreateInternalCallCount()).isEqualTo(1);
100     }
101 
102     @Test
103     @UiThreadTest
onUxRestrictionsChanged_propagatesToControllers()104     public void onUxRestrictionsChanged_propagatesToControllers() {
105         mFragment.onCreate(null);
106         FakePreferenceController controller = mFragment.use(FakePreferenceController.class,
107                 R.string.tpk_fake_controller);
108         CarUxRestrictions uxRestrictions = new CarUxRestrictions.Builder(/* reqOpt= */ true,
109                 CarUxRestrictions.UX_RESTRICTIONS_NO_KEYBOARD, /* timestamp= */ 0).build();
110         mFragment.onUxRestrictionsChanged(uxRestrictions);
111         assertThat(controller.getUxRestrictions()).isEqualTo(uxRestrictions);
112     }
113 
114     @Test
115     @UiThreadTest
onUxRestrictedPreferenceTapped_showToast()116     public void onUxRestrictedPreferenceTapped_showToast() {
117         MockitoSession session = ExtendedMockito.mockitoSession().mockStatic(Toast.class,
118                 withSettings().lenient()).startMocking();
119         Toast mockToast = mock(Toast.class);
120         ExtendedMockito.when(Toast.makeText(any(), anyString(), anyInt()))
121                 .thenReturn(mockToast);
122 
123         mFragment.onCreate(null);
124         FakePreferenceController controller = mFragment.use(FakePreferenceController.class,
125                 R.string.tpk_fake_controller);
126         CarUxRestrictions uxRestrictions = new CarUxRestrictions.Builder(/* reqOpt= */ true,
127                 CarUxRestrictions.UX_RESTRICTIONS_NO_SETUP, /* timestamp= */ 0).build();
128         mFragment.onUxRestrictionsChanged(uxRestrictions);
129         controller.getPreference().performClick();
130         verify(mockToast).show();
131 
132         session.finishMocking();
133     }
134 
135     @Test
136     @UiThreadTest
onDisplayPreferenceDialog_unknownPreferenceType_throwsIllegalArgumentException()137     public void onDisplayPreferenceDialog_unknownPreferenceType_throwsIllegalArgumentException() {
138         mFragment.onCreate(null);
139 
140         assertThrows(IllegalArgumentException.class,
141                 () -> mFragment.onDisplayPreferenceDialog(new Preference(mContext)));
142     }
143 
144     @Test
145     @UiThreadTest
launchFragment_dialogFragment_throwsError()146     public void launchFragment_dialogFragment_throwsError() {
147         mFragment.onCreate(null);
148         DialogFragment dialogFragment = new DialogFragment();
149         assertThrows(IllegalArgumentException.class,
150                 () -> mFragment.launchFragment(dialogFragment));
151     }
152 
153     @Test
154     @UiThreadTest
showDialog_noTag_launchesDialogFragment()155     public void showDialog_noTag_launchesDialogFragment() {
156         mFragment.onCreate(null);
157         DialogFragment dialogFragment = mock(DialogFragment.class);
158         mFragment.showDialog(dialogFragment, /* tag= */ null);
159         verify(dialogFragment).show(mFragment.getFragmentManager(), null);
160     }
161 
162     @Test
163     @UiThreadTest
showDialog_withTag_launchesDialogFragment()164     public void showDialog_withTag_launchesDialogFragment() {
165         mFragment.onCreate(null);
166         DialogFragment dialogFragment = mock(DialogFragment.class);
167         mFragment.showDialog(dialogFragment, TEST_TAG);
168         verify(dialogFragment).show(mFragment.getFragmentManager(), TEST_TAG);
169     }
170 
171     @Test
findDialogByTag_retrieveOriginalDialog_returnsDialog()172     public void findDialogByTag_retrieveOriginalDialog_returnsDialog() throws Throwable {
173         DialogFragment dialogFragment = new DialogFragment();
174         getActivityTestRule().runOnUiThread(() -> {
175             mFragment.onCreate(null);
176             mFragment.showDialog(dialogFragment, TEST_TAG);
177         });
178         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
179         assertThat(mFragment.findDialogByTag(TEST_TAG)).isEqualTo(dialogFragment);
180     }
181 
182     @Test
183     @UiThreadTest
findDialogByTag_notDialogFragment_returnsNull()184     public void findDialogByTag_notDialogFragment_returnsNull() {
185         mFragment.onCreate(null);
186         TestSettingsFragment
187                 fragment = new TestSettingsFragment();
188         mFragment.getFragmentManager().beginTransaction().add(fragment, TEST_TAG).commit();
189         assertThat(mFragment.findDialogByTag(TEST_TAG)).isNull();
190     }
191 
192     @Test
193     @UiThreadTest
findDialogByTag_noSuchFragment_returnsNull()194     public void findDialogByTag_noSuchFragment_returnsNull() {
195         mFragment.onCreate(null);
196         assertThat(mFragment.findDialogByTag(TEST_TAG)).isNull();
197     }
198 
199     @Test
200     @UiThreadTest
startActivityForResult_largeRequestCode_throwsError()201     public void startActivityForResult_largeRequestCode_throwsError() {
202         mFragment.onCreate(null);
203         assertThrows(() -> mFragment.startActivityForResult(new Intent(), 0xffff,
204                 mock(ActivityResultCallback.class)));
205     }
206 
207     @Test
208     @UiThreadTest
startActivityForResult_tooManyRequests_throwsError()209     public void startActivityForResult_tooManyRequests_throwsError() {
210         mFragment.onCreate(null);
211         assertThrows(() -> {
212             for (int i = 0; i < 0xff; i++) {
213                 mFragment.startActivityForResult(new Intent(), i,
214                         mock(ActivityResultCallback.class));
215             }
216         });
217     }
218 
219     @Test
220     @UiThreadTest
startIntentSenderForResult_largeRequestCode_throwsError()221     public void startIntentSenderForResult_largeRequestCode_throwsError() {
222         mFragment.onCreate(null);
223         assertThrows(
224                 () -> mFragment.startIntentSenderForResult(
225                         mock(IntentSender.class), /* requestCode= */ 0xffff,
226                         /* fillInIntent= */ null, /* flagsMask= */ 0,
227                         /* flagsValues= */ 0, /* options= */ null,
228                         mock(ActivityResultCallback.class)));
229     }
230 
231     @Test
232     @UiThreadTest
startIntentSenderForResult_tooManyRequests_throwsError()233     public void startIntentSenderForResult_tooManyRequests_throwsError() {
234         mFragment.onCreate(null);
235         assertThrows(() -> {
236             for (int i = 0; i < 0xff; i++) {
237                 mFragment.startIntentSenderForResult(
238                         mock(IntentSender.class), /* requestCode= */ 0xffff,
239                         /* fillInIntent= */ null, /* flagsMask= */ 0,
240                         /* flagsValues= */ 0, /* options= */ null,
241                         mock(ActivityResultCallback.class));
242             }
243         });
244     }
245 
246     @Test
247     @UiThreadTest
onActivityResult_hasValidRequestCode_triggersOnActivityResult()248     public void onActivityResult_hasValidRequestCode_triggersOnActivityResult() {
249         mFragment.onCreate(null);
250         ActivityResultCallback callback = mock(ActivityResultCallback.class);
251         Intent intent = new Intent(mContext, TestFinishActivity.class);
252 
253         int reqCode = 100;
254         int resCode = -1;
255         mFragment.startActivityForResult(intent, reqCode, callback);
256         int fragmentReqCode = (1 << 8) + reqCode;
257         mFragment.onActivityResult(fragmentReqCode, resCode, intent);
258         verify(callback).processActivityResult(eq(reqCode), eq(resCode), any(Intent.class));
259     }
260 
261     @Test
262     @UiThreadTest
onActivityResult_wrongRequestCode_doesntTriggerOnActivityResult()263     public void onActivityResult_wrongRequestCode_doesntTriggerOnActivityResult() {
264         mFragment.onCreate(null);
265         ActivityResultCallback callback = mock(ActivityResultCallback.class);
266         Intent intent = new Intent(mContext, TestFinishActivity.class);
267 
268         int reqCode = 100;
269         int resCode = -1;
270         mFragment.startActivityForResult(intent, reqCode, callback);
271         int fragmentReqCode = (2 << 8) + reqCode;
272         mFragment.onActivityResult(fragmentReqCode, resCode, intent);
273         verify(callback, never()).processActivityResult(anyInt(), anyInt(),
274                 any(Intent.class));
275     }
276 
setUpFragment()277     protected void setUpFragment() throws Throwable {
278         setUpFragment(null);
279     }
280 
setUpFragment(Fragment fragment)281     protected void setUpFragment(Fragment fragment) throws Throwable {
282         String settingsFragmentTag = "settings_fragment";
283         getActivityTestRule().runOnUiThread(() -> {
284             mFragmentManager.beginTransaction()
285                     .replace(R.id.fragment_container,
286                             fragment != null ? fragment : new TestSettingsFragment(),
287                             settingsFragmentTag)
288                     .commitNow();
289         });
290         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
291         mFragment = (SettingsFragment) mFragmentManager.findFragmentByTag(settingsFragmentTag);
292     }
293 
294     /** Concrete {@link SettingsFragment} for testing. */
295     public static class TestSettingsFragment extends SettingsFragment {
296 
297         @Override
getPreferenceScreenResId()298         protected int getPreferenceScreenResId() {
299             return R.xml.test_base_settings_fragment;
300         }
301 
302     }
303 }
304