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