Implemented integration tests to test restoring search query
* Added perl tools to generate json files for music and movie data * Upgraded build tools version, sdk compile version, and support libraries as this is required by the test packages. * Added new product flavor instrumentationTest to make sure assets and settings required for testing do not also ship with a release version of Kore
This commit is contained in:
parent
e338636bb7
commit
6f60442e7b
|
@ -38,6 +38,13 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
productFlavors {
|
||||
instrumentationTest {
|
||||
applicationId "org.xbmc.kore.instrumentationtest"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
// debug {
|
||||
// minifyEnabled true
|
||||
|
@ -94,5 +101,32 @@ dependencies {
|
|||
compile 'com.astuetz:pagerslidingtabstrip:1.0.1'
|
||||
compile 'com.melnykov:floatingactionbutton:1.3.0'
|
||||
|
||||
androidTestCompile 'com.android.support.test:runner:0.5'
|
||||
androidTestCompile 'com.android.support.test:rules:0.5'
|
||||
androidTestCompile 'org.hamcrest:hamcrest-library:1.3'
|
||||
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
|
||||
androidTestCompile 'com.android.support.test.espresso:espresso-idling-resource:2.2.2'
|
||||
androidTestCompile 'com.android.support:support-v13:23.4.0'
|
||||
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
}
|
||||
|
||||
// Get the path to ADB. Required when running tests directly from Android Studio.
|
||||
// Source: http://stackoverflow.com/a/26771087/112705
|
||||
def adb = android.getAdbExe().toString()
|
||||
|
||||
// Source: http://stackoverflow.com/q/29908110/112705
|
||||
afterEvaluate {
|
||||
task grantAnimationPermissionDev(type: Exec, dependsOn: 'installInstrumentationTestDebug') {
|
||||
println("Executing: $adb shell pm grant $android.productFlavors.instrumentationTest.applicationId android.permission.SET_ANIMATION_SCALE")
|
||||
commandLine "$adb shell pm grant $android.productFlavors.instrumentationTest.applicationId android.permission.SET_ANIMATION_SCALE".split(' ')
|
||||
}
|
||||
|
||||
// When launching individual tests from Android Studio, it seems that only the assemble tasks
|
||||
// get called directly, not the install* versions
|
||||
tasks.each { task ->
|
||||
if (task.name.startsWith('assembleInstrumentationTestDebugAndroidTest')) {
|
||||
task.dependsOn grantAnimationPermissionDev
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package org.xbmc.kore;
|
||||
|
||||
import android.app.Application;
|
||||
import android.test.ApplicationTestCase;
|
||||
|
||||
/**
|
||||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
|
||||
*/
|
||||
public class ApplicationTest extends ApplicationTestCase<Application> {
|
||||
public ApplicationTest() {
|
||||
super(Application.class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* Copyright 2016 Martijn Brekhof. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.xbmc.kore.testhelpers;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
|
||||
import org.xbmc.kore.host.HostInfo;
|
||||
import org.xbmc.kore.host.HostManager;
|
||||
import org.xbmc.kore.jsonrpc.ApiException;
|
||||
import org.xbmc.kore.jsonrpc.method.AudioLibrary;
|
||||
import org.xbmc.kore.jsonrpc.method.VideoLibrary;
|
||||
import org.xbmc.kore.jsonrpc.type.AudioType;
|
||||
import org.xbmc.kore.jsonrpc.type.LibraryType;
|
||||
import org.xbmc.kore.jsonrpc.type.VideoType;
|
||||
import org.xbmc.kore.provider.MediaContract;
|
||||
import org.xbmc.kore.provider.MediaProvider;
|
||||
import org.xbmc.kore.service.library.SyncUtils;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Database {
|
||||
public static final String TAG = LogUtils.makeLogTag(Database.class);
|
||||
|
||||
public static HostInfo fill(Context context) throws ApiException, IOException {
|
||||
MediaProvider mediaProvider = new MediaProvider();
|
||||
mediaProvider.setContext(context);
|
||||
mediaProvider.onCreate();
|
||||
|
||||
HostInfo hostInfo = addHost(context);
|
||||
|
||||
insertMovies(context, hostInfo.getId());
|
||||
insertArtists(context, hostInfo.getId());
|
||||
insertGenres(context, hostInfo.getId());
|
||||
insertAlbums(context, hostInfo.getId());
|
||||
insertSongs(context, hostInfo.getId());
|
||||
|
||||
return hostInfo;
|
||||
}
|
||||
|
||||
public static void flush(Context context, HostInfo hostInfo) {
|
||||
context.getContentResolver()
|
||||
.delete(MediaContract.Hosts.buildHostUri(hostInfo.getId()), null, null);
|
||||
}
|
||||
|
||||
private static HostInfo addHost(Context context) {
|
||||
return HostManager.getInstance(context).addHost("TestHost", "127.0.0.1", 1, 80, 9090, null, null, "52:54:00:12:35:02", 9, false, 9777);
|
||||
}
|
||||
|
||||
private static void insertMovies(Context context, int hostId) throws ApiException, IOException {
|
||||
VideoLibrary.GetMovies getMovies = new VideoLibrary.GetMovies();
|
||||
String result = Utils.readFile(context, "Video.Details.Movie.json");
|
||||
ArrayList<VideoType.DetailsMovie> movieList = (ArrayList) getMovies.resultFromJson(result);
|
||||
|
||||
|
||||
ContentValues movieValuesBatch[] = new ContentValues[movieList.size()];
|
||||
int castCount = 0;
|
||||
|
||||
// Iterate on each movie
|
||||
for (int i = 0; i < movieList.size(); i++) {
|
||||
VideoType.DetailsMovie movie = movieList.get(i);
|
||||
movieValuesBatch[i] = SyncUtils.contentValuesFromMovie(hostId, movie);
|
||||
castCount += movie.cast.size();
|
||||
}
|
||||
|
||||
context.getContentResolver().bulkInsert(MediaContract.Movies.CONTENT_URI, movieValuesBatch);
|
||||
|
||||
ContentValues movieCastValuesBatch[] = new ContentValues[castCount];
|
||||
int count = 0;
|
||||
// Iterate on each movie/cast
|
||||
for (VideoType.DetailsMovie movie : movieList) {
|
||||
for (VideoType.Cast cast : movie.cast) {
|
||||
movieCastValuesBatch[count] = SyncUtils.contentValuesFromCast(hostId, cast);
|
||||
movieCastValuesBatch[count].put(MediaContract.MovieCastColumns.MOVIEID, movie.movieid);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
context.getContentResolver().bulkInsert(MediaContract.MovieCast.CONTENT_URI, movieCastValuesBatch);
|
||||
}
|
||||
|
||||
private static void insertArtists(Context context, int hostId) throws ApiException, IOException {
|
||||
AudioLibrary.GetArtists getArtists = new AudioLibrary.GetArtists(false);
|
||||
String result = Utils.readFile(context, "AudioLibrary.GetArtists.json");
|
||||
ArrayList<AudioType.DetailsArtist> artistList = (ArrayList) getArtists.resultFromJson(result).items;
|
||||
|
||||
ContentValues artistValuesBatch[] = new ContentValues[artistList.size()];
|
||||
for (int i = 0; i < artistList.size(); i++) {
|
||||
AudioType.DetailsArtist artist = artistList.get(i);
|
||||
artistValuesBatch[i] = SyncUtils.contentValuesFromArtist(hostId, artist);
|
||||
}
|
||||
|
||||
context.getContentResolver().bulkInsert(MediaContract.Artists.CONTENT_URI, artistValuesBatch);
|
||||
}
|
||||
|
||||
private static void insertGenres(Context context, int hostId) throws ApiException, IOException {
|
||||
AudioLibrary.GetGenres getGenres = new AudioLibrary.GetGenres();
|
||||
ArrayList<LibraryType.DetailsGenre> genreList = (ArrayList) getGenres.resultFromJson(Utils.readFile(context, "AudioLibrary.GetGenres.json"));
|
||||
|
||||
ContentValues genresValuesBatch[] = new ContentValues[genreList.size()];
|
||||
for (int i = 0; i < genreList.size(); i++) {
|
||||
LibraryType.DetailsGenre genre = genreList.get(i);
|
||||
genresValuesBatch[i] = SyncUtils.contentValuesFromAudioGenre(hostId, genre);
|
||||
}
|
||||
|
||||
context.getContentResolver().bulkInsert(MediaContract.AudioGenres.CONTENT_URI, genresValuesBatch);
|
||||
}
|
||||
|
||||
private static void insertAlbums(Context context, int hostId) throws ApiException, IOException {
|
||||
AudioLibrary.GetAlbums getAlbums = new AudioLibrary.GetAlbums();
|
||||
String result = Utils.readFile(context, "AudioLibrary.GetAlbums.json");
|
||||
ArrayList<AudioType.DetailsAlbum> albumList = (ArrayList) getAlbums.resultFromJson(result).items;
|
||||
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
|
||||
ContentValues albumValuesBatch[] = new ContentValues[albumList.size()];
|
||||
int artistsCount = 0, genresCount = 0;
|
||||
for (int i = 0; i < albumList.size(); i++) {
|
||||
AudioType.DetailsAlbum album = albumList.get(i);
|
||||
albumValuesBatch[i] = SyncUtils.contentValuesFromAlbum(hostId, album);
|
||||
|
||||
artistsCount += album.artistid.size();
|
||||
genresCount += album.genreid.size();
|
||||
}
|
||||
contentResolver.bulkInsert(MediaContract.Albums.CONTENT_URI, albumValuesBatch);
|
||||
|
||||
// Iterate on each album, collect the artists and the genres and insert them
|
||||
ContentValues albumArtistsValuesBatch[] = new ContentValues[artistsCount];
|
||||
ContentValues albumGenresValuesBatch[] = new ContentValues[genresCount];
|
||||
int artistCount = 0, genreCount = 0;
|
||||
for (AudioType.DetailsAlbum album : albumList) {
|
||||
for (int artistId : album.artistid) {
|
||||
albumArtistsValuesBatch[artistCount] = new ContentValues();
|
||||
albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.HOST_ID, hostId);
|
||||
albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.ALBUMID, album.albumid);
|
||||
albumArtistsValuesBatch[artistCount].put(MediaContract.AlbumArtists.ARTISTID, artistId);
|
||||
artistCount++;
|
||||
}
|
||||
|
||||
for (int genreId : album.genreid) {
|
||||
albumGenresValuesBatch[genreCount] = new ContentValues();
|
||||
albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.HOST_ID, hostId);
|
||||
albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.ALBUMID, album.albumid);
|
||||
albumGenresValuesBatch[genreCount].put(MediaContract.AlbumGenres.GENREID, genreId);
|
||||
genreCount++;
|
||||
}
|
||||
}
|
||||
|
||||
contentResolver.bulkInsert(MediaContract.AlbumArtists.CONTENT_URI, albumArtistsValuesBatch);
|
||||
contentResolver.bulkInsert(MediaContract.AlbumGenres.CONTENT_URI, albumGenresValuesBatch);
|
||||
}
|
||||
|
||||
private static void insertSongs(Context context, int hostId) throws ApiException, IOException {
|
||||
AudioLibrary.GetSongs getSongs = new AudioLibrary.GetSongs();
|
||||
ArrayList<AudioType.DetailsSong> songList = (ArrayList) getSongs.resultFromJson(Utils.readFile(context, "AudioLibrary.GetSongs.json")).items;
|
||||
|
||||
ContentValues songValuesBatch[] = new ContentValues[songList.size()];
|
||||
for (int i = 0; i < songList.size(); i++) {
|
||||
AudioType.DetailsSong song = songList.get(i);
|
||||
songValuesBatch[i] = SyncUtils.contentValuesFromSong(hostId, song);
|
||||
}
|
||||
context.getContentResolver().bulkInsert(MediaContract.Songs.CONTENT_URI, songValuesBatch);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright 2016 Martijn Brekhof. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.xbmc.kore.testhelpers;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.support.test.espresso.Espresso;
|
||||
import android.support.test.espresso.NoMatchingViewException;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
|
||||
import org.xbmc.kore.R;
|
||||
|
||||
import static android.support.test.espresso.Espresso.onData;
|
||||
import static android.support.test.espresso.Espresso.onView;
|
||||
import static android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu;
|
||||
import static android.support.test.espresso.action.ViewActions.clearText;
|
||||
import static android.support.test.espresso.action.ViewActions.click;
|
||||
import static android.support.test.espresso.action.ViewActions.typeText;
|
||||
import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
|
||||
import static android.support.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.anything;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
|
||||
public class EspressoTestUtils {
|
||||
|
||||
public static void rotateDevice(Activity activity) {
|
||||
int orientation
|
||||
= activity.getResources().getConfiguration().orientation;
|
||||
activity.setRequestedOrientation(
|
||||
(orientation == Configuration.ORIENTATION_PORTRAIT) ?
|
||||
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE :
|
||||
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks a menu item regardless if it is in the overflow menu or
|
||||
* visible as icon in the action bar
|
||||
* @param activity
|
||||
* @param name Name of the menu item in the overflow menu
|
||||
* @param resourceId Resource identifier of the menu item
|
||||
*/
|
||||
public static void clickMenuItem(Activity activity, String name, int resourceId) {
|
||||
try {
|
||||
onView(withId(resourceId)).perform(click());
|
||||
} catch (NoMatchingViewException e) {
|
||||
openActionBarOverflowOrOptionsMenu(activity);
|
||||
//Use onData as item might not be visible in the View without scrolling
|
||||
onData(allOf(
|
||||
Matchers.withMenuTitle(name)))
|
||||
.perform(click());
|
||||
}
|
||||
}
|
||||
|
||||
public static void clickHomeButton() {
|
||||
onView(withId(android.R.id.home)).perform(click());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the search menu item and enters the given search query
|
||||
* @param activity
|
||||
* @param query
|
||||
*/
|
||||
public static void enterSearchQuery(Activity activity, String query) {
|
||||
EspressoTestUtils.clickMenuItem(activity, activity.getString(R.string.action_search), R.id.action_search);
|
||||
|
||||
onView(isAssignableFrom(AutoCompleteTextView.class))
|
||||
.perform(click(), typeText(query));
|
||||
|
||||
Espresso.closeSoftKeyboard();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the search menu item and clears the search query by entering the empty string
|
||||
* @param activity
|
||||
*/
|
||||
public static void clearSearchQuery(Activity activity) {
|
||||
EspressoTestUtils.clickMenuItem(activity, activity.getString(R.string.action_search), R.id.action_search);
|
||||
|
||||
onView(isAssignableFrom(AutoCompleteTextView.class))
|
||||
.perform(click(), clearText());
|
||||
|
||||
Espresso.closeSoftKeyboard();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the search query by pressing the X button
|
||||
* @param activity
|
||||
*/
|
||||
public static void clearSearchQueryXButton(Activity activity) {
|
||||
try {
|
||||
onView(withId(R.id.search_close_btn)).perform(click());
|
||||
} catch (NoMatchingViewException e) {
|
||||
EspressoTestUtils.clickMenuItem(activity, activity.getString(R.string.action_search), R.id.action_search);
|
||||
onView(withId(R.id.search_close_btn)).perform(click());
|
||||
}
|
||||
Espresso.closeSoftKeyboard();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a click on an item in an adapter view, such as GridView or ListView
|
||||
* @param position
|
||||
* @param resourceId
|
||||
*/
|
||||
public static void clickAdapterViewItem(int position, int resourceId) {
|
||||
onData(anything()).inAdapterView(allOf(withId(resourceId), isDisplayed()))
|
||||
.atPosition(position).perform(click());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that SearchView contains the given text
|
||||
* @param query text that SearchView should contain
|
||||
*/
|
||||
public static void checkTextInSearchQuery(String query) {
|
||||
onView(isAssignableFrom(AutoCompleteTextView.class)).check(matches(withText(query)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the list contains item(s) matching search query
|
||||
* @param query text each element must contain
|
||||
* @param listSize amount of elements expected in list
|
||||
*/
|
||||
public static void checkListMatchesSearchQuery(String query, int listSize, int resourceId) {
|
||||
onView(allOf(withId(resourceId), isDisplayed()))
|
||||
.check(matches(Matchers.withOnlyMatchingDataItems(Matchers.withItemContent(containsString(query)))));
|
||||
onView(allOf(withId(resourceId), isDisplayed()))
|
||||
.check(matches(Matchers.withAdapterSize(listSize)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if search action view does not exist in the current view hierarchy
|
||||
*/
|
||||
public static void checkSearchMenuCollapsed() {
|
||||
onView(isAssignableFrom(AutoCompleteTextView.class)).check(doesNotExist());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2016 Martijn Brekhof. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.xbmc.kore.testhelpers;
|
||||
|
||||
import android.support.test.espresso.IdlingResource;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
|
||||
public class LoaderIdlingResource implements IdlingResource {
|
||||
|
||||
private ResourceCallback mResourceCallback;
|
||||
private LoaderManager loaderManager;
|
||||
|
||||
public LoaderIdlingResource(LoaderManager loaderManager) {
|
||||
this.loaderManager = loaderManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return LoaderIdlingResource.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdleNow() {
|
||||
boolean idle = !loaderManager.hasRunningLoaders();
|
||||
if (idle && mResourceCallback != null) {
|
||||
mResourceCallback.onTransitionToIdle();
|
||||
}
|
||||
return idle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
|
||||
mResourceCallback = resourceCallback;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright 2016 Martijn Brekhof. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.xbmc.kore.testhelpers;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.support.test.espresso.matcher.BoundedMatcher;
|
||||
import android.support.test.espresso.matcher.CursorMatchers;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Adapter;
|
||||
import android.widget.AdapterView;
|
||||
|
||||
import org.hamcrest.BaseMatcher;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
|
||||
public class Matchers {
|
||||
public static MenuItemTitleMatcher withMenuTitle(String title) {
|
||||
return new MenuItemTitleMatcher(title);
|
||||
}
|
||||
|
||||
public static class MenuItemTitleMatcher extends BaseMatcher<Object> {
|
||||
private final String title;
|
||||
public MenuItemTitleMatcher(String title) { this.title = title; }
|
||||
|
||||
@Override
|
||||
public boolean matches(Object o) {
|
||||
if (o instanceof MenuItem) {
|
||||
return ((MenuItem) o).getTitle().equals(title);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public void describeTo(Description description) { }
|
||||
}
|
||||
|
||||
public static Matcher<View> withListSize(final int size) {
|
||||
return new TypeSafeMatcher<View>() {
|
||||
@Override public boolean matchesSafely(final View view) {
|
||||
if (!(view instanceof ViewGroup))
|
||||
return false;
|
||||
|
||||
return ((ViewGroup) view).getChildCount() == size;
|
||||
}
|
||||
|
||||
@Override public void describeTo(final Description description) {
|
||||
description.appendText("List should have " + size + " item(s)");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Matcher<View> withAdapterSize(final int size) {
|
||||
return new TypeSafeMatcher<View>() {
|
||||
@Override
|
||||
protected boolean matchesSafely(View view) {
|
||||
if (!(view instanceof AdapterView))
|
||||
return false;
|
||||
|
||||
return ((AdapterView) view).getCount() == size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("Adapter should have " + size + " item(s)");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Matcher<View> withOnlyMatchingDataItems(final Matcher<Object> dataMatcher) {
|
||||
return new TypeSafeMatcher<View>() {
|
||||
@Override
|
||||
protected boolean matchesSafely(View view) {
|
||||
if (!(view instanceof AdapterView))
|
||||
return false;
|
||||
|
||||
Adapter adapter = ((AdapterView) view).getAdapter();
|
||||
for (int i = 0; i < adapter.getCount(); i++) {
|
||||
if (! dataMatcher.matches(adapter.getItem(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("withOnlyMatchingDataItems: ");
|
||||
dataMatcher.describeTo(description);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Matcher<Object> withItemContent(final Matcher<String> textMatcher) {
|
||||
return new BoundedMatcher<Object, Cursor>(Cursor.class) {
|
||||
@Override
|
||||
protected boolean matchesSafely(Cursor item) {
|
||||
for (int i = 0; i < item.getColumnCount();i++) {
|
||||
switch (item.getType(i)) {
|
||||
case Cursor.FIELD_TYPE_STRING:
|
||||
if (CursorMatchers.withRowString(i, textMatcher).matches(item))
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("withItemContent: ");
|
||||
textMatcher.describeTo(description);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Copyright 2016 Martijn Brekhof. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.xbmc.kore.testhelpers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.IBinder;
|
||||
import android.support.test.rule.ActivityTestRule;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
import android.util.Log;
|
||||
|
||||
import org.xbmc.kore.R;
|
||||
import org.xbmc.kore.host.HostInfo;
|
||||
import org.xbmc.kore.host.HostManager;
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class Utils {
|
||||
private static final String TAG = LogUtils.makeLogTag(Utils.class);
|
||||
|
||||
private static final String ANIMATION_PERMISSION = "android.permission.SET_ANIMATION_SCALE";
|
||||
private static final float DISABLED = 0.0f;
|
||||
private static final float DEFAULT = 1.0f;
|
||||
|
||||
private static boolean isInitialized;
|
||||
|
||||
private static HostInfo hostInfo;
|
||||
private static Context context;
|
||||
|
||||
public static String readFile(Context context, String filename) throws IOException {
|
||||
InputStream is = context.getAssets().open(filename);
|
||||
|
||||
int size = is.available();
|
||||
|
||||
byte[] buffer = new byte[size];
|
||||
|
||||
is.read(buffer);
|
||||
|
||||
is.close();
|
||||
|
||||
return new String(buffer, "UTF-8");
|
||||
}
|
||||
|
||||
public static void closeDrawer(final ActivityTestRule<?> activityTestRule) throws Throwable {
|
||||
activityTestRule.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
DrawerLayout drawerLayout = (DrawerLayout) activityTestRule.getActivity().findViewById(R.id.drawer_layout);
|
||||
drawerLayout.closeDrawers();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void initialize(ActivityTestRule<?> activityTestRule) throws Throwable {
|
||||
if (isInitialized)
|
||||
return;
|
||||
|
||||
context = activityTestRule.getActivity();
|
||||
|
||||
disableAnimations();
|
||||
|
||||
hostInfo = Database.fill(context);
|
||||
|
||||
HostManager.getInstance(context).switchHost(hostInfo);
|
||||
Utils.closeDrawer(activityTestRule);
|
||||
|
||||
isInitialized = true;
|
||||
}
|
||||
|
||||
public static void cleanup() {
|
||||
Database.flush(context, hostInfo);
|
||||
|
||||
enableAnimations();
|
||||
|
||||
isInitialized = false;
|
||||
}
|
||||
|
||||
private static void disableAnimations() {
|
||||
int permStatus = context.checkCallingOrSelfPermission(ANIMATION_PERMISSION);
|
||||
if (permStatus == PackageManager.PERMISSION_GRANTED) {
|
||||
setSystemAnimationsScale(DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
private static void enableAnimations() {
|
||||
int permStatus = context.checkCallingOrSelfPermission(ANIMATION_PERMISSION);
|
||||
if (permStatus == PackageManager.PERMISSION_GRANTED) {
|
||||
setSystemAnimationsScale(DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setSystemAnimationsScale(float animationScale) {
|
||||
try {
|
||||
Class<?> windowManagerStubClazz = Class.forName("android.view.IWindowManager$Stub");
|
||||
Method asInterface = windowManagerStubClazz.getDeclaredMethod("asInterface", IBinder.class);
|
||||
Class<?> serviceManagerClazz = Class.forName("android.os.ServiceManager");
|
||||
Method getService = serviceManagerClazz.getDeclaredMethod("getService", String.class);
|
||||
Class<?> windowManagerClazz = Class.forName("android.view.IWindowManager");
|
||||
Method setAnimationScales = windowManagerClazz.getDeclaredMethod("setAnimationScales", float[].class);
|
||||
Method getAnimationScales = windowManagerClazz.getDeclaredMethod("getAnimationScales");
|
||||
|
||||
IBinder windowManagerBinder = (IBinder) getService.invoke(null, "window");
|
||||
Object windowManagerObj = asInterface.invoke(null, windowManagerBinder);
|
||||
float[] currentScales = (float[]) getAnimationScales.invoke(windowManagerObj);
|
||||
for (int i = 0; i < currentScales.length; i++) {
|
||||
currentScales[i] = animationScale;
|
||||
}
|
||||
setAnimationScales.invoke(windowManagerObj, new Object[]{currentScales});
|
||||
} catch (Exception e) {
|
||||
Log.e("SystemAnimations", "Could not change animation scale to " + animationScale + " :'(");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
* Copyright 2016 Martijn Brekhof. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.xbmc.kore.tests.ui;
|
||||
|
||||
import android.support.test.espresso.Espresso;
|
||||
import android.support.test.rule.ActivityTestRule;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.LargeTest;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.xbmc.kore.R;
|
||||
import org.xbmc.kore.testhelpers.EspressoTestUtils;
|
||||
import org.xbmc.kore.testhelpers.LoaderIdlingResource;
|
||||
import org.xbmc.kore.testhelpers.Utils;
|
||||
import org.xbmc.kore.ui.MoviesActivity;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@LargeTest
|
||||
public class RestoreSearchQueryListFragmentTest {
|
||||
|
||||
private final String SEARCH_QUERY = "Room";
|
||||
private final int SEARCH_QUERY_LIST_SIZE = 2;
|
||||
private final int COMPLETE_LIST_SIZE = 300;
|
||||
|
||||
private LoaderIdlingResource loaderIdlingResource;
|
||||
|
||||
@Rule
|
||||
public ActivityTestRule<MoviesActivity> mActivityRule = new ActivityTestRule<>(
|
||||
MoviesActivity.class);
|
||||
|
||||
@Before
|
||||
public void setUp() throws Throwable {
|
||||
Utils.initialize(mActivityRule);
|
||||
loaderIdlingResource = new LoaderIdlingResource(mActivityRule.getActivity().getSupportLoaderManager());
|
||||
Espresso.registerIdlingResources(loaderIdlingResource);
|
||||
Utils.closeDrawer(mActivityRule);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
Espresso.unregisterIdlingResources(loaderIdlingResource);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void cleanup() {
|
||||
Utils.cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple test that checks if search query results in expected item(s)
|
||||
*/
|
||||
@Test
|
||||
public void simpleSearchTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), SEARCH_QUERY);
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(SEARCH_QUERY, SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple test that checks if search query is restored after device rotate
|
||||
*/
|
||||
@Test
|
||||
public void simpleRotateTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), SEARCH_QUERY);
|
||||
EspressoTestUtils.rotateDevice(mActivityRule.getActivity());
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(SEARCH_QUERY, SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if search query is restored when user returns to list fragment from
|
||||
* detail fragment
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Click on list item
|
||||
* 3. Press back
|
||||
* 4. Result: search query entered at 1. should be restored in search field
|
||||
*/
|
||||
@Test
|
||||
public void searchClickBackTest() {
|
||||
EspressoTestUtils.clearSearchQuery(mActivityRule.getActivity());
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), SEARCH_QUERY);
|
||||
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
|
||||
Espresso.pressBack();
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(SEARCH_QUERY, SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if search query is restored when user returns to list fragment from
|
||||
* detail fragment when device is rotated while on detail fragment
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Click on list item
|
||||
* 3. Rotate device
|
||||
* 4. Press back
|
||||
* 5. Result: search query entered at 1. should be restored in search field
|
||||
*/
|
||||
@Test
|
||||
public void searchClickRotateBackTest() {
|
||||
EspressoTestUtils.clearSearchQuery(mActivityRule.getActivity());
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), SEARCH_QUERY);
|
||||
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
|
||||
EspressoTestUtils.rotateDevice(mActivityRule.getActivity());
|
||||
Espresso.pressBack();
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(SEARCH_QUERY, SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if saved search query is cleared when user clears the
|
||||
* search query view
|
||||
*
|
||||
* UI interaction flow tested
|
||||
* 1. Enter search query
|
||||
* 2. Click on list item
|
||||
* 3. Return to list
|
||||
* 4. Clear search query
|
||||
* 5. Click on list item
|
||||
* 6. Return to list
|
||||
* 7. Result: search query should be empty and collapsed
|
||||
*/
|
||||
@Test
|
||||
public void searchClickBackClearSearchClickBackTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), SEARCH_QUERY);
|
||||
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
|
||||
Espresso.pressBack();
|
||||
EspressoTestUtils.clearSearchQuery(mActivityRule.getActivity());
|
||||
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
|
||||
Espresso.pressBack();
|
||||
|
||||
EspressoTestUtils.checkSearchMenuCollapsed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if after restoring search query the search query is cleared
|
||||
* when user presses back again.
|
||||
*
|
||||
* UI interaction flow tested
|
||||
* 1. Enter search query
|
||||
* 2. Click on list item
|
||||
* 3. Return to list
|
||||
* 4. Press back
|
||||
* 7. Result: search query should be cleared, collapsed, and list should show everything
|
||||
*/
|
||||
@Test
|
||||
public void searchClickBackBackTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), SEARCH_QUERY);
|
||||
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
|
||||
Espresso.pressBack();
|
||||
Espresso.pressBack();
|
||||
|
||||
EspressoTestUtils.checkSearchMenuCollapsed();
|
||||
EspressoTestUtils.checkListMatchesSearchQuery("", COMPLETE_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if pressing back clears a previous search
|
||||
*
|
||||
* UI interaction flow tested
|
||||
* 1. Enter search query
|
||||
* 2. Press back
|
||||
* 3. Result: search query should be cleared, collapsed, and list should show everything
|
||||
*/
|
||||
public void searchBackTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), SEARCH_QUERY);
|
||||
Espresso.pressBack();
|
||||
EspressoTestUtils.checkSearchMenuCollapsed();
|
||||
EspressoTestUtils.checkListMatchesSearchQuery("", COMPLETE_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if after restoring the search query pressing home button up clears a previous search
|
||||
*
|
||||
* UI interaction flow tested
|
||||
* 1. Enter search query
|
||||
* 2. Click on list item
|
||||
* 3. Press back
|
||||
* 4. Press home button
|
||||
* 5. Result: search query should be cleared, collapsed, and list should show everything
|
||||
*/
|
||||
public void searchClickBackUpTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), SEARCH_QUERY);
|
||||
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
|
||||
Espresso.pressBack();
|
||||
EspressoTestUtils.clickHomeButton();
|
||||
EspressoTestUtils.checkSearchMenuCollapsed();
|
||||
EspressoTestUtils.checkListMatchesSearchQuery("", COMPLETE_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if pressing home button up clears a previous search
|
||||
*
|
||||
* UI interaction flow tested
|
||||
* 1. Enter search query
|
||||
* 2. Press home button
|
||||
* 3. Result: search query should be cleared, collapsed, and list should show everything
|
||||
*/
|
||||
public void searchUpTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), SEARCH_QUERY);
|
||||
Espresso.pressBack();
|
||||
EspressoTestUtils.clickHomeButton();
|
||||
EspressoTestUtils.checkSearchMenuCollapsed();
|
||||
EspressoTestUtils.checkListMatchesSearchQuery("", COMPLETE_LIST_SIZE, R.id.list);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,338 @@
|
|||
/*
|
||||
* Copyright 2016 Martijn Brekhof. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.xbmc.kore.tests.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.test.espresso.Espresso;
|
||||
import android.support.test.rule.ActivityTestRule;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.LargeTest;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.xbmc.kore.R;
|
||||
import org.xbmc.kore.testhelpers.EspressoTestUtils;
|
||||
import org.xbmc.kore.testhelpers.LoaderIdlingResource;
|
||||
import org.xbmc.kore.testhelpers.Utils;
|
||||
import org.xbmc.kore.ui.MusicActivity;
|
||||
|
||||
import static android.support.test.espresso.Espresso.onView;
|
||||
import static android.support.test.espresso.action.ViewActions.click;
|
||||
import static android.support.test.espresso.action.ViewActions.swipeLeft;
|
||||
import static android.support.test.espresso.action.ViewActions.swipeRight;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withText;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@LargeTest
|
||||
public class RestoreSearchQueryViewPagerTest {
|
||||
|
||||
private final String ARTIST_SEARCH_QUERY = "Ben";
|
||||
private final int ARTIST_SEARCH_QUERY_LIST_SIZE = 2;
|
||||
private final String ALBUMS_SEARCH_QUERY = "tes";
|
||||
private final int ALBUM_SEARCH_QUERY_LIST_SIZE = 3;
|
||||
private final int ARTIST_COMPLETE_LIST_SIZE = 224;
|
||||
private final int ALBUM_COMPLETE_LIST_SIZE = 231;
|
||||
|
||||
private LoaderIdlingResource loaderIdlingResource;
|
||||
|
||||
@Rule
|
||||
public ActivityTestRule<MusicActivity> mActivityRule = new ActivityTestRule<>(
|
||||
MusicActivity.class);
|
||||
|
||||
@Before
|
||||
public void setUp() throws Throwable {
|
||||
Utils.initialize(mActivityRule);
|
||||
loaderIdlingResource = new LoaderIdlingResource(mActivityRule.getActivity().getSupportLoaderManager());
|
||||
Espresso.registerIdlingResources(loaderIdlingResource);
|
||||
Utils.closeDrawer(mActivityRule);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
Espresso.unregisterIdlingResources(loaderIdlingResource);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void cleanup() {
|
||||
Utils.cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple test that checks if search query results in expected item(s)
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Result: search query entered at 1. should show in search field and list should match search query
|
||||
*/
|
||||
@Test
|
||||
public void simpleSearchTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), ARTIST_SEARCH_QUERY);
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(ARTIST_SEARCH_QUERY, ARTIST_SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple test that checks if search query is restored after device rotate
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Rotate device
|
||||
* 3. Result: search query entered at 1. should show in search field and list should match search query
|
||||
*/
|
||||
@Test
|
||||
public void simpleRotateTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.rotateDevice(mActivityRule.getActivity());
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(ARTIST_SEARCH_QUERY, ARTIST_SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if search query is restored when user returns to list fragment from
|
||||
* detail fragment
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Click on list item
|
||||
* 3. Press back
|
||||
* 4. Result: search query entered at 1. should be restored in search field
|
||||
*/
|
||||
@Test
|
||||
public void searchClickBackTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
|
||||
Espresso.pressBack();
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(ARTIST_SEARCH_QUERY, ARTIST_SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if search query is restored when user returns to list fragment from
|
||||
* detail fragment when device is rotated while on detail fragment
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Click on list item
|
||||
* 3. Rotate device
|
||||
* 4. Press back
|
||||
* 5. Result: search query entered at 1. should be restored in search field
|
||||
*/
|
||||
@Test
|
||||
public void searchClickRotateBackTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.clickAdapterViewItem(0, R.id.list);
|
||||
EspressoTestUtils.rotateDevice(mActivityRule.getActivity());
|
||||
Espresso.pressBack();
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(ARTIST_SEARCH_QUERY, ARTIST_SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if search query is cleared when switching to
|
||||
* different tab in the TabAdapter
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Switch to Albums tab
|
||||
* 3. Result: search query should be cleared
|
||||
*/
|
||||
@Test
|
||||
public void searchSwitchTabTest() {
|
||||
Activity activity = mActivityRule.getActivity();
|
||||
|
||||
EspressoTestUtils.enterSearchQuery(activity, ARTIST_SEARCH_QUERY);
|
||||
clickAlbumsTab();
|
||||
|
||||
EspressoTestUtils.clickMenuItem(activity, activity.getString(R.string.action_search), R.id.action_search);
|
||||
EspressoTestUtils.checkTextInSearchQuery("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if search query is still cleared when
|
||||
* device is rotated after switching to a different tab
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Switch to Albums tab
|
||||
* 3. Rotate device
|
||||
* 4. Open search menu item
|
||||
* 5. Result: search query should be cleared
|
||||
*/
|
||||
@Test
|
||||
public void searchSwitchTabRotateTest() {
|
||||
Activity activity = mActivityRule.getActivity();
|
||||
|
||||
EspressoTestUtils.enterSearchQuery(activity, ARTIST_SEARCH_QUERY);
|
||||
clickAlbumsTab();
|
||||
EspressoTestUtils.rotateDevice(activity);
|
||||
EspressoTestUtils.clickMenuItem(activity, activity.getString(R.string.action_search), R.id.action_search);
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery("");
|
||||
EspressoTestUtils.checkListMatchesSearchQuery("", ALBUM_COMPLETE_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if search query is restored when returning
|
||||
* to the original tab
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Switch to Albums tab
|
||||
* 3. Switch to Artists tab
|
||||
* 4. Result: search query entered at 1. should show in search field and list should match search query
|
||||
*/
|
||||
@Test
|
||||
public void searchSwitchTabReturnTest() {
|
||||
EspressoTestUtils.enterSearchQuery(mActivityRule.getActivity(), ARTIST_SEARCH_QUERY);
|
||||
clickAlbumsTab();
|
||||
clickArtistsTab();
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(ARTIST_SEARCH_QUERY, ARTIST_SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Tests if search query is restored when returning
|
||||
* to the original tab after switching to a different
|
||||
* tab and rotating the device
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Switch to Albums tab
|
||||
* 3. Rotated device
|
||||
* 4. Switch to Artists tab
|
||||
* 5. Result: search query entered at 1. should show in search field and list should match search query
|
||||
*/
|
||||
@Test
|
||||
public void searchSwitchTabRotateReturnTest() {
|
||||
Activity activity = mActivityRule.getActivity();
|
||||
|
||||
EspressoTestUtils.enterSearchQuery(activity, ARTIST_SEARCH_QUERY);
|
||||
clickAlbumsTab();
|
||||
EspressoTestUtils.rotateDevice(activity);
|
||||
clickArtistsTab();
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(ARTIST_SEARCH_QUERY, ARTIST_SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if search query is still cleared when user clears a previous
|
||||
* search query and switches to a different tab and returns to the
|
||||
* original tab
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Clear search query
|
||||
* 3. Switch to Albums tab
|
||||
* 4. Switch to Artists tab
|
||||
* 5. Click search menu item
|
||||
* 6. Result: search query should be cleared and list should contain all items
|
||||
*/
|
||||
@Test
|
||||
public void searchClearSwitchTabSwitchBack() {
|
||||
Activity activity = mActivityRule.getActivity();
|
||||
|
||||
EspressoTestUtils.enterSearchQuery(activity, ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.checkTextInSearchQuery(ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.clearSearchQuery(activity);
|
||||
clickAlbumsTab();
|
||||
clickArtistsTab();
|
||||
EspressoTestUtils.clickMenuItem(activity, activity.getString(R.string.action_search), R.id.action_search);
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery("");
|
||||
EspressoTestUtils.checkListMatchesSearchQuery("", ARTIST_COMPLETE_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same test as {@link #searchClearSwitchTabSwitchBack()} but this time clearing performed using X button
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query
|
||||
* 2. Clear search query
|
||||
* 3. Switch to Albums tab
|
||||
* 4. Switch to Artists tab
|
||||
* 5. Click search menu item using X button
|
||||
* 6. Result: search query should be cleared and list should contain all items
|
||||
*/
|
||||
@Test
|
||||
public void searchSwitchTabSwitchBackClearUsingXButtonSwitchTabSwitchBack() {
|
||||
Activity activity = mActivityRule.getActivity();
|
||||
|
||||
EspressoTestUtils.enterSearchQuery(activity, ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.checkTextInSearchQuery(ARTIST_SEARCH_QUERY);
|
||||
clickAlbumsTab();
|
||||
clickArtistsTab();
|
||||
EspressoTestUtils.clearSearchQueryXButton(activity);
|
||||
clickAlbumsTab();
|
||||
clickArtistsTab();
|
||||
|
||||
EspressoTestUtils.checkSearchMenuCollapsed();
|
||||
EspressoTestUtils.clickMenuItem(activity, activity.getString(R.string.action_search), R.id.action_search);
|
||||
EspressoTestUtils.checkTextInSearchQuery("");
|
||||
EspressoTestUtils.checkListMatchesSearchQuery("", ARTIST_COMPLETE_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if search queries for separate tabs are restored correctly
|
||||
*
|
||||
* UI interaction flow tested:
|
||||
* 1. Enter search query artists tab
|
||||
* 2. Enter search query albums tab
|
||||
* 3. Switch to Artists tab
|
||||
* 4. Result: search query entered at 1. should show in search field and list should match search query
|
||||
* 5. Switch to Albums tab
|
||||
* 6. Result: search query entered at 2. should show in search field and list should match search query
|
||||
*/
|
||||
@Test
|
||||
public void searchArtistsSearchAlbumsSwitchArtists() {
|
||||
Activity activity = mActivityRule.getActivity();
|
||||
|
||||
EspressoTestUtils.enterSearchQuery(activity, ARTIST_SEARCH_QUERY);
|
||||
clickAlbumsTab();
|
||||
EspressoTestUtils.enterSearchQuery(activity, ALBUMS_SEARCH_QUERY);
|
||||
clickArtistsTab();
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(ARTIST_SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(ARTIST_SEARCH_QUERY, ARTIST_SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
|
||||
clickAlbumsTab();
|
||||
|
||||
EspressoTestUtils.checkTextInSearchQuery(ALBUMS_SEARCH_QUERY);
|
||||
EspressoTestUtils.checkListMatchesSearchQuery(ALBUMS_SEARCH_QUERY, ALBUM_SEARCH_QUERY_LIST_SIZE, R.id.list);
|
||||
}
|
||||
|
||||
private void clickAlbumsTab() {
|
||||
onView(withId(R.id.pager_tab_strip)).perform(swipeLeft());
|
||||
onView(withText(R.string.albums)).perform(click());
|
||||
}
|
||||
|
||||
private void clickArtistsTab() {
|
||||
onView(withId(R.id.pager_tab_strip)).perform(swipeRight());
|
||||
onView(withText(R.string.artists)).perform(click());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.xbmc.kore">
|
||||
|
||||
<!-- For espresso testing purposes, this is removed in live builds, but not in dev builds -->
|
||||
<uses-permission android:name="android.permission.SET_ANIMATION_SCALE" />
|
||||
|
||||
</manifest>
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,247 @@
|
|||
{
|
||||
"id" : "libGenres",
|
||||
"result" : {
|
||||
"limits" : {
|
||||
"total" : 39,
|
||||
"start" : 0,
|
||||
"end" : 39
|
||||
},
|
||||
"genres" : [
|
||||
{
|
||||
"genreid" : 9,
|
||||
"title" : "Ambient",
|
||||
"label" : "Ambient",
|
||||
"thumbnail" : ""
|
||||
},
|
||||
{
|
||||
"label" : "Bluegrass",
|
||||
"thumbnail" : "",
|
||||
"genreid" : 33,
|
||||
"title" : "Bluegrass"
|
||||
},
|
||||
{
|
||||
"title" : "Blues",
|
||||
"genreid" : 1,
|
||||
"thumbnail" : "",
|
||||
"label" : "Blues"
|
||||
},
|
||||
{
|
||||
"label" : "Blues Compilation",
|
||||
"thumbnail" : "",
|
||||
"genreid" : 35,
|
||||
"title" : "Blues Compilation"
|
||||
},
|
||||
{
|
||||
"thumbnail" : "",
|
||||
"label" : "Brutal Death Metal",
|
||||
"title" : "Brutal Death Metal",
|
||||
"genreid" : 32
|
||||
},
|
||||
{
|
||||
"title" : "Celtic",
|
||||
"genreid" : 24,
|
||||
"thumbnail" : "",
|
||||
"label" : "Celtic"
|
||||
},
|
||||
{
|
||||
"genreid" : 11,
|
||||
"title" : "Classic for Kids",
|
||||
"label" : "Classic for Kids",
|
||||
"thumbnail" : ""
|
||||
},
|
||||
{
|
||||
"thumbnail" : "",
|
||||
"label" : "Classical",
|
||||
"title" : "Classical",
|
||||
"genreid" : 3
|
||||
},
|
||||
{
|
||||
"genreid" : 13,
|
||||
"title" : "Country",
|
||||
"label" : "Country",
|
||||
"thumbnail" : ""
|
||||
},
|
||||
{
|
||||
"title" : "Dancehall",
|
||||
"genreid" : 31,
|
||||
"thumbnail" : "",
|
||||
"label" : "Dancehall"
|
||||
},
|
||||
{
|
||||
"title" : "Easy Listening",
|
||||
"genreid" : 20,
|
||||
"thumbnail" : "",
|
||||
"label" : "Easy Listening"
|
||||
},
|
||||
{
|
||||
"title" : "Folk",
|
||||
"genreid" : 4,
|
||||
"thumbnail" : "",
|
||||
"label" : "Folk"
|
||||
},
|
||||
{
|
||||
"label" : "Folklore",
|
||||
"thumbnail" : "",
|
||||
"genreid" : 22,
|
||||
"title" : "Folklore"
|
||||
},
|
||||
{
|
||||
"label" : "Goregrind",
|
||||
"thumbnail" : "",
|
||||
"genreid" : 14,
|
||||
"title" : "Goregrind"
|
||||
},
|
||||
{
|
||||
"genreid" : 8,
|
||||
"title" : "Grind Core",
|
||||
"label" : "Grind Core",
|
||||
"thumbnail" : ""
|
||||
},
|
||||
{
|
||||
"title" : "Grindcore",
|
||||
"genreid" : 18,
|
||||
"thumbnail" : "",
|
||||
"label" : "Grindcore"
|
||||
},
|
||||
{
|
||||
"label" : "Hardcore",
|
||||
"thumbnail" : "",
|
||||
"genreid" : 12,
|
||||
"title" : "Hardcore"
|
||||
},
|
||||
{
|
||||
"label" : "Hardcore Thrash",
|
||||
"thumbnail" : "",
|
||||
"genreid" : 30,
|
||||
"title" : "Hardcore Thrash"
|
||||
},
|
||||
{
|
||||
"title" : "Hip-Hop",
|
||||
"genreid" : 17,
|
||||
"thumbnail" : "",
|
||||
"label" : "Hip-Hop"
|
||||
},
|
||||
{
|
||||
"title" : "Instrumental",
|
||||
"genreid" : 39,
|
||||
"thumbnail" : "",
|
||||
"label" : "Instrumental"
|
||||
},
|
||||
{
|
||||
"genreid" : 5,
|
||||
"title" : "Jazz",
|
||||
"label" : "Jazz",
|
||||
"thumbnail" : ""
|
||||
},
|
||||
{
|
||||
"label" : "Jazz+Funk",
|
||||
"thumbnail" : "",
|
||||
"genreid" : 21,
|
||||
"title" : "Jazz+Funk"
|
||||
},
|
||||
{
|
||||
"thumbnail" : "",
|
||||
"label" : "Metal",
|
||||
"title" : "Metal",
|
||||
"genreid" : 10
|
||||
},
|
||||
{
|
||||
"label" : "Oldies",
|
||||
"thumbnail" : "",
|
||||
"genreid" : 36,
|
||||
"title" : "Oldies"
|
||||
},
|
||||
{
|
||||
"genreid" : 23,
|
||||
"title" : "Other",
|
||||
"label" : "Other",
|
||||
"thumbnail" : ""
|
||||
},
|
||||
{
|
||||
"title" : "Pop",
|
||||
"genreid" : 25,
|
||||
"thumbnail" : "",
|
||||
"label" : "Pop"
|
||||
},
|
||||
{
|
||||
"thumbnail" : "",
|
||||
"label" : "porno grind",
|
||||
"title" : "porno grind",
|
||||
"genreid" : 26
|
||||
},
|
||||
{
|
||||
"title" : "Punk",
|
||||
"genreid" : 7,
|
||||
"thumbnail" : "",
|
||||
"label" : "Punk"
|
||||
},
|
||||
{
|
||||
"label" : "Punk Rock",
|
||||
"thumbnail" : "",
|
||||
"genreid" : 16,
|
||||
"title" : "Punk Rock"
|
||||
},
|
||||
{
|
||||
"title" : "Rap",
|
||||
"genreid" : 28,
|
||||
"thumbnail" : "",
|
||||
"label" : "Rap"
|
||||
},
|
||||
{
|
||||
"title" : "Reggae",
|
||||
"genreid" : 2,
|
||||
"thumbnail" : "",
|
||||
"label" : "Reggae"
|
||||
},
|
||||
{
|
||||
"title" : "Rhythm and Blues",
|
||||
"genreid" : 19,
|
||||
"thumbnail" : "",
|
||||
"label" : "Rhythm and Blues"
|
||||
},
|
||||
{
|
||||
"title" : "Rock",
|
||||
"genreid" : 34,
|
||||
"thumbnail" : "",
|
||||
"label" : "Rock"
|
||||
},
|
||||
{
|
||||
"genreid" : 15,
|
||||
"title" : "Salsa",
|
||||
"label" : "Salsa",
|
||||
"thumbnail" : ""
|
||||
},
|
||||
{
|
||||
"thumbnail" : "",
|
||||
"label" : "Soul",
|
||||
"title" : "Soul",
|
||||
"genreid" : 29
|
||||
},
|
||||
{
|
||||
"thumbnail" : "",
|
||||
"label" : "Soundtrack",
|
||||
"title" : "Soundtrack",
|
||||
"genreid" : 6
|
||||
},
|
||||
{
|
||||
"thumbnail" : "",
|
||||
"label" : "Sprachkurs",
|
||||
"title" : "Sprachkurs",
|
||||
"genreid" : 27
|
||||
},
|
||||
{
|
||||
"title" : "Κινηματογραφική",
|
||||
"genreid" : 37,
|
||||
"thumbnail" : "",
|
||||
"label" : "Κινηματογραφική"
|
||||
},
|
||||
{
|
||||
"title" : "ゲーム音楽",
|
||||
"genreid" : 38,
|
||||
"thumbnail" : "",
|
||||
"label" : "ゲーム音楽"
|
||||
}
|
||||
]
|
||||
},
|
||||
"jsonrpc" : "2.0"
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -17,11 +17,13 @@ package org.xbmc.kore.provider;
|
|||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.UriMatcher;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
import android.provider.BaseColumns;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.xbmc.kore.utils.LogUtils;
|
||||
import org.xbmc.kore.utils.SelectionBuilder;
|
||||
|
@ -36,6 +38,8 @@ public class MediaProvider extends ContentProvider {
|
|||
|
||||
private MediaDatabase mOpenHelper;
|
||||
|
||||
private Context context;
|
||||
|
||||
private static final UriMatcher sUriMatcher = buildUriMatcher();
|
||||
|
||||
private static final int HOSTS_LIST = 100;
|
||||
|
@ -218,9 +222,16 @@ public class MediaProvider extends ContentProvider {
|
|||
return matcher;
|
||||
}
|
||||
|
||||
public void setContext(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
mOpenHelper = new MediaDatabase(getContext());
|
||||
if (context == null) {
|
||||
context = getContext();
|
||||
}
|
||||
mOpenHelper = new MediaDatabase(context);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -341,7 +352,7 @@ public class MediaProvider extends ContentProvider {
|
|||
throw new UnsupportedOperationException("Unsuported uri: " + uri);
|
||||
}
|
||||
}
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
context.getContentResolver().notifyChange(uri, null);
|
||||
|
||||
return insertedUri;
|
||||
}
|
||||
|
@ -433,10 +444,10 @@ public class MediaProvider extends ContentProvider {
|
|||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
context.getContentResolver().notifyChange(uri, null);
|
||||
|
||||
LogUtils.LOGD(TAG, "Bulk insert finished for uri (" + uri +
|
||||
") in (ms): " + (System.currentTimeMillis() - startTime));
|
||||
") in (ms): " + (System.currentTimeMillis() - startTime));
|
||||
return values.length;
|
||||
}
|
||||
|
||||
|
@ -470,7 +481,7 @@ public class MediaProvider extends ContentProvider {
|
|||
final SelectionBuilder builder = buildQuerySelection(uri, match);
|
||||
int result = builder.where(selection, selectionArgs)
|
||||
.update(db, values);
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
context.getContentResolver().notifyChange(uri, null);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -483,7 +494,7 @@ public class MediaProvider extends ContentProvider {
|
|||
int result = builder.where(selection, selectionArgs)
|
||||
.delete(db);
|
||||
LogUtils.LOGD(TAG, "delete(uri=" + uri + "). Rows affected: " + result);
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
context.getContentResolver().notifyChange(uri, null);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -73,9 +73,9 @@ public abstract class AbstractCursorListFragment extends AbstractListFragment
|
|||
|
||||
// The search filter to use in the loader
|
||||
private String searchFilter = null;
|
||||
private boolean loaderLoading;
|
||||
private String savedSearchFilter;
|
||||
private boolean supportsSearch;
|
||||
private boolean loaderLoading;
|
||||
|
||||
private SearchView searchView;
|
||||
|
||||
|
@ -308,6 +308,7 @@ public abstract class AbstractCursorListFragment extends AbstractListFragment
|
|||
adapter.swapCursor(cursor);
|
||||
// To prevent the empty text from appearing on the first load, set it now
|
||||
emptyView.setText(getString(R.string.swipe_down_to_refresh));
|
||||
loaderLoading = false;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
*.json
|
|
@ -0,0 +1,51 @@
|
|||
#
|
||||
# Copyright 2016 Martijn Brekhof. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
package JsonTools;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter qw(import);
|
||||
use Cpanel::JSON::XS qw(encode_json decode_json);
|
||||
use LWP::UserAgent;
|
||||
|
||||
our @EXPORT_OK = qw(sendJsonRequest writeJsonFile);
|
||||
|
||||
sub sendJsonRequest($$) {
|
||||
my $url = shift;
|
||||
my $json = shift;
|
||||
|
||||
my $jsonrequest = encode_json($json);
|
||||
my $req = HTTP::Request->new( 'POST', $url );
|
||||
$req->header( 'Content-Type' => 'application/json-rpc' );
|
||||
$req->content( $jsonrequest );
|
||||
my $ua = LWP::UserAgent->new;
|
||||
my $response = $ua->request($req);
|
||||
|
||||
if (! $response->is_success) {
|
||||
die $response->status_line;
|
||||
}
|
||||
|
||||
return decode_json($response->decoded_content);
|
||||
}
|
||||
|
||||
sub writeJsonFile($$) {
|
||||
my $filename = shift;
|
||||
my $json = shift;
|
||||
open(FH, ">", $filename);
|
||||
print FH Cpanel::JSON::XS->new->pretty(1)->encode($json);
|
||||
close(FH);
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
#!/usr/bin/perl
|
||||
#
|
||||
# Copyright 2016 Martijn Brekhof. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Types::Serialiser;
|
||||
use JsonTools qw(sendJsonRequest writeJsonFile);
|
||||
|
||||
sub getMovies() {
|
||||
my $jsonrequest = {
|
||||
"jsonrpc" => "2.0",
|
||||
"method" => "VideoLibrary.GetMovies",
|
||||
"params" => {
|
||||
"limits" => { "start" => 0, "end" => 300 },
|
||||
"properties" => [
|
||||
"title",
|
||||
"genre",
|
||||
"year",
|
||||
"rating",
|
||||
"director",
|
||||
"trailer",
|
||||
"tagline",
|
||||
"plot",
|
||||
"plotoutline",
|
||||
"originaltitle",
|
||||
"lastplayed",
|
||||
"playcount",
|
||||
"writer",
|
||||
"studio",
|
||||
"mpaa",
|
||||
"cast",
|
||||
"country",
|
||||
"imdbnumber",
|
||||
"runtime",
|
||||
"set",
|
||||
"showlink",
|
||||
"streamdetails",
|
||||
"top250",
|
||||
"votes",
|
||||
"fanart",
|
||||
"thumbnail",
|
||||
"file",
|
||||
"sorttitle",
|
||||
"resume",
|
||||
"setid",
|
||||
"dateadded",
|
||||
"tag",
|
||||
"art"
|
||||
],
|
||||
"sort" => { "order" => "ascending", "method" => "label", "ignorearticle" => Types::Serialiser::true }
|
||||
},
|
||||
"id" => "libMovies"
|
||||
};
|
||||
|
||||
return sendJsonRequest("http://127.0.0.1:8080/jsonrpc", $jsonrequest);
|
||||
}
|
||||
|
||||
writeJsonFile("Video.Details.Movie.json", getMovies);
|
|
@ -0,0 +1,169 @@
|
|||
#!/usr/bin/perl
|
||||
#
|
||||
# Copyright 2016 Martijn Brekhof. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Types::Serialiser;
|
||||
use JsonTools qw(sendJsonRequest writeJsonFile);
|
||||
|
||||
my $url = "http://127.0.0.1:8080/jsonrpc";
|
||||
|
||||
sub getSongs($) {
|
||||
my $artist = shift;
|
||||
|
||||
my $jsonrequest = {
|
||||
"jsonrpc" => "2.0",
|
||||
"method" => "AudioLibrary.GetSongs",
|
||||
"params" => {
|
||||
"properties" => [
|
||||
"title",
|
||||
"artist",
|
||||
"albumartist",
|
||||
"genre",
|
||||
"year",
|
||||
"rating",
|
||||
"album",
|
||||
"track",
|
||||
"duration",
|
||||
"comment",
|
||||
"lyrics",
|
||||
"musicbrainztrackid",
|
||||
"musicbrainzartistid",
|
||||
"musicbrainzalbumid",
|
||||
"musicbrainzalbumartistid",
|
||||
"playcount",
|
||||
"fanart",
|
||||
"thumbnail",
|
||||
"file",
|
||||
"albumid",
|
||||
"lastplayed",
|
||||
"disc",
|
||||
"genreid",
|
||||
"artistid",
|
||||
"displayartist",
|
||||
"albumartistid"
|
||||
],
|
||||
"sort" => { "order" => "ascending", "method" => "label", "ignorearticle" => Types::Serialiser::true }
|
||||
},
|
||||
"id" => "libSongs",
|
||||
};
|
||||
|
||||
if ( defined $artist ) {
|
||||
$jsonrequest->{"params"}{"filter"} = {
|
||||
"field" => "artist",
|
||||
"operator" => "is",
|
||||
"value" => [ $artist ]
|
||||
};
|
||||
}
|
||||
|
||||
return sendJsonRequest($url, $jsonrequest);
|
||||
}
|
||||
|
||||
sub getArtists() {
|
||||
my $jsonrequest = {
|
||||
"jsonrpc" => "2.0",
|
||||
"method" => "AudioLibrary.GetArtists",
|
||||
"params" => {
|
||||
"limits" => { "start" => 0, "end" => 300 },
|
||||
"properties" => [
|
||||
"instrument",
|
||||
"style",
|
||||
"mood",
|
||||
"born",
|
||||
"formed",
|
||||
"description",
|
||||
"genre",
|
||||
"died",
|
||||
"disbanded",
|
||||
"yearsactive",
|
||||
"musicbrainzartistid",
|
||||
"fanart",
|
||||
"thumbnail"
|
||||
],
|
||||
"sort" => { "order" => "ascending", "method" => "label", "ignorearticle" => Types::Serialiser::true }
|
||||
},
|
||||
"id" => "libArtists"
|
||||
};
|
||||
|
||||
return sendJsonRequest($url, $jsonrequest);
|
||||
}
|
||||
|
||||
sub getGenres() {
|
||||
my $jsonrequest = {
|
||||
"jsonrpc" => "2.0",
|
||||
"method" => "AudioLibrary.GetGenres",
|
||||
"params" => {
|
||||
"properties" => [
|
||||
"title",
|
||||
"thumbnail"
|
||||
],
|
||||
"sort" => { "order" => "ascending", "method" => "label", "ignorearticle" => Types::Serialiser::true }
|
||||
},
|
||||
"id" => "libGenres"
|
||||
};
|
||||
|
||||
return sendJsonRequest($url, $jsonrequest);
|
||||
}
|
||||
|
||||
sub getAlbums($) {
|
||||
my $artist = shift;
|
||||
my $jsonrequest = {
|
||||
"jsonrpc" => "2.0",
|
||||
"method" => "AudioLibrary.GetAlbums",
|
||||
"params" => {
|
||||
"properties" => [
|
||||
"title",
|
||||
"description",
|
||||
"artist",
|
||||
"genre",
|
||||
"theme",
|
||||
"mood",
|
||||
"style",
|
||||
"type",
|
||||
"albumlabel",
|
||||
"rating",
|
||||
"year",
|
||||
"musicbrainzalbumid",
|
||||
"musicbrainzalbumartistid",
|
||||
"fanart",
|
||||
"thumbnail",
|
||||
"playcount",
|
||||
"genreid",
|
||||
"artistid",
|
||||
"displayartist"
|
||||
],
|
||||
"sort" => { "order" => "ascending", "method" => "label", "ignorearticle" => Types::Serialiser::true }
|
||||
},
|
||||
"id" => "libAlbums"
|
||||
};
|
||||
|
||||
if ( defined $artist ) {
|
||||
$jsonrequest->{"params"}{"filter"} = {
|
||||
"field" => "artist",
|
||||
"operator" => "is",
|
||||
"value" => "$artist"
|
||||
};
|
||||
}
|
||||
|
||||
return sendJsonRequest($url, $jsonrequest);
|
||||
}
|
||||
|
||||
writeJsonFile("AudioLibrary.GetGenres.json", getGenres());
|
||||
writeJsonFile("AudioLibrary.GetArtists.json", getArtists());
|
||||
writeJsonFile("AudioLibrary.GetAlbums.json", getAlbums(undef));
|
||||
writeJsonFile("AudioLibrary.GetSongs.json", getSongs(undef));
|
Loading…
Reference in New Issue