From 2e7ac6af80cdac1e0c8be0325cd21536efd29c65 Mon Sep 17 00:00:00 2001 From: Tomer Froumin Date: Sat, 30 Jan 2016 12:50:16 +0200 Subject: [PATCH] Added support for pausing when phone in call --- app/src/main/AndroidManifest.xml | 8 + app/src/main/java/org/xbmc/kore/Settings.java | 4 + .../java/org/xbmc/kore/jsonrpc/ApiMethod.java | 20 ++- .../org/xbmc/kore/jsonrpc/method/Player.java | 26 ++- .../xbmc/kore/service/PauseCallService.java | 155 ++++++++++++++++++ .../java/org/xbmc/kore/ui/RemoteActivity.java | 2 - app/src/main/res/values/strings.xml | 3 + app/src/main/res/xml/preferences.xml | 5 + 8 files changed, 216 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/org/xbmc/kore/service/PauseCallService.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 18de800..23da927 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,6 +9,7 @@ + + + + + + + + diff --git a/app/src/main/java/org/xbmc/kore/Settings.java b/app/src/main/java/org/xbmc/kore/Settings.java index 1e58a87..c76bb23 100644 --- a/app/src/main/java/org/xbmc/kore/Settings.java +++ b/app/src/main/java/org/xbmc/kore/Settings.java @@ -53,6 +53,10 @@ public class Settings { public static final String KEY_PREF_SHOW_NOTIFICATION = "pref_show_notification"; public static final boolean DEFAULT_PREF_SHOW_NOTIFICATION = false; + // Pause during calls + public static final String KEY_PREF_PAUSE_DURING_CALLS = "pref_pause_during_calls"; + public static final boolean DEFAULT_PREF_PAUSE_DURING_CALLS = true; + // Other keys used in preferences.xml public static final String KEY_PREF_ABOUT = "pref_about"; diff --git a/app/src/main/java/org/xbmc/kore/jsonrpc/ApiMethod.java b/app/src/main/java/org/xbmc/kore/jsonrpc/ApiMethod.java index acb7fdb..b2f8424 100644 --- a/app/src/main/java/org/xbmc/kore/jsonrpc/ApiMethod.java +++ b/app/src/main/java/org/xbmc/kore/jsonrpc/ApiMethod.java @@ -64,15 +64,27 @@ public abstract class ApiMethod { * Constructor, sets up the necessary items to make the call later */ public ApiMethod() { - synchronized (this) { - this.id = (++lastId % 10000); - } + this(true); + } + /** + * Constructor, sets up the necessary items to make the call later + */ + public ApiMethod(boolean sendId) { // Create the rpc request object with the common fields according to JSON RPC spec jsonRequest = objectMapper.createObjectNode(); jsonRequest.put("jsonrpc", "2.0"); jsonRequest.put(METHOD_NODE, getMethodName()); - jsonRequest.put(ID_NODE, id); + + if(sendId) { + synchronized (this) { + this.id = (++lastId % 10000); + } + jsonRequest.put(ID_NODE, id); + } + else { + id = -1; + } } /** diff --git a/app/src/main/java/org/xbmc/kore/jsonrpc/method/Player.java b/app/src/main/java/org/xbmc/kore/jsonrpc/method/Player.java index 02c72ec..f98aafc 100644 --- a/app/src/main/java/org/xbmc/kore/jsonrpc/method/Player.java +++ b/app/src/main/java/org/xbmc/kore/jsonrpc/method/Player.java @@ -116,7 +116,6 @@ public class Player { } } - /** * Pauses or unpause playback and returns the new state */ @@ -493,4 +492,29 @@ public class Player { } } + /** + * Send notification message to XBMC/Kodi + */ + public static final class Notification extends ApiMethod { + public final static String METHOD_NAME = "GUI.ShowNotification"; + + /** + * Sends a text notification message to XBMC/Kodi + * @param title The title of the notification + * @param message The text message of the notification + */ + public Notification(String title, String message) { + super(false); + addParameterToRequest("title", title); + addParameterToRequest("message", message); + } + + @Override + public String getMethodName() { return METHOD_NAME; } + + @Override + public String resultFromJson(ObjectNode jsonObject) throws ApiException { + return jsonObject.get(RESULT_NODE).textValue(); + } + } } diff --git a/app/src/main/java/org/xbmc/kore/service/PauseCallService.java b/app/src/main/java/org/xbmc/kore/service/PauseCallService.java new file mode 100644 index 0000000..0253546 --- /dev/null +++ b/app/src/main/java/org/xbmc/kore/service/PauseCallService.java @@ -0,0 +1,155 @@ +/* + * Copyright 2016 Tomer Froumin. 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.service; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.preference.PreferenceManager; +import android.telephony.TelephonyManager; + +import org.xbmc.kore.R; +import org.xbmc.kore.Settings; +import org.xbmc.kore.host.HostConnectionObserver; +import org.xbmc.kore.host.HostManager; +import org.xbmc.kore.jsonrpc.method.Player; +import org.xbmc.kore.jsonrpc.type.ListType; +import org.xbmc.kore.jsonrpc.type.PlayerType; +import org.xbmc.kore.utils.LogUtils; + +/** + * This service maintains a notification in the notification area while + * something is playing, and keeps running while it is playing. + * This service stops itself as soon as the playing stops or there's no + * connection. Thus, this should only be started if something is already + * playing, otherwise it will shutdown automatically. + * It doesn't try to mirror Kodi's state at all times, because that would + * imply running at all times which can be resource consuming. + * + * A {@link HostConnectionObserver} singleton is used to keep track of Kodi's + * state. This singleton should be the same as used in the app's activities + */ +public class PauseCallService extends BroadcastReceiver + implements HostConnectionObserver.PlayerEventsObserver { + public static final String TAG = LogUtils.makeLogTag(PauseCallService.class); + private static int lastState = TelephonyManager.CALL_STATE_IDLE; + private static HostConnectionObserver mHostConnectionObserver = null; + private static int currentActivePlayerId = -1; + private static boolean isPlaying = false; + private static boolean shouldResume = false; + + @Override + public void onReceive(Context context, Intent intent) { + // Check whether we should react to phone state changes + boolean shouldPause = PreferenceManager + .getDefaultSharedPreferences(context) + .getBoolean(Settings.KEY_PREF_PAUSE_DURING_CALLS, Settings.DEFAULT_PREF_PAUSE_DURING_CALLS); + if(!shouldPause) return; + + int state = 0; + String stateStr = intent.getExtras().getString(TelephonyManager.EXTRA_STATE); + LogUtils.LOGD(TAG, "onReceive " + stateStr); + + // The phone state changed from in call to idle + if(stateStr.equals(TelephonyManager.EXTRA_STATE_IDLE)) { + state = TelephonyManager.CALL_STATE_IDLE; + } + // The phone state changed from idle to in call + else if(stateStr.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) { + state = TelephonyManager.CALL_STATE_OFFHOOK; + } + // The phone state changed from idle to ringing + else if(stateStr.equals(TelephonyManager.EXTRA_STATE_RINGING)) { + state = TelephonyManager.CALL_STATE_RINGING; + } + + if(state == lastState) return; + handleState(context, state); + lastState = state; + } + + protected void handleState(Context context, int state) { + // We won't create a new thread because the request to the host are + // already done in a separate thread. Just fire the request and forget + HostManager hostManager = HostManager.getInstance(context); + if (mHostConnectionObserver != null) { + mHostConnectionObserver.unregisterPlayerObserver(this); + } + mHostConnectionObserver = hostManager.getHostConnectionObserver(); + mHostConnectionObserver.registerPlayerObserver(this, true); + + if(state == TelephonyManager.CALL_STATE_OFFHOOK && isPlaying) { + Player.PlayPause action = new Player.PlayPause(currentActivePlayerId); + action.execute(hostManager.getConnection(), null, null); + shouldResume = true; + } + else if(state == TelephonyManager.CALL_STATE_IDLE && !isPlaying && shouldResume) { + Player.PlayPause action = new Player.PlayPause(currentActivePlayerId); + action.execute(hostManager.getConnection(), null, null); + shouldResume = false; + } + else if(state == TelephonyManager.CALL_STATE_RINGING) { + Player.Notification action = new Player.Notification( + context.getResources().getString(R.string.pause_call_incoming_title), + context.getResources().getString(R.string.pause_call_incoming_message)); + action.execute(hostManager.getConnection(), null, null); + } + } + + @Override + public void playerOnPlay(PlayerType.GetActivePlayersReturnType getActivePlayerResult, + PlayerType.PropertyValue getPropertiesResult, + ListType.ItemsAll getItemResult) { + currentActivePlayerId = getActivePlayerResult.playerid; + isPlaying = true; + } + + @Override + public void playerOnPause(PlayerType.GetActivePlayersReturnType getActivePlayerResult, + PlayerType.PropertyValue getPropertiesResult, + ListType.ItemsAll getItemResult) { + currentActivePlayerId = getActivePlayerResult.playerid; + isPlaying = false; + } + + @Override + public void playerOnStop() { + if (mHostConnectionObserver != null) { + mHostConnectionObserver.unregisterPlayerObserver(this); + } + currentActivePlayerId = -1; + isPlaying = false; + } + + @Override + public void playerOnConnectionError(int errorCode, String description) { + playerOnStop(); + } + + @Override + public void playerNoResultsYet() {} + + @Override + public void systemOnQuit() { + playerOnStop(); + } + + @Override + public void inputOnInputRequested(String title, String type, String value) {} + + @Override + public void observerOnStopObserving() {} +} \ No newline at end of file diff --git a/app/src/main/java/org/xbmc/kore/ui/RemoteActivity.java b/app/src/main/java/org/xbmc/kore/ui/RemoteActivity.java index 1a24bf9..acbf2d4 100644 --- a/app/src/main/java/org/xbmc/kore/ui/RemoteActivity.java +++ b/app/src/main/java/org/xbmc/kore/ui/RemoteActivity.java @@ -36,9 +36,7 @@ import android.widget.Toast; import org.xbmc.kore.R; import org.xbmc.kore.Settings; -import org.xbmc.kore.eventclient.EventServerConnection; import org.xbmc.kore.host.HostConnectionObserver; -import org.xbmc.kore.host.HostInfo; import org.xbmc.kore.host.HostManager; import org.xbmc.kore.jsonrpc.ApiCallback; import org.xbmc.kore.jsonrpc.HostConnection; diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7b296b6..c337452 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -318,6 +318,7 @@ Switch to remote after media start Show notification while playing + Pause playing while phone in a call Use volume keys to control volume Vibrate on remote button press Side menu shortcuts @@ -343,6 +344,8 @@ Play on Kodi + Incoming call + Check your phone, someone is calling you An error occurred while getting pvr info: %1$s An error occurred while getting channels info, probably because your media center doesn\'t have a tuner or it isn\'t configured.\n\nIf that\'s the case and you\'d like to remove this entry from the side menu, you can do it in the Settings. diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index f7f1901..eac38d4 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -34,6 +34,11 @@ android:title="@string/show_notification" android:defaultValue="false"/> + +