1/*
2 * Copyright (C) 2017 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 */
16package com.android.settings.location;
17
18import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT;
19import static com.google.common.truth.Truth.assertThat;
20import static org.mockito.ArgumentMatchers.any;
21import static org.mockito.ArgumentMatchers.argThat;
22import static org.mockito.ArgumentMatchers.eq;
23import static org.mockito.Mockito.doReturn;
24import static org.mockito.Mockito.mock;
25import static org.mockito.Mockito.never;
26import static org.mockito.Mockito.spy;
27import static org.mockito.Mockito.verify;
28import static org.mockito.Mockito.when;
29
30import android.arch.lifecycle.LifecycleOwner;
31import android.content.Context;
32import android.content.Intent;
33import android.provider.Settings;
34import android.support.v7.preference.Preference;
35import android.support.v7.preference.PreferenceCategory;
36import android.support.v7.preference.PreferenceScreen;
37import android.text.TextUtils;
38import com.android.settings.R;
39import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
40import com.android.settings.testutils.SettingsRobolectricTestRunner;
41import com.android.settings.widget.AppPreference;
42import com.android.settingslib.core.lifecycle.Lifecycle;
43import com.android.settingslib.location.RecentLocationApps;
44import com.android.settingslib.location.RecentLocationApps.Request;
45import java.util.ArrayList;
46import java.util.List;
47import org.junit.Before;
48import org.junit.Test;
49import org.junit.runner.RunWith;
50import org.mockito.ArgumentCaptor;
51import org.mockito.ArgumentMatcher;
52import org.mockito.InOrder;
53import org.mockito.Mock;
54import org.mockito.Mockito;
55import org.mockito.MockitoAnnotations;
56import org.robolectric.RuntimeEnvironment;
57
58@RunWith(SettingsRobolectricTestRunner.class)
59public class RecentLocationRequestPreferenceControllerTest {
60
61    @Mock
62    private LocationSettings mFragment;
63    @Mock
64    private PreferenceCategory mCategory;
65    @Mock
66    private PreferenceScreen mScreen;
67    @Mock
68    private RecentLocationApps mRecentLocationApps;
69    @Mock
70    private Preference mSeeAllButton;
71
72    private Context mContext;
73    private RecentLocationRequestPreferenceController mController;
74    private LifecycleOwner mLifecycleOwner;
75    private Lifecycle mLifecycle;
76
77    @Before
78    public void setUp() {
79        MockitoAnnotations.initMocks(this);
80        mContext = spy(RuntimeEnvironment.application);
81        mLifecycleOwner = () -> mLifecycle;
82        mLifecycle = new Lifecycle(mLifecycleOwner);
83        mController = spy(new RecentLocationRequestPreferenceController(
84                mContext, mFragment, mLifecycle, mRecentLocationApps));
85        when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mCategory);
86        when(mScreen.findPreference(mController.KEY_SEE_ALL_BUTTON)).thenReturn(mSeeAllButton);
87        final String key = mController.getPreferenceKey();
88        when(mCategory.getKey()).thenReturn(key);
89        when(mCategory.getContext()).thenReturn(mContext);
90    }
91
92    @Test
93    public void onLocationModeChanged_LocationOn_shouldEnablePreference() {
94        mController.displayPreference(mScreen);
95
96        mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, false);
97
98        verify(mCategory).setEnabled(true);
99    }
100
101    @Test
102    public void onLocationModeChanged_LocationOff_shouldDisablePreference() {
103        mController.displayPreference(mScreen);
104
105        mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_OFF, false);
106
107        verify(mCategory).setEnabled(false);
108    }
109
110    @Test
111    public void updateState_noRecentRequest_shouldRemoveAllAndAddBanner() {
112        doReturn(new ArrayList<>()).when(mRecentLocationApps).getAppListSorted();
113        mController.displayPreference(mScreen);
114
115        mController.updateState(mCategory);
116
117        verify(mCategory).removeAll();
118        final String title = mContext.getString(R.string.location_no_recent_apps);
119        verify(mCategory).addPreference(argThat(titleMatches(title)));
120    }
121
122    @Test
123    public void updateState_hasRecentRequest_shouldRemoveAllAndAddInjectedSettings() {
124        List<Request> requests = createMockRequests(2);
125        doReturn(requests).when(mRecentLocationApps).getAppListSorted();
126
127        mController.displayPreference(mScreen);
128        mController.updateState(mCategory);
129
130        verify(mCategory).removeAll();
131        // Verifies two preferences are added in original order
132        InOrder inOrder = Mockito.inOrder(mCategory);
133        inOrder.verify(mCategory).addPreference(argThat(titleMatches("appTitle0")));
134        inOrder.verify(mCategory).addPreference(argThat(titleMatches("appTitle1")));
135    }
136
137    @Test
138    public void updateState_hasOverThreeRequests_shouldDisplaySeeAllButton() {
139        List<Request> requests = createMockRequests(6);
140        when(mRecentLocationApps.getAppListSorted()).thenReturn(requests);
141
142        mController.displayPreference(mScreen);
143        mController.updateState(mCategory);
144
145        verify(mCategory).removeAll();
146        // Verifies the first three preferences are added
147        InOrder inOrder = Mockito.inOrder(mCategory);
148        inOrder.verify(mCategory).addPreference(argThat(titleMatches("appTitle0")));
149        inOrder.verify(mCategory).addPreference(argThat(titleMatches("appTitle1")));
150        inOrder.verify(mCategory).addPreference(argThat(titleMatches("appTitle2")));
151        verify(mCategory, never()).addPreference(argThat(titleMatches("appTitle3")));
152        // Verifies the "See all" preference is visible
153        verify(mSeeAllButton).setVisible(true);
154    }
155
156    @Test
157    public void createAppPreference_shouldAddClickListener() {
158        final Request request = mock(Request.class);
159        final AppPreference preference = mock(AppPreference.class);
160        doReturn(preference).when(mController).createAppPreference(any(Context.class));
161
162        mController.createAppPreference(mContext, request);
163
164        verify(preference).setOnPreferenceClickListener(
165                any(RecentLocationRequestPreferenceController.PackageEntryClickedListener.class));
166    }
167
168    @Test
169    public void onPreferenceClick_shouldLaunchAppDetails() {
170        final Context context= mock(Context.class);
171        when(mFragment.getContext()).thenReturn(context);
172
173        final List<RecentLocationApps.Request> requests = new ArrayList<>();
174        final Request request = mock(Request.class);
175        requests.add(request);
176        doReturn(requests).when(mRecentLocationApps).getAppListSorted();
177        final AppPreference preference = new AppPreference(mContext);
178        doReturn(preference).when(mController).createAppPreference(any(Context.class));
179        mController.displayPreference(mScreen);
180        mController.updateState(mCategory);
181
182        final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
183
184        preference.performClick();
185
186        verify(context).startActivity(intent.capture());
187
188        assertThat(intent.getValue().getStringExtra(EXTRA_SHOW_FRAGMENT))
189                .isEqualTo(AppInfoDashboardFragment.class.getName());
190    }
191
192    private static ArgumentMatcher<Preference> titleMatches(String expected) {
193        return preference -> TextUtils.equals(expected, preference.getTitle());
194    }
195
196    private List<RecentLocationApps.Request> createMockRequests(int count) {
197        List<RecentLocationApps.Request> requests = new ArrayList<>();
198        for (int i = 0; i < count; i++) {
199            // Add mock requests
200            Request req = mock(Request.class, "request" + i);
201            requests.add(req);
202            // Map mock AppPreferences with mock requests
203            String title = "appTitle" + i;
204            AppPreference appPreference = mock(AppPreference.class, "AppPreference" + i);
205            doReturn(title).when(appPreference).getTitle();
206            doReturn(appPreference)
207                .when(mController).createAppPreference(any(Context.class), eq(req));
208        }
209        return requests;
210    }
211}