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 {
|
buildTypes {
|
||||||
// debug {
|
// debug {
|
||||||
// minifyEnabled true
|
// minifyEnabled true
|
||||||
|
@ -94,5 +101,32 @@ dependencies {
|
||||||
compile 'com.astuetz:pagerslidingtabstrip:1.0.1'
|
compile 'com.astuetz:pagerslidingtabstrip:1.0.1'
|
||||||
compile 'com.melnykov:floatingactionbutton:1.3.0'
|
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'])
|
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.ContentProvider;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.UriMatcher;
|
import android.content.UriMatcher;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.provider.BaseColumns;
|
import android.provider.BaseColumns;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import org.xbmc.kore.utils.LogUtils;
|
import org.xbmc.kore.utils.LogUtils;
|
||||||
import org.xbmc.kore.utils.SelectionBuilder;
|
import org.xbmc.kore.utils.SelectionBuilder;
|
||||||
|
@ -36,6 +38,8 @@ public class MediaProvider extends ContentProvider {
|
||||||
|
|
||||||
private MediaDatabase mOpenHelper;
|
private MediaDatabase mOpenHelper;
|
||||||
|
|
||||||
|
private Context context;
|
||||||
|
|
||||||
private static final UriMatcher sUriMatcher = buildUriMatcher();
|
private static final UriMatcher sUriMatcher = buildUriMatcher();
|
||||||
|
|
||||||
private static final int HOSTS_LIST = 100;
|
private static final int HOSTS_LIST = 100;
|
||||||
|
@ -218,9 +222,16 @@ public class MediaProvider extends ContentProvider {
|
||||||
return matcher;
|
return matcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setContext(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreate() {
|
public boolean onCreate() {
|
||||||
mOpenHelper = new MediaDatabase(getContext());
|
if (context == null) {
|
||||||
|
context = getContext();
|
||||||
|
}
|
||||||
|
mOpenHelper = new MediaDatabase(context);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,7 +352,7 @@ public class MediaProvider extends ContentProvider {
|
||||||
throw new UnsupportedOperationException("Unsuported uri: " + uri);
|
throw new UnsupportedOperationException("Unsuported uri: " + uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getContext().getContentResolver().notifyChange(uri, null);
|
context.getContentResolver().notifyChange(uri, null);
|
||||||
|
|
||||||
return insertedUri;
|
return insertedUri;
|
||||||
}
|
}
|
||||||
|
@ -433,10 +444,10 @@ public class MediaProvider extends ContentProvider {
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction();
|
db.endTransaction();
|
||||||
}
|
}
|
||||||
getContext().getContentResolver().notifyChange(uri, null);
|
context.getContentResolver().notifyChange(uri, null);
|
||||||
|
|
||||||
LogUtils.LOGD(TAG, "Bulk insert finished for uri (" + uri +
|
LogUtils.LOGD(TAG, "Bulk insert finished for uri (" + uri +
|
||||||
") in (ms): " + (System.currentTimeMillis() - startTime));
|
") in (ms): " + (System.currentTimeMillis() - startTime));
|
||||||
return values.length;
|
return values.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -470,7 +481,7 @@ public class MediaProvider extends ContentProvider {
|
||||||
final SelectionBuilder builder = buildQuerySelection(uri, match);
|
final SelectionBuilder builder = buildQuerySelection(uri, match);
|
||||||
int result = builder.where(selection, selectionArgs)
|
int result = builder.where(selection, selectionArgs)
|
||||||
.update(db, values);
|
.update(db, values);
|
||||||
getContext().getContentResolver().notifyChange(uri, null);
|
context.getContentResolver().notifyChange(uri, null);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -483,7 +494,7 @@ public class MediaProvider extends ContentProvider {
|
||||||
int result = builder.where(selection, selectionArgs)
|
int result = builder.where(selection, selectionArgs)
|
||||||
.delete(db);
|
.delete(db);
|
||||||
LogUtils.LOGD(TAG, "delete(uri=" + uri + "). Rows affected: " + result);
|
LogUtils.LOGD(TAG, "delete(uri=" + uri + "). Rows affected: " + result);
|
||||||
getContext().getContentResolver().notifyChange(uri, null);
|
context.getContentResolver().notifyChange(uri, null);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,9 +73,9 @@ public abstract class AbstractCursorListFragment extends AbstractListFragment
|
||||||
|
|
||||||
// The search filter to use in the loader
|
// The search filter to use in the loader
|
||||||
private String searchFilter = null;
|
private String searchFilter = null;
|
||||||
|
private boolean loaderLoading;
|
||||||
private String savedSearchFilter;
|
private String savedSearchFilter;
|
||||||
private boolean supportsSearch;
|
private boolean supportsSearch;
|
||||||
private boolean loaderLoading;
|
|
||||||
|
|
||||||
private SearchView searchView;
|
private SearchView searchView;
|
||||||
|
|
||||||
|
@ -308,6 +308,7 @@ public abstract class AbstractCursorListFragment extends AbstractListFragment
|
||||||
adapter.swapCursor(cursor);
|
adapter.swapCursor(cursor);
|
||||||
// To prevent the empty text from appearing on the first load, set it now
|
// To prevent the empty text from appearing on the first load, set it now
|
||||||
emptyView.setText(getString(R.string.swipe_down_to_refresh));
|
emptyView.setText(getString(R.string.swipe_down_to_refresh));
|
||||||
|
loaderLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@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