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  */
16 
17 package com.android.tv.search;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static junit.framework.Assert.fail;
22 
23 import static org.mockito.Mockito.reset;
24 import static org.mockito.Mockito.verify;
25 
26 import android.app.SearchManager;
27 import android.content.ContentResolver;
28 import android.content.ContentUris;
29 import android.database.Cursor;
30 import android.net.Uri;
31 
32 import com.android.tv.common.flags.impl.SettableFlagsModule;
33 import com.android.tv.data.ProgramImpl;
34 import com.android.tv.data.api.Program;
35 import com.android.tv.perf.PerformanceMonitor;
36 import com.android.tv.perf.stub.StubPerformanceMonitor;
37 import com.android.tv.search.LocalSearchProvider.SearchResult;
38 import com.android.tv.search.LocalSearchProviderTest.TestAppComponent.TestAppModule;
39 import com.android.tv.testing.TestSingletonApp;
40 import com.android.tv.testing.constants.ConfigConstants;
41 import com.android.tv.testing.robo.ContentProviders;
42 
43 import dagger.Component;
44 import dagger.Module;
45 import dagger.Provides;
46 import dagger.android.AndroidInjectionModule;
47 import dagger.android.AndroidInjector;
48 import dagger.android.DispatchingAndroidInjector;
49 import dagger.android.HasAndroidInjector;
50 
51 import org.junit.Before;
52 import org.junit.Test;
53 import org.junit.runner.RunWith;
54 import org.mockito.Mock;
55 import org.mockito.MockitoAnnotations;
56 import org.robolectric.RobolectricTestRunner;
57 import org.robolectric.RuntimeEnvironment;
58 import org.robolectric.annotation.Config;
59 
60 import java.util.ArrayList;
61 import java.util.List;
62 
63 import javax.inject.Inject;
64 import javax.inject.Singleton;
65 
66 /** Unit test for {@link LocalSearchProvider}. */
67 @RunWith(RobolectricTestRunner.class)
68 @Config(sdk = ConfigConstants.SDK, application = LocalSearchProviderTest.TestApp.class)
69 public class LocalSearchProviderTest {
70 
71     /** Test app for {@link LocalSearchProviderTest} */
72     public static class TestApp extends TestSingletonApp implements HasAndroidInjector {
73         @Inject DispatchingAndroidInjector<Object> mDispatchingAndroidProvider;
74 
75         @Override
onCreate()76         public void onCreate() {
77             super.onCreate();
78             DaggerLocalSearchProviderTest_TestAppComponent.builder()
79                     .settableFlagsModule(flagsModule)
80                     .build()
81                     .inject(this);
82         }
83 
84         @Override
androidInjector()85         public AndroidInjector<Object> androidInjector() {
86             return mDispatchingAndroidProvider;
87         }
88     }
89 
90     @Component(
91             modules = {
92                 AndroidInjectionModule.class,
93                 SettableFlagsModule.class,
94                 LocalSearchProvider.Module.class,
95                 TestAppModule.class
96             })
97     @Singleton
98     interface TestAppComponent extends AndroidInjector<TestApp> {
99         @Module
100         abstract static class TestAppModule {
101             @Provides
102             @Singleton
providePerformanceMonitor()103             static PerformanceMonitor providePerformanceMonitor() {
104                 return new StubPerformanceMonitor();
105             }
106         }
107     }
108 
109     private final Program mProgram1 =
110             new ProgramImpl.Builder()
111                     .setTitle("Dummy program")
112                     .setDescription("Dummy program season 2")
113                     .setPosterArtUri("FakeUri")
114                     .setStartTimeUtcMillis(1516674000000L)
115                     .setEndTimeUtcMillis(1516677000000L)
116                     .setChannelId(7)
117                     .setVideoWidth(1080)
118                     .setVideoHeight(960)
119                     .build();
120 
121     private final String mAuthority = "com.android.tv.search";
122     private final String mKeyword = "mKeyword";
123     private final Uri mBaseSearchUri =
124             Uri.parse(
125                     "content://"
126                             + mAuthority
127                             + "/"
128                             + SearchManager.SUGGEST_URI_PATH_QUERY
129                             + "/"
130                             + mKeyword);
131 
132     private final Uri mWrongSearchUri =
133             Uri.parse("content://" + mAuthority + "/wrong_path/" + mKeyword);
134 
135     private LocalSearchProvider mProvider;
136     private ContentResolver mContentResolver;
137 
138     @Mock private SearchInterface mMockSearchInterface;
139     private final FakeSearchInterface mFakeSearchInterface = new FakeSearchInterface();
140 
141     @Before
setup()142     public void setup() {
143         MockitoAnnotations.initMocks(this);
144         mProvider = ContentProviders.register(LocalSearchProvider.class, mAuthority);
145         mContentResolver = RuntimeEnvironment.application.getContentResolver();
146     }
147 
148     @Test
testQuery_normalUri()149     public void testQuery_normalUri() {
150         verifyQueryWithArguments(null, null);
151         verifyQueryWithArguments(1, null);
152         verifyQueryWithArguments(null, 1);
153         verifyQueryWithArguments(1, 1);
154     }
155 
156     @Test
testQuery_invalidUri()157     public void testQuery_invalidUri() {
158         try (Cursor c = mContentResolver.query(mWrongSearchUri, null, null, null, null)) {
159             fail("Query with invalid URI should fail.");
160         } catch (IllegalArgumentException e) {
161             // Success.
162         }
163     }
164 
165     @Test
testQuery_invalidLimit()166     public void testQuery_invalidLimit() {
167         verifyQueryWithArguments(-1, null);
168     }
169 
170     @Test
testQuery_invalidAction()171     public void testQuery_invalidAction() {
172         verifyQueryWithArguments(null, SearchInterface.ACTION_TYPE_START - 1);
173         verifyQueryWithArguments(null, SearchInterface.ACTION_TYPE_END + 1);
174     }
175 
verifyQueryWithArguments(Integer limit, Integer action)176     private void verifyQueryWithArguments(Integer limit, Integer action) {
177         mProvider.setSearchInterface(mMockSearchInterface);
178         Uri uri = mBaseSearchUri;
179         if (limit != null || action != null) {
180             Uri.Builder builder = uri.buildUpon();
181             if (limit != null) {
182                 builder.appendQueryParameter(
183                         SearchManager.SUGGEST_PARAMETER_LIMIT, limit.toString());
184             }
185             if (action != null) {
186                 builder.appendQueryParameter(
187                         LocalSearchProvider.SUGGEST_PARAMETER_ACTION, action.toString());
188             }
189             uri = builder.build();
190         }
191         try (Cursor c = mContentResolver.query(uri, null, null, null, null)) {
192             // Do nothing.
193         }
194         int expectedLimit =
195                 limit == null || limit <= 0 ? LocalSearchProvider.DEFAULT_SEARCH_LIMIT : limit;
196         int expectedAction =
197                 (action == null
198                                 || action < SearchInterface.ACTION_TYPE_START
199                                 || action > SearchInterface.ACTION_TYPE_END)
200                         ? LocalSearchProvider.DEFAULT_SEARCH_ACTION
201                         : action;
202         verify(mMockSearchInterface).search(mKeyword, expectedLimit, expectedAction);
203         reset(mMockSearchInterface);
204     }
205 
206     @Test
testGetType()207     public void testGetType() {
208         assertThat(mProvider.getType(mBaseSearchUri)).isEqualTo(SearchManager.SUGGEST_MIME_TYPE);
209     }
210 
211     @Test
query_empty()212     public void query_empty() {
213         mProvider.setSearchInterface(mFakeSearchInterface);
214         Cursor cursor = mContentResolver.query(mBaseSearchUri, null, null, null, null);
215         assertThat(cursor.moveToNext()).isFalse();
216     }
217 
218     @Test
query_program1()219     public void query_program1() {
220         mProvider.setSearchInterface(mFakeSearchInterface);
221         mFakeSearchInterface.mPrograms.add(mProgram1);
222         Uri uri =
223                 Uri.parse(
224                         "content://"
225                                 + mAuthority
226                                 + "/"
227                                 + SearchManager.SUGGEST_URI_PATH_QUERY
228                                 + "/"
229                                 + "Dummy");
230         Cursor cursor = mContentResolver.query(uri, null, null, null, null);
231         assertThat(fromCursor(cursor)).containsExactly(FakeSearchInterface.fromProgram(mProgram1));
232     }
233 
fromCursor(Cursor cursor)234     private List<SearchResult> fromCursor(Cursor cursor) {
235         List<SearchResult> results = new ArrayList<>();
236         while (cursor.moveToNext()) {
237             SearchResult.Builder result = SearchResult.builder();
238             int i = 0;
239             result.setTitle(cursor.getString(i++));
240             result.setDescription(cursor.getString(i++));
241             result.setImageUri(cursor.getString(i++));
242             result.setIntentAction(cursor.getString(i++));
243             String intentData = cursor.getString(i++);
244             result.setIntentData(intentData);
245             result.setIntentExtraData(cursor.getString(i++));
246             result.setContentType(cursor.getString(i++));
247             result.setIsLive(cursor.getString(i++).equals("1"));
248             result.setVideoWidth(Integer.valueOf(cursor.getString(i++)));
249             result.setVideoHeight(Integer.valueOf(cursor.getString(i++)));
250             result.setDuration(Long.valueOf(cursor.getString(i++)));
251             result.setProgressPercentage(Integer.valueOf(cursor.getString(i)));
252             result.setChannelId(ContentUris.parseId(Uri.parse(intentData)));
253             results.add(result.build());
254         }
255         return results;
256     }
257 }
258