Fix notifications to work with Android Oreo

Whan the background service is launcher, it is launched on the foreground and a notification is always shown.
The service stays on while something is playing. It stops itself on a connection error, quitting Kodi or 5 seconds after stopping
Make the Pause Phone Calls preference dependent on the Show Notification preference, as we always need to show a notification when the service is running
This commit is contained in:
Synced Synapse 2017-12-24 17:49:40 +00:00
parent 250c1f0fd1
commit 3077653dd9
6 changed files with 163 additions and 64 deletions

View File

@ -19,6 +19,7 @@ import android.Manifest;
import android.app.Service;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.content.ContextCompat;
@ -51,6 +52,10 @@ public class ConnectionObserversManagerService extends Service
private HostConnectionObserver mHostConnectionObserver = null;
private List<HostConnectionObserver.PlayerEventsObserver> mConnectionObservers = new ArrayList<>();
private NotificationObserver mNotificationObserver;
private boolean somethingPlaying = false;
private Handler mStopHandler = new Handler();
@Override
public void onCreate() {
@ -69,6 +74,7 @@ public class ConnectionObserversManagerService extends Service
if (mConnectionObservers.isEmpty()) {
LogUtils.LOGD(TAG, "No observers, stopping observer service.");
stopSelf();
return START_NOT_STICKY;
}
// Get the connection observer here, not on create to check if
@ -81,6 +87,16 @@ public class ConnectionObserversManagerService extends Service
return START_STICKY;
}
// Create the observers we are managing
createObservers();
if (mConnectionObservers.isEmpty()) {
stopForeground(true);
stopSelf();
return START_NOT_STICKY;
}
startForeground(NotificationObserver.NOTIFICATION_ID, mNotificationObserver.getNothingPlayingNotification());
// If there's a change in hosts, unregister from the previous one
if (mHostConnectionObserver != null) {
mHostConnectionObserver.unregisterPlayerObserver(this);
@ -97,14 +113,13 @@ public class ConnectionObserversManagerService extends Service
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));
}
// Always show a notification
// boolean showNotification = PreferenceManager
// .getDefaultSharedPreferences(this)
// .getBoolean(Settings.KEY_PREF_SHOW_NOTIFICATION,
// Settings.DEFAULT_PREF_SHOW_NOTIFICATION);
mNotificationObserver = new NotificationObserver(this);
mConnectionObservers.add(mNotificationObserver);
// Check whether we should react to phone state changes and wether
// we have permissions to do so
@ -136,6 +151,7 @@ public class ConnectionObserversManagerService extends Service
if (mHostConnectionObserver != null) {
mHostConnectionObserver.unregisterPlayerObserver(this);
}
stopForeground(true);
stopSelf();
}
@ -165,6 +181,7 @@ public class ConnectionObserversManagerService extends Service
for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
observer.playerOnPlay(getActivePlayerResult, getPropertiesResult, getItemResult);
}
somethingPlaying = true;
}
public void playerOnPause(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
@ -173,6 +190,7 @@ public class ConnectionObserversManagerService extends Service
for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
observer.playerOnPause(getActivePlayerResult, getPropertiesResult, getItemResult);
}
somethingPlaying = true;
}
public void playerOnStop() {
@ -180,30 +198,43 @@ public class ConnectionObserversManagerService extends Service
observer.playerOnStop();
}
// Stop service
LogUtils.LOGD(TAG, "Player stopped");
// if (mHostConnectionObserver != null) {
// mHostConnectionObserver.unregisterPlayerObserver(this);
// }
// stopSelf();
somethingPlaying = false;
// Stop service if nothing starts in a couple of seconds
mStopHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (!somethingPlaying) {
LogUtils.LOGD(TAG, "Stopping service");
if (mHostConnectionObserver != null) {
mHostConnectionObserver.unregisterPlayerObserver(ConnectionObserversManagerService.this);
}
stopForeground(true);
stopSelf();
}
}
}, 5000);
}
public void playerNoResultsYet() {
for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
observer.playerNoResultsYet();
}
somethingPlaying = false;
}
public void playerOnConnectionError(int errorCode, String description) {
for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
observer.playerOnConnectionError(errorCode, description);
}
somethingPlaying = false;
// Stop service
LogUtils.LOGD(TAG, "Shutting down observer service - Connection error");
if (mHostConnectionObserver != null) {
mHostConnectionObserver.unregisterPlayerObserver(this);
}
stopForeground(true);
stopSelf();
}
@ -211,12 +242,14 @@ public class ConnectionObserversManagerService extends Service
for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
observer.systemOnQuit();
}
somethingPlaying = false;
// Stop service
LogUtils.LOGD(TAG, "Shutting down observer service - System quit");
if (mHostConnectionObserver != null) {
mHostConnectionObserver.unregisterPlayerObserver(this);
}
stopForeground(true);
stopSelf();
}
@ -233,6 +266,7 @@ public class ConnectionObserversManagerService extends Service
}
// Called when the user changes host
LogUtils.LOGD(TAG, "Shutting down observer service - Stop observing");
stopForeground(true);
stopSelf();
}
}

View File

@ -17,8 +17,11 @@ package org.xbmc.kore.service;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
@ -52,11 +55,15 @@ public class NotificationObserver
implements HostConnectionObserver.PlayerEventsObserver {
public static final String TAG = LogUtils.makeLogTag(NotificationObserver.class);
private static final int NOTIFICATION_ID = 1;
public static final int NOTIFICATION_ID = 1;
public static final String NOTIFICATION_CHANNEL = "KORE";
private PendingIntent mRemoteStartPendingIntent;
private Service mService;
private Notification mNothingPlayingNotification;
public NotificationObserver(Service service) {
this.mService = service;
@ -65,6 +72,12 @@ public class NotificationObserver
stackBuilder.addParentStack(RemoteActivity.class);
stackBuilder.addNextIntent(new Intent(mService, RemoteActivity.class));
mRemoteStartPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
// Create the notification channel
if (Utils.isOreoOrLater()) {
buildNotificationChannel();
}
mNothingPlayingNotification = buildNothingPlayingNotification();
}
@Override
@ -78,21 +91,21 @@ public class NotificationObserver
public void playerOnPlay(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
PlayerType.PropertyValue getPropertiesResult,
ListType.ItemsAll getItemResult) {
buildNotification(getActivePlayerResult, getPropertiesResult, getItemResult);
notifyPlaying(getActivePlayerResult, getPropertiesResult, getItemResult);
}
public void playerOnPause(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
PlayerType.PropertyValue getPropertiesResult,
ListType.ItemsAll getItemResult) {
buildNotification(getActivePlayerResult, getPropertiesResult, getItemResult);
notifyPlaying(getActivePlayerResult, getPropertiesResult, getItemResult);
}
public void playerOnStop() {
removeNotification();
notifyNothingPlaying();
}
public void playerNoResultsYet() {
removeNotification();
notifyNothingPlaying();
}
public void playerOnConnectionError(int errorCode, String description) {
@ -111,13 +124,53 @@ public class NotificationObserver
removeNotification();
}
@TargetApi(Build.VERSION_CODES.O)
private void buildNotificationChannel() {
NotificationChannel channel =
new NotificationChannel(NOTIFICATION_CHANNEL,
mService.getString(R.string.app_name),
NotificationManager.IMPORTANCE_LOW);
channel.enableLights(false);
channel.enableVibration(false);
channel.setShowBadge(false);
NotificationManager notificationManager =
(NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(channel);
}
// Picasso target that will be used to load images
private static Target picassoTarget = null;
private Notification buildNothingPlayingNotification() {
int smallIcon = R.drawable.ic_devices_white_24dp;
NotificationCompat.Builder builder = new NotificationCompat.Builder(mService, NOTIFICATION_CHANNEL);
return builder
.setSmallIcon(smallIcon)
.setShowWhen(false)
.setOngoing(true)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setCategory(NotificationCompat.CATEGORY_TRANSPORT)
.setContentIntent(mRemoteStartPendingIntent)
.setContentTitle(String.format(mService.getString(R.string.connected_to),
HostManager.getInstance(mService).getHostInfo().getName()))
.setContentText(mService.getString(R.string.nothing_playing))
.build();
}
public Notification getNothingPlayingNotification() {
if (mNothingPlayingNotification == null) {
mNothingPlayingNotification = buildNothingPlayingNotification();
}
return mNothingPlayingNotification;
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void buildNotification(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
PlayerType.PropertyValue getPropertiesResult,
ListType.ItemsAll getItemResult) {
private void notifyPlaying(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
PlayerType.PropertyValue getPropertiesResult,
ListType.ItemsAll getItemResult) {
final String title, underTitle, poster;
int smallIcon, playPauseIcon, rewindIcon, ffIcon;
@ -212,7 +265,7 @@ public class NotificationObserver
}
// Build the notification
NotificationCompat.Builder builder = new NotificationCompat.Builder(mService);
NotificationCompat.Builder builder = new NotificationCompat.Builder(mService, NOTIFICATION_CHANNEL);
final Notification notification = builder
.setSmallIcon(smallIcon)
.setShowWhen(false)
@ -270,10 +323,9 @@ public class NotificationObserver
expandedRV.setImageViewBitmap(expandedIconResId, bitmap);
}
// NotificationManager notificationManager =
// (NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE);
// notificationManager.notify(NOTIFICATION_ID, notification);
mService.startForeground(NOTIFICATION_ID, notification);
NotificationManager notificationManager =
(NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(NOTIFICATION_ID, notification);
picassoTarget = null;
}
};
@ -296,9 +348,14 @@ public class NotificationObserver
}
private void removeNotification() {
// NotificationManager notificationManager =
// (NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE);
// notificationManager.cancel(NOTIFICATION_ID);
mService.stopForeground(true);
NotificationManager notificationManager =
(NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(NOTIFICATION_ID);
}
private void notifyNothingPlaying() {
NotificationManager notificationManager =
(NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(NOTIFICATION_ID, mNothingPlayingNotification);
}
}

View File

@ -64,6 +64,7 @@ import org.xbmc.kore.ui.volumecontrollers.VolumeKeyActionHandler;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.TabsAdapter;
import org.xbmc.kore.utils.UIUtils;
import org.xbmc.kore.utils.Utils;
import java.net.MalformedURLException;
import java.net.URL;
@ -623,32 +624,20 @@ public class RemoteActivity extends BaseActivity
}
lastImageUrl = imageUrl;
// 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));
// }
// 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) {
// Start service that manages connection observers
LogUtils.LOGD(TAG, "Starting observer service");
if (Utils.isOreoOrLater()) {
startForegroundService(new Intent(this, ConnectionObserversManagerService.class));
} else {
startService(new Intent(this, ConnectionObserversManagerService.class));
}
}
}
public void playerOnPause(PlayerType.GetActivePlayersReturnType getActivePlayerResult,

View File

@ -16,6 +16,8 @@
package org.xbmc.kore.ui.sections.settings;
import android.Manifest;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
@ -40,6 +42,8 @@ import org.xbmc.kore.utils.Utils;
import java.lang.reflect.Method;
import static org.xbmc.kore.service.NotificationObserver.NOTIFICATION_ID;
/**
* Simple fragment to display preferences screen
*/
@ -144,12 +148,22 @@ public class SettingsFragment extends PreferenceFragmentCompat
}
}
// If one of the settings that use the observer service are modified, restart it
// 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);
if (sharedPreferences.getBoolean(Settings.KEY_PREF_SHOW_NOTIFICATION, Settings.DEFAULT_PREF_SHOW_NOTIFICATION)) {
if (Utils.isOreoOrLater()) {
getActivity().startForegroundService(intent);
} else {
getActivity().startService(intent);
}
} else {
NotificationManager notificationManager =
(NotificationManager) getActivity().getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(NOTIFICATION_ID);
}
}
}

View File

@ -69,6 +69,10 @@ public class Utils {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
}
public static boolean isOreoOrLater() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
}
public static boolean isLollipopAndPreOreo() {
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) &&
(Build.VERSION.SDK_INT < 27);

View File

@ -64,17 +64,18 @@
android:title="@string/show_notification"
android:defaultValue="false"/>
<SwitchPreferenceCompat
android:key="pref_pause_during_calls"
android:title="@string/pause_during_calls"
android:defaultValue="false"
android:dependency="pref_show_notification"/>
<SwitchPreferenceCompat
android:key="pref_show_nowplayingpanel"
android:title="@string/show_now_playing_panel"
android:summary="@string/show_now_playing_panel_summary"
android:defaultValue="true"/>
<SwitchPreferenceCompat
android:key="pref_pause_during_calls"
android:title="@string/pause_during_calls"
android:defaultValue="false"/>
<MultiSelectListPreference
android:key="pref_nav_drawer_items"
android:title="@string/nav_drawer_items"