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"/> + +