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 package com.android.settings.location;
17 
18 import static com.google.common.truth.Truth.assertThat;
19 
20 import static org.mockito.ArgumentMatchers.any;
21 import static org.mockito.ArgumentMatchers.anyChar;
22 import static org.mockito.ArgumentMatchers.anyInt;
23 import static org.mockito.Mockito.never;
24 import static org.mockito.Mockito.spy;
25 import static org.mockito.Mockito.times;
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.ApplicationInfo;
33 import android.content.pm.PackageManager;
34 import android.content.pm.PackageManager.NameNotFoundException;
35 import android.content.pm.ResolveInfo;
36 import android.content.res.Resources;
37 import android.location.LocationManager;
38 import android.os.Bundle;
39 import android.text.Html;
40 
41 import androidx.lifecycle.LifecycleOwner;
42 import androidx.preference.PreferenceScreen;
43 
44 import com.android.settings.R;
45 import com.android.settingslib.core.lifecycle.Lifecycle;
46 import com.android.settingslib.widget.FooterPreference;
47 
48 import org.junit.Before;
49 import org.junit.Test;
50 import org.junit.runner.RunWith;
51 import org.mockito.ArgumentCaptor;
52 import org.mockito.Mock;
53 import org.mockito.MockitoAnnotations;
54 import org.robolectric.RobolectricTestRunner;
55 import org.robolectric.RuntimeEnvironment;
56 
57 import java.util.ArrayList;
58 import java.util.List;
59 
60 @RunWith(RobolectricTestRunner.class)
61 public class LocationSettingsFooterPreferenceControllerTest {
62 
63     private static final int TEST_RES_ID = 1234;
64     private static final String TEST_TEXT = "text";
65     private static final String PREFERENCE_KEY = "location_footer";
66 
67     private Context mContext;
68     private LocationSettingsFooterPreferenceController mController;
69     private Lifecycle mLifecycle;
70 
71     @Mock
72     private PreferenceScreen mPreferenceScreen;
73     @Mock
74     private FooterPreference mFooterPreference;
75     @Mock
76     private PackageManager mPackageManager;
77     @Mock
78     private Resources mResources;
79 
80     @Before
setUp()81     public void setUp() throws NameNotFoundException {
82         MockitoAnnotations.initMocks(this);
83         mContext = spy(RuntimeEnvironment.application);
84         when(mContext.getPackageManager()).thenReturn(mPackageManager);
85 
86         LifecycleOwner lifecycleOwner = () -> mLifecycle;
87         mLifecycle = new Lifecycle(lifecycleOwner);
88         LocationSettings locationSettings = spy(new LocationSettings());
89         when(locationSettings.getSettingsLifecycle()).thenReturn(mLifecycle);
90 
91         mController = spy(new LocationSettingsFooterPreferenceController(mContext, PREFERENCE_KEY));
92         mController.init(locationSettings);
93 
94         when(mPreferenceScreen.findPreference(PREFERENCE_KEY)).thenReturn(mFooterPreference);
95         when(mPackageManager.getResourcesForApplication(any(ApplicationInfo.class)))
96                 .thenReturn(mResources);
97         when(mResources.getString(TEST_RES_ID)).thenReturn(TEST_TEXT);
98         mController.displayPreference(mPreferenceScreen);
99     }
100 
101     @Test
isAvailable_hasValidFooter_returnsTrue()102     public void isAvailable_hasValidFooter_returnsTrue() {
103         final List<ResolveInfo> testResolveInfos = new ArrayList<>();
104         testResolveInfos.add(
105                 getTestResolveInfo(/*isSystemApp*/ true, /*hasRequiredMetadata*/ true));
106         when(mPackageManager.queryBroadcastReceivers(any(Intent.class), anyInt()))
107                 .thenReturn(testResolveInfos);
108 
109         assertThat(mController.isAvailable()).isTrue();
110     }
111 
112     /**
113      * Display the footer even without the injected string.
114      */
115     @Test
isAvailable_noSystemApp_returnsTrue()116     public void isAvailable_noSystemApp_returnsTrue() {
117         final List<ResolveInfo> testResolveInfos = new ArrayList<>();
118         testResolveInfos.add(
119                 getTestResolveInfo(/*isSystemApp*/ false, /*hasRequiredMetadata*/ true));
120         when(mPackageManager.queryBroadcastReceivers(any(Intent.class), anyInt()))
121                 .thenReturn(testResolveInfos);
122         assertThat(mController.isAvailable()).isTrue();
123     }
124 
125     /**
126      * Display the footer even without the injected string.
127      */
128     @Test
isAvailable_noRequiredMetadata_returnsTrue()129     public void isAvailable_noRequiredMetadata_returnsTrue() {
130         final List<ResolveInfo> testResolveInfos = new ArrayList<>();
131         testResolveInfos.add(
132                 getTestResolveInfo(/*isSystemApp*/ true, /*hasRequiredMetadata*/ false));
133         when(mPackageManager.queryBroadcastReceivers(any(Intent.class), anyInt()))
134                 .thenReturn(testResolveInfos);
135         assertThat(mController.isAvailable()).isTrue();
136     }
137 
138     @Test
updateState_setTitle()139     public void updateState_setTitle() {
140         final List<ResolveInfo> testResolveInfos = new ArrayList<>();
141         testResolveInfos.add(
142                 getTestResolveInfo(/*isSystemApp*/ true, /*hasRequiredMetadata*/ true));
143         when(mPackageManager.queryBroadcastReceivers(any(Intent.class), anyInt()))
144                 .thenReturn(testResolveInfos);
145         mController.updateState(mFooterPreference);
146         ArgumentCaptor<CharSequence> title = ArgumentCaptor.forClass(CharSequence.class);
147         verify(mFooterPreference).setTitle(title.capture());
148         assertThat(title.getValue()).isNotNull();
149     }
150 
151     @Test
onLocationModeChanged_off_setTitle()152     public void onLocationModeChanged_off_setTitle() {
153         final List<ResolveInfo> testResolveInfos = new ArrayList<>();
154         testResolveInfos.add(
155                 getTestResolveInfo(/*isSystemApp*/ true, /*hasRequiredMetadata*/ true));
156         when(mPackageManager.queryBroadcastReceivers(any(Intent.class), anyInt()))
157                 .thenReturn(testResolveInfos);
158         mController.updateState(mFooterPreference);
159         verify(mFooterPreference).setTitle(any());
160         mController.onLocationModeChanged(/* mode= */ 0, /* restricted= */ false);
161         ArgumentCaptor<CharSequence> title = ArgumentCaptor.forClass(CharSequence.class);
162         verify(mFooterPreference, times(2)).setTitle(title.capture());
163 
164         assertThat(title.getValue().toString()).contains(
165                 Html.fromHtml(mContext.getString(
166                         R.string.location_settings_footer_location_off)).toString());
167     }
168 
169     @Test
onLocationModeChanged_on_setTitle()170     public void onLocationModeChanged_on_setTitle() {
171         final List<ResolveInfo> testResolveInfos = new ArrayList<>();
172         testResolveInfos.add(
173                 getTestResolveInfo(/*isSystemApp*/ true, /*hasRequiredMetadata*/ true));
174         when(mPackageManager.queryBroadcastReceivers(any(Intent.class), anyInt()))
175                 .thenReturn(testResolveInfos);
176         mController.updateState(mFooterPreference);
177         verify(mFooterPreference).setTitle(any());
178         mController.onLocationModeChanged(/* mode= */ 1, /* restricted= */ false);
179         ArgumentCaptor<CharSequence> title = ArgumentCaptor.forClass(CharSequence.class);
180         verify(mFooterPreference, times(2)).setTitle(title.capture());
181         assertThat(title.getValue().toString()).doesNotContain(
182                 Html.fromHtml(mContext.getString(
183                         R.string.location_settings_footer_location_off)).toString());
184     }
185 
186     @Test
updateState_notSystemApp_ignore()187     public void updateState_notSystemApp_ignore() {
188         final List<ResolveInfo> testResolveInfos = new ArrayList<>();
189         testResolveInfos.add(
190                 getTestResolveInfo(/*isSystemApp*/ false, /*hasRequiredMetadata*/ true));
191         when(mPackageManager.queryBroadcastReceivers(any(Intent.class), anyInt()))
192                 .thenReturn(testResolveInfos);
193         mController.updateState(mFooterPreference);
194         verify(mFooterPreference, never()).setTitle(anyChar());
195     }
196 
197     /**
198      * Returns a ResolveInfo object for testing
199      * @param isSystemApp If true, the application is a system app.
200      * @param hasRequiredMetaData If true, the broadcast receiver has a valid value for
201      *                            {@link LocationManager#METADATA_SETTINGS_FOOTER_STRING}
202      */
getTestResolveInfo(boolean isSystemApp, boolean hasRequiredMetaData)203     private ResolveInfo getTestResolveInfo(boolean isSystemApp, boolean hasRequiredMetaData) {
204         ResolveInfo testResolveInfo = new ResolveInfo();
205         ApplicationInfo testAppInfo = new ApplicationInfo();
206         if (isSystemApp) {
207             testAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
208         }
209         ActivityInfo testActivityInfo = new ActivityInfo();
210         testActivityInfo.name = "TestActivityName";
211         testActivityInfo.packageName = "TestPackageName";
212         testActivityInfo.applicationInfo = testAppInfo;
213         if (hasRequiredMetaData) {
214             testActivityInfo.metaData = new Bundle();
215             testActivityInfo.metaData.putInt(
216                     LocationManager.METADATA_SETTINGS_FOOTER_STRING, TEST_RES_ID);
217         }
218         testResolveInfo.activityInfo = testActivityInfo;
219         return testResolveInfo;
220     }
221 }
222