Skip to content

Commit

Permalink
Redesigned playing movies on device running Kore
Browse files Browse the repository at this point in the history
* Implemented a new widget "fabspeeddial"
   * Provides user with two options to play the media item. One
     option to play the item on Kodi, one to play it on the remote.
   * Replaced deprecated FAB button from
     com.melnykov:floatingactionbutton:1.3.0 with the FAB button from
     the design library
   * Implemented a busy indicator (pulsate) when fab button is clicked
     and JSON  API method is still pending
   * Added a setting to allow the user to disable local playback and
     revert back to the old behaviour
* Refactored AbstractFragmentInfo
   * Replaced RelativeLayout by CoordinatorLayout to support
     hiding/showing the FAB button when scrolling
   * Replaced the tree view observer to fade out art view when scrolling
     with a behavior for the CoordinaterLayout
* Removed empty theme file for v19
* Refactored HostConnection to allow new activities to attach its
  callbacks to any pending ApiMethod. This is required to support device
  configuration changes.
  • Loading branch information
poisdeux authored and SyncedSynapse committed Feb 9, 2018
1 parent 0cd91c3 commit e2c39e3
Show file tree
Hide file tree
Showing 32 changed files with 1,225 additions and 887 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ dependencies {
compile "com.android.support:cardview-v7:${supportLibVersion}"
compile "com.android.support:preference-v14:${supportLibVersion}"
compile "com.android.support:support-v13:${supportLibVersion}"
compile "com.android.support:design:${supportLibVersion}"

compile 'com.fasterxml.jackson.core:jackson-databind:2.5.2'
compile 'com.jakewharton:butterknife:6.1.0'
Expand All @@ -124,7 +125,6 @@ dependencies {
compile 'de.greenrobot:eventbus:2.4.0'
compile 'org.jmdns:jmdns:3.5.1'
compile 'com.astuetz:pagerslidingtabstrip:1.0.1'
compile 'com.melnykov:floatingactionbutton:1.3.0'
compile 'at.blogc:expandabletextview:1.0.3'
compile 'com.sothree.slidinguppanel:library:3.3.1'

Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/org/xbmc/kore/Settings.java
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ public static String getNavDrawerItemsPrefKey(int hostId) {
public static final String KEY_PREF_SINGLE_COLUMN = "pref_single_multi_column";
public static final boolean DEFAULT_PREF_SINGLE_COLUMN = false;

// Switch to remote
public static final String KEY_PREF_DISABLE_LOCAL_PLAY = "pref_disable_local_play";
public static final boolean DEFAULT_PREF_DISABLE_LOCAL_PLAY = false;

/**
* Determines the bit flags used by {@link DownloadManager.Request} to correspond to the enabled network connections
* from the settings screen.
Expand Down
102 changes: 78 additions & 24 deletions app/src/main/java/org/xbmc/kore/jsonrpc/HostConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.xbmc.kore.jsonrpc;

import android.annotation.SuppressLint;
import android.os.Handler;
import android.os.Process;
import android.text.TextUtils;
Expand Down Expand Up @@ -281,11 +282,13 @@ public void unregisterApplicationNotificationsObserver(ApplicationNotificationsO
}

/**
* Calls the a method on the server
* Calls the given method on the server
* This call is always asynchronous. The results will be posted, through the
* {@link ApiCallback callback} parameter, on the specified {@link android.os.Handler}.
*
* @param method Method object that represents the methood too call
* <BR/>
* If you need to update the callback and handler (e.g. due to a device configuration change)
* use {@link #updateClientCallback(int, ApiCallback, Handler)}
* @param method Method object that represents the methood too call
* @param callback {@link ApiCallback} to post the response to
* @param handler {@link Handler} to invoke callbacks on. When null, the
* callbacks are invoked on the same thread as the request.
Expand All @@ -297,13 +300,21 @@ public <T> void execute(final ApiMethod<T> method, final ApiCallback<T> callback
LogUtils.LOGD(TAG, "Starting method execute. Method: " + method.getMethodName() +
" on host: " + hostInfo.getJsonRpcHttpEndpoint());

if (protocol == PROTOCOL_TCP) {
/**
* Do not call this from the runnable below as it may cause a race condition
* with {@link #updateClientCallback(int, ApiCallback, Handler)}
*/
// Save this method/callback for any later response
addClientCallback(method, callback, handler);
}

// Launch background thread
Runnable command = new Runnable() {
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
if (protocol == PROTOCOL_HTTP) {
// executeThroughHttp(method, callback, handler);
executeThroughOkHttp(method, callback, handler);
} else {
executeThroughTcp(method, callback, handler);
Expand Down Expand Up @@ -342,6 +353,67 @@ public <T> Future<T> execute(ApiMethod<T> method) {
return ApiFuture.from(this, method);
}

/**
* Updates the client callback for the given {@link ApiMethod} if it is still pending.
* This can be used when the activity or fragment has been destroyed and recreated and
* you are still interested in the result of any pending {@link ApiMethod}
* @param methodId for which a new callback needs to be attached
* @param callback new callback that needs to be called for the new activity or fragment
* @param handler used to execute the callback on the UI thread
* @param <T> result type
* @return true if the {@link ApiMethod} was still pending, false otherwise.
*/
@SuppressWarnings("unchecked")
public <T> boolean updateClientCallback(final int methodId, final ApiCallback<T> callback,
final Handler handler) {

if (getProtocol() == PROTOCOL_HTTP)
return false;

synchronized (clientCallbacks) {
String id = String.valueOf(methodId);
if (clientCallbacks.containsKey(id)) {
clientCallbacks.put(id, new MethodCallInfo<>((ApiMethod<T>) clientCallbacks.get(id).method,
callback, handler));
return true;
}
return false;
}
}

/**
* Stores the method and callback to handle asynchronous responses.
* Note this is only needed for requests over TCP.
* @param method
* @param callback
* @param handler
* @param <T>
*/
private <T> void addClientCallback(final ApiMethod<T> method, final ApiCallback<T> callback,
final Handler handler) {

if (getProtocol() == PROTOCOL_HTTP)
return;

String methodId = String.valueOf(method.getId());

synchronized (clientCallbacks) {
if (clientCallbacks.containsKey(methodId)) {
if ((handler != null) && (callback != null)) {
handler.post(new Runnable() {
@Override
public void run() {
callback.onError(ApiException.API_METHOD_WITH_SAME_ID_ALREADY_EXECUTING,
"A method with the same Id is already executing");
}
});
}
return;
}
clientCallbacks.put(methodId, new MethodCallInfo<T>(method, callback, handler));
}
}

/**
* Sends the JSON RPC request through HTTP (using OkHttp library)
*/
Expand All @@ -355,7 +427,6 @@ private <T> void executeThroughOkHttp(final ApiMethod<T> method, final ApiCallba
.url(hostInfo.getJsonRpcHttpEndpoint())
.post(RequestBody.create(MEDIA_TYPE_JSON, jsonRequest))
.build();
LogUtils.LOGD(TAG, "Sending request via OkHttp: " + jsonRequest);
Response response = sendOkHttpRequest(client, request);
final T result = method.resultFromJson(parseJsonResponse(handleOkHttpResponse(response)));

Expand Down Expand Up @@ -517,25 +588,7 @@ private ObjectNode parseJsonResponse(String response) throws ApiException {
private <T> void executeThroughTcp(final ApiMethod<T> method, final ApiCallback<T> callback,
final Handler handler) {
String methodId = String.valueOf(method.getId());
try {
// Save this method/callback for later response
// Check if a method with this id is already running and raise an error if so
synchronized (clientCallbacks) {
if (clientCallbacks.containsKey(methodId)) {
if (callback != null) {
postOrRunNow(handler, new Runnable() {
@Override
public void run() {
callback.onError(ApiException.API_METHOD_WITH_SAME_ID_ALREADY_EXECUTING,
"A method with the same Id is already executing");
}
});
}
return;
}
clientCallbacks.put(methodId, new MethodCallInfo<T>(method, callback, handler));
}

try {
// TODO: Validate if this shouldn't be enclosed by a synchronized.
if (socket == null) {
// Open connection to the server and setup reader thread
Expand Down Expand Up @@ -843,6 +896,7 @@ public void run() {
});
}
}

clientCallbacks.clear();
}
}
Expand Down
Loading

0 comments on commit e2c39e3

Please sign in to comment.