diff --git a/app/src/main/java/com/syncedsynapse/kore2/service/NotificationService.java b/app/src/main/java/com/syncedsynapse/kore2/service/NotificationService.java index e6b7cc9..be3d6e0 100644 --- a/app/src/main/java/com/syncedsynapse/kore2/service/NotificationService.java +++ b/app/src/main/java/com/syncedsynapse/kore2/service/NotificationService.java @@ -23,6 +23,8 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; import android.os.Build; import android.os.IBinder; import android.support.v4.app.NotificationCompat; @@ -30,13 +32,17 @@ import android.support.v4.app.TaskStackBuilder; import android.view.View; import android.widget.RemoteViews; +import com.squareup.picasso.Picasso; +import com.squareup.picasso.Target; import com.syncedsynapse.kore2.R; import com.syncedsynapse.kore2.host.HostConnectionObserver; import com.syncedsynapse.kore2.host.HostManager; import com.syncedsynapse.kore2.jsonrpc.type.ListType; import com.syncedsynapse.kore2.jsonrpc.type.PlayerType; import com.syncedsynapse.kore2.ui.RemoteActivity; +import com.syncedsynapse.kore2.utils.CharacterDrawable; import com.syncedsynapse.kore2.utils.LogUtils; +import com.syncedsynapse.kore2.utils.UIUtils; import com.syncedsynapse.kore2.utils.Utils; /** @@ -162,11 +168,14 @@ public class NotificationService extends Service stopSelf(); } + // Picasso target that will be used to load images + private static Target picassoTarget = null; + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private void buildNotification(PlayerType.GetActivePlayersReturnType getActivePlayerResult, PlayerType.PropertyValue getPropertiesResult, ListType.ItemsAll getItemResult) { - String title, underTitle, poster; + final String title, underTitle, poster; int smallIcon, playPauseIcon, rewindIcon, ffIcon; boolean isVideo = ((getItemResult.type.equals(ListType.ItemsAll.TYPE_MOVIE)) || @@ -229,7 +238,7 @@ public class NotificationService extends Service } // Setup the collpased and expanded notifications - RemoteViews collapsedRV = new RemoteViews(this.getPackageName(), R.layout.notification_colapsed); + final RemoteViews collapsedRV = new RemoteViews(this.getPackageName(), R.layout.notification_colapsed); collapsedRV.setImageViewResource(R.id.rewind, rewindIcon); collapsedRV.setOnClickPendingIntent(R.id.rewind, rewindPendingItent); collapsedRV.setImageViewResource(R.id.play, playPauseIcon); @@ -239,7 +248,7 @@ public class NotificationService extends Service collapsedRV.setTextViewText(R.id.title, title); collapsedRV.setTextViewText(R.id.text2, underTitle); - RemoteViews expandedRV = new RemoteViews(this.getPackageName(), R.layout.notification_expanded); + final RemoteViews expandedRV = new RemoteViews(this.getPackageName(), R.layout.notification_expanded); expandedRV.setImageViewResource(R.id.rewind, rewindIcon); expandedRV.setOnClickPendingIntent(R.id.rewind, rewindPendingItent); expandedRV.setImageViewResource(R.id.play, playPauseIcon); @@ -248,7 +257,7 @@ public class NotificationService extends Service expandedRV.setOnClickPendingIntent(R.id.fast_forward, ffPendingItent); expandedRV.setTextViewText(R.id.title, title); expandedRV.setTextViewText(R.id.text2, underTitle); - int expandedIconResId; + final int expandedIconResId; if (isVideo) { expandedIconResId = R.id.icon_slim; expandedRV.setViewVisibility(R.id.icon_slim, View.VISIBLE); @@ -261,7 +270,7 @@ public class NotificationService extends Service // Build the notification NotificationCompat.Builder builder = new NotificationCompat.Builder(this); - Notification notification = builder + final Notification notification = builder .setSmallIcon(smallIcon) .setShowWhen(false) .setOngoing(true) @@ -271,30 +280,66 @@ public class NotificationService extends Service .setContent(collapsedRV) .build(); - // Load images. Use the same dimensions as the remote to hit the cache both times + // This is a convoluted way of loading the image and showing the + // notification, but it's what works with Picasso and is efficient. + // Here's what's going on: + // + // 1. The image is loaded asynchronously into a Target, and only after + // it is loaded is the notification shown. Using targets is a lot more + // efficient than letting Picasso load it directly into the + // notification imageview, which causes a lot of flickering + // + // 2. The target needs to be static, because Picasso only keeps a weak + // reference to it, so we need to keed a strong reference and reset it + // to null when we're done. We also need to check if it is not null in + // case a previous request hasn't finished yet. + // + // 3. We can only show the notification after the bitmap is loaded into + // the target, so it is done in the callback + // + // 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(); - int posterWidth = resources.getDimensionPixelOffset(R.dimen.now_playing_poster_width); - int posterHeight = isVideo? + final int posterWidth = resources.getDimensionPixelOffset(R.dimen.now_playing_poster_width); + final int posterHeight = isVideo? resources.getDimensionPixelOffset(R.dimen.now_playing_poster_height): posterWidth; + if (picassoTarget == null ) { + picassoTarget = new Target() { + @Override + public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { + showNotification(bitmap); + } - HostManager hostManager = HostManager.getInstance(this); - hostManager.getPicasso() - .load(hostManager.getHostInfo().getImageUrl(poster)) - .resize(posterWidth, posterHeight) - .into(collapsedRV, R.id.icon, NOTIFICATION_ID, notification); + @Override + public void onBitmapFailed(Drawable errorDrawable) { + CharacterDrawable avatarDrawable = UIUtils.getCharacterAvatar(NotificationService.this, title); + showNotification(Utils.drawableToBitmap(avatarDrawable, posterWidth, posterHeight)); + } - if (Utils.isJellybeanOrLater()) { - notification.bigContentView = expandedRV; + @Override + public void onPrepareLoad(Drawable placeHolderDrawable) { } + + private void showNotification(Bitmap bitmap) { + collapsedRV.setImageViewBitmap(R.id.icon, bitmap); + if (Utils.isJellybeanOrLater()) { + notification.bigContentView = expandedRV; + expandedRV.setImageViewBitmap(expandedIconResId, bitmap); + } + + NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.notify(NOTIFICATION_ID, notification); + picassoTarget = null; + } + }; + + // Load the image + HostManager hostManager = HostManager.getInstance(this); hostManager.getPicasso() .load(hostManager.getHostInfo().getImageUrl(poster)) .resize(posterWidth, posterHeight) - .into(expandedRV, expandedIconResId, NOTIFICATION_ID, notification); + .into(picassoTarget); } - - NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); - notificationManager.notify(NOTIFICATION_ID, notification); - } private PendingIntent buildActionPendingIntent(int playerId, String action) { diff --git a/app/src/main/java/com/syncedsynapse/kore2/utils/UIUtils.java b/app/src/main/java/com/syncedsynapse/kore2/utils/UIUtils.java index f3c7054..8b490c9 100644 --- a/app/src/main/java/com/syncedsynapse/kore2/utils/UIUtils.java +++ b/app/src/main/java/com/syncedsynapse/kore2/utils/UIUtils.java @@ -129,22 +129,8 @@ public class UIUtils { String imageUrl, String stringAvatar, ImageView imageView, int imageWidth, int imageHeight) { - // Load character avatar - if (characterAvatarColors == null) { - characterAvatarColors = context.getResources() - .obtainTypedArray(R.array.character_avatar_colors); - } - char charAvatar = TextUtils.isEmpty(stringAvatar) ? - ' ' : stringAvatar.charAt(0); - avatarColorsIdx = TextUtils.isEmpty(stringAvatar) ? 0 : - Math.max(Character.getNumericValue(stringAvatar.charAt(0)) + - Character.getNumericValue(stringAvatar.charAt(stringAvatar.length() - 1)) + - stringAvatar.length(), 0) % characterAvatarColors.length(); - int color = characterAvatarColors.getColor(avatarColorsIdx, 0xff000000); - CharacterDrawable avatarDrawable = new CharacterDrawable(charAvatar, color); - -// avatarColorsIdx = randomGenerator.nextInt(characterAvatarColors.length()); + CharacterDrawable avatarDrawable = getCharacterAvatar(context, stringAvatar); if (TextUtils.isEmpty(imageUrl)) { imageView.setImageDrawable(avatarDrawable); return; @@ -166,6 +152,31 @@ public class UIUtils { } } + /** + * Returns a CharacterDrawable that is suitable to use as an avatar + * @param context Context + * @param str String to use to create the avatar + * @return Character avatar to use in a image view + */ + public static CharacterDrawable getCharacterAvatar(Context context, String str) { + // Load character avatar + if (characterAvatarColors == null) { + characterAvatarColors = context + .getResources() + .obtainTypedArray(R.array.character_avatar_colors); + } + + char charAvatar = TextUtils.isEmpty(str) ? + ' ' : str.charAt(0); + avatarColorsIdx = TextUtils.isEmpty(str) ? 0 : + Math.max(Character.getNumericValue(str.charAt(0)) + + Character.getNumericValue(str.charAt(str.length() - 1)) + + str.length(), 0) % characterAvatarColors.length(); + int color = characterAvatarColors.getColor(avatarColorsIdx, 0xff000000); +// avatarColorsIdx = randomGenerator.nextInt(characterAvatarColors.length()); + return new CharacterDrawable(charAvatar, color); + } + /** * Sets play/pause button icon on a ImageView, based on speed * @param context Activity diff --git a/app/src/main/java/com/syncedsynapse/kore2/utils/Utils.java b/app/src/main/java/com/syncedsynapse/kore2/utils/Utils.java index ff2977d..2630594 100644 --- a/app/src/main/java/com/syncedsynapse/kore2/utils/Utils.java +++ b/app/src/main/java/com/syncedsynapse/kore2/utils/Utils.java @@ -17,6 +17,10 @@ package com.syncedsynapse.kore2.utils; import android.content.Context; import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.text.TextUtils; @@ -131,4 +135,23 @@ public class Utils { context.startActivity(intent); } } + + + /** + * Converts a drawable to a bitmap + * @param drawable Drawable to convert + * @return Bitmap + */ + public static Bitmap drawableToBitmap (Drawable drawable, int width, int height) { + if (drawable instanceof BitmapDrawable) { + return ((BitmapDrawable)drawable).getBitmap(); + } + + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + + return bitmap; + } }