diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2b61071..a9e22a0 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 30ccde8..09ad38f 100644
--- a/app/src/main/java/org/xbmc/kore/Settings.java
+++ b/app/src/main/java/org/xbmc/kore/Settings.java
@@ -69,6 +69,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 = false;
+
// 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/ConnectionObserversManagerService.java b/app/src/main/java/org/xbmc/kore/service/ConnectionObserversManagerService.java
new file mode 100644
index 0000000..8789c08
--- /dev/null
+++ b/app/src/main/java/org/xbmc/kore/service/ConnectionObserversManagerService.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2016 Synced Synapse. 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.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.preference.PreferenceManager;
+
+import org.xbmc.kore.Settings;
+import org.xbmc.kore.host.HostConnectionObserver;
+import org.xbmc.kore.host.HostManager;
+import org.xbmc.kore.jsonrpc.type.ListType;
+import org.xbmc.kore.jsonrpc.type.PlayerType;
+import org.xbmc.kore.utils.LogUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This service is a wrapper over {@link HostConnectionObserver} that
+ * manages connection observers, and does it in a service that keeps running
+ * until the connection is lost.
+ * The observers are created here.
+ * This service stops itself as soon as there's no connection.
+ *
+ * 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 ConnectionObserversManagerService extends Service
+ implements HostConnectionObserver.PlayerEventsObserver {
+ public static final String TAG = LogUtils.makeLogTag(ConnectionObserversManagerService.class);
+
+ private HostConnectionObserver mHostConnectionObserver = null;
+
+ private List mConnectionObservers;
+
+ @Override
+ public void onCreate() {
+ // We do not create any thread because all the works is supposed to
+ // be done on the main thread, so that the connection observer
+ // can be shared with the app, and notify it on the UI thread
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ LogUtils.LOGD(TAG, "onStartCommand");
+ // Create the observers we are managing
+ createObservers();
+
+ // If no observers created, stop immediately
+ if (mConnectionObservers.size() == 0) {
+ LogUtils.LOGD(TAG, "No observers, stopping observer service.");
+ stopSelf();
+ }
+
+ // Get the connection observer here, not on create to check if
+ // there has been a change in hosts, and if so unregister the previous one
+ HostConnectionObserver connectionObserver = HostManager.getInstance(this).getHostConnectionObserver();
+
+ // If we are already initialized and the same host, exit
+ if (mHostConnectionObserver == connectionObserver) {
+ LogUtils.LOGD(TAG, "Already initialized");
+ return START_STICKY;
+ }
+
+ // If there's a change in hosts, unregister from the previous one
+ if (mHostConnectionObserver != null) {
+ mHostConnectionObserver.unregisterPlayerObserver(this);
+ }
+
+ // Register us on the connection observer
+ mHostConnectionObserver = connectionObserver;
+ mHostConnectionObserver.registerPlayerObserver(this, true);
+
+ // If we get killed, after returning from here, don't restart
+ return START_STICKY;
+ }
+
+ private void createObservers() {
+ mConnectionObservers = new ArrayList<>();
+
+ // Check whether we should show a notification
+ boolean showNotification = PreferenceManager
+ .getDefaultSharedPreferences(this)
+ .getBoolean(Settings.KEY_PREF_SHOW_NOTIFICATION,
+ Settings.DEFAULT_PREF_SHOW_NOTIFICATION);
+ if (showNotification) {
+ mConnectionObservers.add(new NotificationObserver(this));
+ }
+
+ // 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) {
+ mConnectionObservers.add(new PauseCallObserver(this));
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ // We don't provide binding, so return null
+ return null;
+ }
+
+ @Override
+ public void onTaskRemoved (Intent rootIntent) {
+ // Gracefully stop
+ for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
+ observer.playerOnConnectionError(0, "Task removed");
+ }
+
+ LogUtils.LOGD(TAG, "Shutting down observer service - Task removed");
+ if (mHostConnectionObserver != null) {
+ mHostConnectionObserver.unregisterPlayerObserver(this);
+ }
+ stopSelf();
+ }
+
+ @Override
+ public void onDestroy() {
+ // Gracefully stop
+ for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
+ observer.playerOnConnectionError(0, "Service destroyed");
+ }
+ LogUtils.LOGD(TAG, "Shutting down observer service - destroyed");
+ if (mHostConnectionObserver != null) {
+ mHostConnectionObserver.unregisterPlayerObserver(this);
+ }
+ }
+
+ /**
+ * HostConnectionObserver.PlayerEventsObserver interface callbacks
+ */
+ public void playerOnPlay(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
+ PlayerType.PropertyValue getPropertiesResult,
+ ListType.ItemsAll getItemResult) {
+ for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
+ observer.playerOnPlay(getActivePlayerResult, getPropertiesResult, getItemResult);
+ }
+ }
+
+ public void playerOnPause(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
+ PlayerType.PropertyValue getPropertiesResult,
+ ListType.ItemsAll getItemResult) {
+ for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
+ observer.playerOnPause(getActivePlayerResult, getPropertiesResult, getItemResult);
+ }
+ }
+
+ public void playerOnStop() {
+ for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
+ observer.playerOnStop();
+ }
+
+ // Stop service
+ LogUtils.LOGD(TAG, "Player stopped");
+// if (mHostConnectionObserver != null) {
+// mHostConnectionObserver.unregisterPlayerObserver(this);
+// }
+// stopSelf();
+ }
+
+ public void playerNoResultsYet() {
+ for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
+ observer.playerNoResultsYet();
+ }
+ }
+
+ public void playerOnConnectionError(int errorCode, String description) {
+ for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
+ observer.playerOnConnectionError(errorCode, description);
+ }
+
+ // Stop service
+ LogUtils.LOGD(TAG, "Shutting down observer service - Connection error");
+ if (mHostConnectionObserver != null) {
+ mHostConnectionObserver.unregisterPlayerObserver(this);
+ }
+ stopSelf();
+ }
+
+ public void systemOnQuit() {
+ for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
+ observer.systemOnQuit();
+ }
+
+ // Stop service
+ LogUtils.LOGD(TAG, "Shutting down observer service - System quit");
+ if (mHostConnectionObserver != null) {
+ mHostConnectionObserver.unregisterPlayerObserver(this);
+ }
+ stopSelf();
+ }
+
+ // Ignore this
+ public void inputOnInputRequested(String title, String type, String value) {
+ for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
+ observer.inputOnInputRequested(title, type, value);
+ }
+ }
+
+ public void observerOnStopObserving() {
+ for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
+ observer.observerOnStopObserving();
+ }
+ // Called when the user changes host
+ LogUtils.LOGD(TAG, "Shutting down observer service - Stop observing");
+ stopSelf();
+ }
+}
diff --git a/app/src/main/java/org/xbmc/kore/service/NotificationService.java b/app/src/main/java/org/xbmc/kore/service/NotificationObserver.java
similarity index 73%
rename from app/src/main/java/org/xbmc/kore/service/NotificationService.java
rename to app/src/main/java/org/xbmc/kore/service/NotificationObserver.java
index bcb5c3d..318ccba 100644
--- a/app/src/main/java/org/xbmc/kore/service/NotificationService.java
+++ b/app/src/main/java/org/xbmc/kore/service/NotificationObserver.java
@@ -46,87 +46,32 @@ import org.xbmc.kore.utils.UIUtils;
import org.xbmc.kore.utils.Utils;
/**
- * 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
+ * This class mantains a notification on the notification area while something is playing.
+ * It is meant to be used in conjunction with {@link ConnectionObserversManagerService},
+ * which should create an instance of this and manage it
*/
-public class NotificationService extends Service
+public class NotificationObserver
implements HostConnectionObserver.PlayerEventsObserver {
- public static final String TAG = LogUtils.makeLogTag(NotificationService.class);
+ public static final String TAG = LogUtils.makeLogTag(NotificationObserver.class);
private static final int NOTIFICATION_ID = 1;
- private HostConnectionObserver mHostConnectionObserver = null;
-
private PendingIntent mRemoteStartPendingIntent;
+ private Context mContext;
- @Override
- public void onCreate() {
- // We do not create any thread because all the works is supposed to
- // be done on the main thread, so that the connection observer
- // can be shared with the app, and notify it on the UI thread
+ public NotificationObserver(Context context) {
+ this.mContext = context;
// Create the intent to start the remote when the user taps the notification
- TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+ TaskStackBuilder stackBuilder = TaskStackBuilder.create(mContext);
stackBuilder.addParentStack(RemoteActivity.class);
- stackBuilder.addNextIntent(new Intent(this, RemoteActivity.class));
+ stackBuilder.addNextIntent(new Intent(mContext, RemoteActivity.class));
mRemoteStartPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
}
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- LogUtils.LOGD(TAG, "onStartCommand");
- // Get the connection observer here, not on create to check if
- // there has been a change in hosts, and if so unregister the previous one
- HostConnectionObserver connectionObserver = HostManager.getInstance(this).getHostConnectionObserver();
-
- // If we are already initialized and the same host, exit
- if (mHostConnectionObserver == connectionObserver) {
- LogUtils.LOGD(TAG, "Already initialized");
- return START_STICKY;
- }
-
- // If there's a change in hosts, unregister from the previous one
- if (mHostConnectionObserver != null) {
- mHostConnectionObserver.unregisterPlayerObserver(this);
- }
-
- // Register us on the connection observer
- mHostConnectionObserver = connectionObserver;
- mHostConnectionObserver.registerPlayerObserver(this, true);
-
- // If we get killed, after returning from here, don't restart
- return START_STICKY;
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- // We don't provide binding, so return null
- return null;
- }
-
- @Override
- public void onTaskRemoved (Intent rootIntent) {
- // Gracefully stop
- removeNotification();
- LogUtils.LOGD(TAG, "Shutting down notification service - Task removed");
- if (mHostConnectionObserver != null) {
- mHostConnectionObserver.unregisterPlayerObserver(this);
- }
- stopSelf();
- }
-
/**
* HostConnectionObserver.PlayerEventsObserver interface callbacks
*/
-
public void playerOnPlay(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
PlayerType.PropertyValue getPropertiesResult,
ListType.ItemsAll getItemResult) {
@@ -141,12 +86,6 @@ public class NotificationService extends Service
public void playerOnStop() {
removeNotification();
- // Stop service
- LogUtils.LOGD(TAG, "Shutting down notification service - Player stopped");
- if (mHostConnectionObserver != null) {
- mHostConnectionObserver.unregisterPlayerObserver(this);
- }
- stopSelf();
}
public void playerNoResultsYet() {
@@ -155,22 +94,10 @@ public class NotificationService extends Service
public void playerOnConnectionError(int errorCode, String description) {
removeNotification();
- // Stop service
- LogUtils.LOGD(TAG, "Shutting down notification service - Connection error");
- if (mHostConnectionObserver != null) {
- mHostConnectionObserver.unregisterPlayerObserver(this);
- }
- stopSelf();
}
public void systemOnQuit() {
removeNotification();
- // Stop service
- LogUtils.LOGD(TAG, "Shutting down notification service - System quit");
- if (mHostConnectionObserver != null) {
- mHostConnectionObserver.unregisterPlayerObserver(this);
- }
- stopSelf();
}
// Ignore this
@@ -179,8 +106,6 @@ public class NotificationService extends Service
public void observerOnStopObserving() {
// Called when the user changes host
removeNotification();
- LogUtils.LOGD(TAG, "Shutting down notification service - System quit");
- stopSelf();
}
// Picasso target that will be used to load images
@@ -205,7 +130,7 @@ public class NotificationService extends Service
break;
case ListType.ItemsAll.TYPE_EPISODE:
title = getItemResult.title;
- String seasonEpisode = String.format(getString(R.string.season_episode_abbrev),
+ String seasonEpisode = String.format(mContext.getString(R.string.season_episode_abbrev),
getItemResult.season, getItemResult.episode);
underTitle = String.format("%s | %s", getItemResult.showtitle, seasonEpisode);
poster = getItemResult.art.poster;
@@ -253,7 +178,7 @@ public class NotificationService extends Service
}
// Setup the collpased and expanded notifications
- final RemoteViews collapsedRV = new RemoteViews(this.getPackageName(), R.layout.notification_colapsed);
+ final RemoteViews collapsedRV = new RemoteViews(mContext.getPackageName(), R.layout.notification_colapsed);
collapsedRV.setImageViewResource(R.id.rewind, rewindIcon);
collapsedRV.setOnClickPendingIntent(R.id.rewind, rewindPendingItent);
collapsedRV.setImageViewResource(R.id.play, playPauseIcon);
@@ -263,7 +188,7 @@ public class NotificationService extends Service
collapsedRV.setTextViewText(R.id.title, title);
collapsedRV.setTextViewText(R.id.text2, underTitle);
- final RemoteViews expandedRV = new RemoteViews(this.getPackageName(), R.layout.notification_expanded);
+ final RemoteViews expandedRV = new RemoteViews(mContext.getPackageName(), R.layout.notification_expanded);
expandedRV.setImageViewResource(R.id.rewind, rewindIcon);
expandedRV.setOnClickPendingIntent(R.id.rewind, rewindPendingItent);
expandedRV.setImageViewResource(R.id.play, playPauseIcon);
@@ -284,7 +209,7 @@ public class NotificationService extends Service
}
// Build the notification
- NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext);
final Notification notification = builder
.setSmallIcon(smallIcon)
.setShowWhen(false)
@@ -314,7 +239,7 @@ public class NotificationService extends Service
//
// 4. We specifically resize the image to the same dimensions used in
// the remote, so that Picasso reuses it in the remote and here from the cache
- Resources resources = this.getResources();
+ Resources resources = mContext.getResources();
final int posterWidth = resources.getDimensionPixelOffset(R.dimen.now_playing_poster_width);
final int posterHeight = isVideo?
resources.getDimensionPixelOffset(R.dimen.now_playing_poster_height):
@@ -328,7 +253,7 @@ public class NotificationService extends Service
@Override
public void onBitmapFailed(Drawable errorDrawable) {
- CharacterDrawable avatarDrawable = UIUtils.getCharacterAvatar(NotificationService.this, title);
+ CharacterDrawable avatarDrawable = UIUtils.getCharacterAvatar(mContext, title);
showNotification(Utils.drawableToBitmap(avatarDrawable, posterWidth, posterHeight));
}
@@ -342,14 +267,15 @@ public class NotificationService extends Service
expandedRV.setImageViewBitmap(expandedIconResId, bitmap);
}
- NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
+ NotificationManager notificationManager =
+ (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(NOTIFICATION_ID, notification);
picassoTarget = null;
}
};
// Load the image
- HostManager hostManager = HostManager.getInstance(this);
+ HostManager hostManager = HostManager.getInstance(mContext);
hostManager.getPicasso()
.load(hostManager.getHostInfo().getImageUrl(poster))
.resize(posterWidth, posterHeight)
@@ -358,15 +284,16 @@ public class NotificationService extends Service
}
private PendingIntent buildActionPendingIntent(int playerId, String action) {
- Intent intent = new Intent(this, IntentActionsService.class)
+ Intent intent = new Intent(mContext, IntentActionsService.class)
.setAction(action)
.putExtra(IntentActionsService.EXTRA_PLAYER_ID, playerId);
- return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ return PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
private void removeNotification() {
- NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
+ NotificationManager notificationManager =
+ (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(NOTIFICATION_ID);
}
}
diff --git a/app/src/main/java/org/xbmc/kore/service/PauseCallObserver.java b/app/src/main/java/org/xbmc/kore/service/PauseCallObserver.java
new file mode 100644
index 0000000..12a4c93
--- /dev/null
+++ b/app/src/main/java/org/xbmc/kore/service/PauseCallObserver.java
@@ -0,0 +1,132 @@
+/*
+ * 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 PauseCallObserver extends PhoneStateListener
+ implements HostConnectionObserver.PlayerEventsObserver {
+ public static final String TAG = LogUtils.makeLogTag(PauseCallObserver.class);
+
+ private int currentActivePlayerId = -1;
+ private boolean isPlaying = false;
+ private boolean shouldResume = false;
+
+ private Context mContext;
+ private HostManager mHostManager;
+
+ public PauseCallObserver(Context context) {
+ this.mContext = context;
+ mHostManager = HostManager.getInstance(context);
+
+ ((TelephonyManager)mContext
+ .getSystemService(Context.TELEPHONY_SERVICE))
+ .listen(this, PhoneStateListener.LISTEN_CALL_STATE);
+ }
+
+ @Override
+ public void onCallStateChanged(int state, String incomingNumber) {
+ if (state == TelephonyManager.CALL_STATE_OFFHOOK && isPlaying) {
+ Player.PlayPause action = new Player.PlayPause(currentActivePlayerId);
+ action.execute(mHostManager.getConnection(), null, null);
+ shouldResume = true;
+ } else if (state == TelephonyManager.CALL_STATE_IDLE && !isPlaying && shouldResume) {
+ Player.PlayPause action = new Player.PlayPause(currentActivePlayerId);
+ action.execute(mHostManager.getConnection(), null, null);
+ shouldResume = false;
+ } else if (state == TelephonyManager.CALL_STATE_RINGING) {
+ Player.Notification action = new Player.Notification(
+ mContext.getResources().getString(R.string.pause_call_incoming_title),
+ mContext.getResources().getString(R.string.pause_call_incoming_message));
+ action.execute(mHostManager.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;
+ }
+
+ private void stopListener() {
+ ((TelephonyManager)mContext
+ .getSystemService(Context.TELEPHONY_SERVICE))
+ .listen(this, PhoneStateListener.LISTEN_NONE);
+ }
+
+ @Override
+ public void playerOnStop() {
+ currentActivePlayerId = -1;
+ isPlaying = false;
+ shouldResume = false;
+ stopListener();
+ }
+
+ @Override
+ public void playerOnConnectionError(int errorCode, String description) {
+ playerOnStop();
+ }
+
+ @Override
+ public void playerNoResultsYet() {
+ playerOnStop();
+ }
+
+ @Override
+ public void systemOnQuit() {
+ playerOnStop();
+ }
+
+ @Override
+ public void inputOnInputRequested(String title, String type, String value) {}
+
+ @Override
+ public void observerOnStopObserving() {
+ playerOnStop();
+ }
+}
\ 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..2c540d9 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;
@@ -56,7 +57,7 @@ import org.xbmc.kore.jsonrpc.type.GlobalType;
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.ConnectionObserversManagerService;
import org.xbmc.kore.ui.hosts.AddHostActivity;
import org.xbmc.kore.ui.hosts.AddHostFragmentFinish;
import org.xbmc.kore.ui.views.CirclePageIndicator;
@@ -618,15 +619,32 @@ public class RemoteActivity extends BaseActivity
}
lastImageUrl = imageUrl;
- // Check whether we should show a notification
- boolean showNotification = PreferenceManager
- .getDefaultSharedPreferences(this)
- .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));
- }
+ // Start service that manages connection observers
+ LogUtils.LOGD(TAG, "Starting observer service");
+ startService(new Intent(this, ConnectionObserversManagerService.class));
+
+
+// // Check whether we should show a notification
+// boolean showNotification = PreferenceManager
+// .getDefaultSharedPreferences(this)
+// .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, NotificationObserver.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 listening service
+// LogUtils.LOGD(TAG, "Starting phone state listener");
+// startService(new Intent(this, PauseCallObserver.class));
+// }
}
public void playerOnPause(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
@@ -636,6 +654,7 @@ public class RemoteActivity extends BaseActivity
}
public void playerOnStop() {
+ LogUtils.LOGD(TAG, "Player stopping");
if (lastImageUrl != null) {
setImageViewBackground(null);
}
diff --git a/app/src/main/java/org/xbmc/kore/ui/SettingsFragment.java b/app/src/main/java/org/xbmc/kore/ui/SettingsFragment.java
index 9913cd0..d206ac6 100644
--- a/app/src/main/java/org/xbmc/kore/ui/SettingsFragment.java
+++ b/app/src/main/java/org/xbmc/kore/ui/SettingsFragment.java
@@ -28,6 +28,7 @@ import android.support.v4.app.TaskStackBuilder;
import org.xbmc.kore.R;
import org.xbmc.kore.Settings;
import org.xbmc.kore.host.HostManager;
+import org.xbmc.kore.service.ConnectionObserversManagerService;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.UIUtils;
@@ -105,6 +106,14 @@ public class SettingsFragment extends PreferenceFragment
.addNextIntent(new Intent(getActivity(), SettingsActivity.class))
.startActivities();
}
+
+ // If one of the settings that use the observer service are modified, restart it
+ if (key.equals(Settings.KEY_PREF_SHOW_NOTIFICATION) || key.equals(Settings.KEY_PREF_PAUSE_DURING_CALLS)) {
+ LogUtils.LOGD(TAG, "Stoping connection observer service");
+ Intent intent = new Intent(getActivity(), ConnectionObserversManagerService.class);
+ getActivity().stopService(intent);
+ getActivity().startService(intent);
+ }
}
/**
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"/>
+
+