diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2b61071..5baf1f0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,6 +9,7 @@ + { * 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..8ecf548 --- /dev/null +++ b/app/src/main/java/org/xbmc/kore/service/PauseCallService.java @@ -0,0 +1,118 @@ +/* + * 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.Context; +import android.telephony.PhoneStateListener; +import android.telephony.TelephonyManager; + +import org.xbmc.kore.R; +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 listener handles changes to the phone state, such as receiving a + * call or hanging up, and synchronizes Kodi's currently playing state + * in order to prevent missing the movie (or what's playing) while the + * viewer is talking on the phone. + * + * The listener query Kodi's state on phone state changed event. + * When a call ends we only resume if it was paused by the listener. + */ +public class PauseCallService extends PhoneStateListener + implements HostConnectionObserver.PlayerEventsObserver { + public static final String TAG = LogUtils.makeLogTag(PauseCallService.class); + private Context context; + private int currentActivePlayerId = -1; + private boolean isPlaying = false; + private boolean shouldResume = false; + + public PauseCallService(Context context) { + this.context = context; + } + + @Override + public void onCallStateChanged(int state, String incomingNumber) { + // 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); + hostManager.getHostConnectionObserver().replyWithLastResult(this); + + 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) { + if(currentActivePlayerId != getActivePlayerResult.playerid) { + shouldResume = false; + } + currentActivePlayerId = getActivePlayerResult.playerid; + isPlaying = false; + } + + @Override + public void playerOnStop() { + currentActivePlayerId = -1; + isPlaying = false; + shouldResume = false; + } + + @Override + public void playerOnConnectionError(int errorCode, String description) { + playerOnStop(); + } + + @Override + public void playerNoResultsYet() { + playerOnStop(); + } + + @Override + public void systemOnQuit() {} + + @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 132e294..9f6a625 100644 --- a/app/src/main/java/org/xbmc/kore/ui/RemoteActivity.java +++ b/app/src/main/java/org/xbmc/kore/ui/RemoteActivity.java @@ -15,6 +15,7 @@ */ package org.xbmc.kore.ui; +import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Point; @@ -27,6 +28,8 @@ import android.support.v4.view.ViewPager; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; import android.support.v7.widget.Toolbar; +import android.telephony.PhoneStateListener; +import android.telephony.TelephonyManager; import android.text.TextUtils; import android.view.KeyEvent; import android.view.Menu; @@ -38,9 +41,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; @@ -57,6 +58,7 @@ import org.xbmc.kore.jsonrpc.type.ListType; import org.xbmc.kore.jsonrpc.type.PlayerType; import org.xbmc.kore.jsonrpc.type.PlaylistType; import org.xbmc.kore.service.NotificationService; +import org.xbmc.kore.service.PauseCallService; import org.xbmc.kore.ui.hosts.AddHostActivity; import org.xbmc.kore.ui.hosts.AddHostFragmentFinish; import org.xbmc.kore.ui.views.CirclePageIndicator; @@ -97,6 +99,8 @@ public class RemoteActivity extends BaseActivity private NavigationDrawerFragment navigationDrawerFragment; + private PauseCallService pauseCallService = null; + @InjectView(R.id.background_image) ImageView backgroundImage; @InjectView(R.id.pager_indicator) CirclePageIndicator pageIndicator; @InjectView(R.id.pager) ViewPager viewPager; @@ -621,12 +625,28 @@ public class RemoteActivity extends BaseActivity // Check whether we should show a notification boolean showNotification = PreferenceManager .getDefaultSharedPreferences(this) - .getBoolean(Settings.KEY_PREF_SHOW_NOTIFICATION, Settings.DEFAULT_PREF_SHOW_NOTIFICATION); + .getBoolean(Settings.KEY_PREF_SHOW_NOTIFICATION, + Settings.DEFAULT_PREF_SHOW_NOTIFICATION); if (showNotification) { // Let's start the notification service LogUtils.LOGD(TAG, "Starting notification service"); startService(new Intent(this, NotificationService.class)); } + + // Check whether we should react to phone state changes + boolean shouldPause = PreferenceManager + .getDefaultSharedPreferences(this) + .getBoolean(Settings.KEY_PREF_USE_HARDWARE_VOLUME_KEYS, + Settings.DEFAULT_PREF_USE_HARDWARE_VOLUME_KEYS); + if (shouldPause) { + // Let's start the notification service + LogUtils.LOGD(TAG, "Starting phone state listener"); + if(pauseCallService == null) { + pauseCallService = new PauseCallService(this); + ((TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE)).listen( + pauseCallService, PhoneStateListener.LISTEN_CALL_STATE); + } + } } public void playerOnPause(PlayerType.GetActivePlayersReturnType getActivePlayerResult, @@ -640,6 +660,12 @@ public class RemoteActivity extends BaseActivity setImageViewBackground(null); } lastImageUrl = null; + + if(pauseCallService != null) { + ((TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE)).listen( + pauseCallService, PhoneStateListener.LISTEN_NONE); + pauseCallService = null; + } } public void playerNoResultsYet() { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2200a89..13cac18 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -327,6 +327,7 @@ Switch to remote after media start Keep remote above lockscreen 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 @@ -352,6 +353,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 16b987c..479c529 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -39,6 +39,11 @@ android:title="@string/show_notification" android:defaultValue="false"/> + +