AppWidget to Kotlin

This commit is contained in:
tzugen 2022-04-05 21:56:25 +02:00
parent 4c22c8b41b
commit d0959ffcb5
No known key found for this signature in database
GPG Key ID: 61E9C34BC10EC930
10 changed files with 319 additions and 389 deletions

View File

@ -1,221 +0,0 @@
package org.moire.ultrasonic.provider;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.os.Environment;
import android.view.KeyEvent;
import android.widget.RemoteViews;
import org.moire.ultrasonic.R;
import org.moire.ultrasonic.activity.NavigationActivity;
import org.moire.ultrasonic.domain.Track;
import org.moire.ultrasonic.imageloader.BitmapUtils;
import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver;
import org.moire.ultrasonic.service.MediaPlayerController;
import org.moire.ultrasonic.util.Constants;
import timber.log.Timber;
/**
* Widget Provider for the Ultrasonic Widgets
*/
public class UltrasonicAppWidgetProvider extends AppWidgetProvider
{
protected int layoutId;
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
defaultAppWidget(context, appWidgetIds);
}
/**
* Initialize given widgets to default state, where we launch Ultrasonic on default click
* and hide actions if service not running.
*/
private void defaultAppWidget(Context context, int[] appWidgetIds)
{
final Resources res = context.getResources();
final RemoteViews views = new RemoteViews(context.getPackageName(), this.layoutId);
views.setTextViewText(R.id.title, null);
views.setTextViewText(R.id.album, null);
views.setTextViewText(R.id.artist, res.getText(R.string.widget_initial_text));
linkButtons(context, views, false);
pushUpdate(context, appWidgetIds, views);
}
private void pushUpdate(Context context, int[] appWidgetIds, RemoteViews views)
{
// Update specific list of appWidgetIds if given, otherwise default to all
final AppWidgetManager manager = AppWidgetManager.getInstance(context);
if (manager != null)
{
if (appWidgetIds != null)
{
manager.updateAppWidget(appWidgetIds, views);
}
else
{
manager.updateAppWidget(new ComponentName(context, this.getClass()), views);
}
}
}
/**
* Handle a change notification coming over from {@link MediaPlayerController}
*/
public void notifyChange(Context context, Track currentSong, boolean playing, boolean setAlbum)
{
if (hasInstances(context))
{
performUpdate(context, currentSong, playing, setAlbum);
}
}
/**
* Check against {@link AppWidgetManager} if there are any instances of this widget.
*/
private boolean hasInstances(Context context)
{
AppWidgetManager manager = AppWidgetManager.getInstance(context);
if (manager != null)
{
int[] appWidgetIds = manager.getAppWidgetIds(new ComponentName(context, getClass()));
return (appWidgetIds.length > 0);
}
return false;
}
/**
* Update all active widget instances by pushing changes
*/
private void performUpdate(Context context, Track currentSong, boolean playing, boolean setAlbum)
{
final Resources res = context.getResources();
final RemoteViews views = new RemoteViews(context.getPackageName(), this.layoutId);
String title = currentSong == null ? null : currentSong.getTitle();
String artist = currentSong == null ? null : currentSong.getArtist();
String album = currentSong == null ? null : currentSong.getAlbum();
CharSequence errorState = null;
// Show error message?
String status = Environment.getExternalStorageState();
if (status.equals(Environment.MEDIA_SHARED) || status.equals(Environment.MEDIA_UNMOUNTED))
{
errorState = res.getText(R.string.widget_sdcard_busy);
}
else if (status.equals(Environment.MEDIA_REMOVED))
{
errorState = res.getText(R.string.widget_sdcard_missing);
}
else if (currentSong == null)
{
errorState = res.getText(R.string.widget_initial_text);
}
if (errorState != null)
{
// Show error state to user
views.setTextViewText(R.id.title, null);
views.setTextViewText(R.id.artist, errorState);
if (setAlbum)
{
views.setTextViewText(R.id.album, null);
}
views.setImageViewResource(R.id.appwidget_coverart, R.drawable.unknown_album);
}
else
{
// No error, so show normal titles
views.setTextViewText(R.id.title, title);
views.setTextViewText(R.id.artist, artist);
if (setAlbum)
{
views.setTextViewText(R.id.album, album);
}
}
// Set correct drawable for pause state
if (playing)
{
views.setImageViewResource(R.id.control_play, R.drawable.media_pause_normal_dark);
}
else
{
views.setImageViewResource(R.id.control_play, R.drawable.media_start_normal_dark);
}
// Set the cover art
try
{
Bitmap bitmap = currentSong == null ? null : BitmapUtils.Companion.getAlbumArtBitmapFromDisk(currentSong, 240);
if (bitmap == null)
{
// Set default cover art
views.setImageViewResource(R.id.appwidget_coverart, R.drawable.unknown_album);
}
else
{
views.setImageViewBitmap(R.id.appwidget_coverart, bitmap);
}
}
catch (Exception x)
{
Timber.e(x, "Failed to load cover art");
views.setImageViewResource(R.id.appwidget_coverart, R.drawable.unknown_album);
}
// Link actions buttons to intents
linkButtons(context, views, currentSong != null);
pushUpdate(context, null, views);
}
/**
* Link up various button actions using {@link PendingIntent}.
*/
private static void linkButtons(Context context, RemoteViews views, boolean playerActive)
{
Intent intent = new Intent(context, NavigationActivity.class).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
if (playerActive)
intent.putExtra(Constants.INTENT_SHOW_PLAYER, true);
intent.setAction("android.intent.action.MAIN");
intent.addCategory("android.intent.category.LAUNCHER");
PendingIntent pendingIntent = PendingIntent.getActivity(context, 10, intent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.appwidget_coverart, pendingIntent);
views.setOnClickPendingIntent(R.id.appwidget_top, pendingIntent);
// Emulate media button clicks.
intent = new Intent(Constants.CMD_PROCESS_KEYCODE);
intent.setComponent(new ComponentName(context, MediaButtonIntentReceiver.class));
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
pendingIntent = PendingIntent.getBroadcast(context, 11, intent, 0);
views.setOnClickPendingIntent(R.id.control_play, pendingIntent);
intent = new Intent(Constants.CMD_PROCESS_KEYCODE);
intent.setComponent(new ComponentName(context, MediaButtonIntentReceiver.class));
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT));
pendingIntent = PendingIntent.getBroadcast(context, 12, intent, 0);
views.setOnClickPendingIntent(R.id.control_next, pendingIntent);
intent = new Intent(Constants.CMD_PROCESS_KEYCODE);
intent.setComponent(new ComponentName(context, MediaButtonIntentReceiver.class));
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PREVIOUS));
pendingIntent = PendingIntent.getBroadcast(context, 13, intent, 0);
views.setOnClickPendingIntent(R.id.control_previous, pendingIntent);
}
}

View File

@ -1,42 +0,0 @@
/*
This file is part of Subsonic.
Subsonic is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Subsonic is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 (C) Sindre Mehus
*/
package org.moire.ultrasonic.provider;
import org.moire.ultrasonic.R;
public class UltrasonicAppWidgetProvider4X1 extends UltrasonicAppWidgetProvider
{
public UltrasonicAppWidgetProvider4X1()
{
super();
this.layoutId = R.layout.appwidget4x1;
}
private static UltrasonicAppWidgetProvider4X1 instance;
public static synchronized UltrasonicAppWidgetProvider4X1 getInstance()
{
if (instance == null)
{
instance = new UltrasonicAppWidgetProvider4X1();
}
return instance;
}
}

View File

@ -1,42 +0,0 @@
/*
This file is part of Subsonic.
Subsonic is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Subsonic is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 (C) Sindre Mehus
*/
package org.moire.ultrasonic.provider;
import org.moire.ultrasonic.R;
public class UltrasonicAppWidgetProvider4X2 extends UltrasonicAppWidgetProvider
{
public UltrasonicAppWidgetProvider4X2()
{
super();
this.layoutId = R.layout.appwidget4x2;
}
private static UltrasonicAppWidgetProvider4X2 instance;
public static synchronized UltrasonicAppWidgetProvider4X2 getInstance()
{
if (instance == null)
{
instance = new UltrasonicAppWidgetProvider4X2();
}
return instance;
}
}

View File

@ -1,42 +0,0 @@
/*
This file is part of Subsonic.
Subsonic is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Subsonic is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 (C) Sindre Mehus
*/
package org.moire.ultrasonic.provider;
import org.moire.ultrasonic.R;
public class UltrasonicAppWidgetProvider4X3 extends UltrasonicAppWidgetProvider
{
public UltrasonicAppWidgetProvider4X3()
{
super();
this.layoutId = R.layout.appwidget4x3;
}
private static UltrasonicAppWidgetProvider4X3 instance;
public static synchronized UltrasonicAppWidgetProvider4X3 getInstance()
{
if (instance == null)
{
instance = new UltrasonicAppWidgetProvider4X3();
}
return instance;
}
}

View File

@ -1,42 +0,0 @@
/*
This file is part of Subsonic.
Subsonic is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Subsonic is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2010 (C) Sindre Mehus
*/
package org.moire.ultrasonic.provider;
import org.moire.ultrasonic.R;
public class UltrasonicAppWidgetProvider4X4 extends UltrasonicAppWidgetProvider
{
public UltrasonicAppWidgetProvider4X4()
{
super();
this.layoutId = R.layout.appwidget4x4;
}
private static UltrasonicAppWidgetProvider4X4 instance;
public static synchronized UltrasonicAppWidgetProvider4X4 getInstance()
{
if (instance == null)
{
instance = new UltrasonicAppWidgetProvider4X4();
}
return instance;
}
}

View File

@ -0,0 +1,207 @@
/*
* UltrasonicAppWidgetProvider.kt
* Copyright (C) 2009-2022 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
*/
package org.moire.ultrasonic.provider
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Environment
import android.view.KeyEvent
import android.widget.RemoteViews
import org.moire.ultrasonic.R
import org.moire.ultrasonic.activity.NavigationActivity
import org.moire.ultrasonic.domain.Track
import org.moire.ultrasonic.imageloader.BitmapUtils
import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver
import org.moire.ultrasonic.util.Constants
import timber.log.Timber
import java.lang.Exception
/**
* Widget Provider for the Ultrasonic Widgets
*/
open class UltrasonicAppWidgetProvider : AppWidgetProvider() {
@JvmField
protected var layoutId = 0
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
defaultAppWidget(context, appWidgetIds)
}
/**
* Initialize given widgets to default state, where we launch Ultrasonic on default click
* and hide actions if service not running.
*/
private fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
val res = context.resources
val views = RemoteViews(context.packageName, layoutId)
views.setTextViewText(R.id.title, null)
views.setTextViewText(R.id.album, null)
views.setTextViewText(R.id.artist, res.getText(R.string.widget_initial_text))
linkButtons(context, views, false)
pushUpdate(context, appWidgetIds, views)
}
private fun pushUpdate(context: Context, appWidgetIds: IntArray?, views: RemoteViews) {
// Update specific list of appWidgetIds if given, otherwise default to all
val manager = AppWidgetManager.getInstance(context)
if (manager != null) {
if (appWidgetIds != null) {
manager.updateAppWidget(appWidgetIds, views)
} else {
manager.updateAppWidget(ComponentName(context, this.javaClass), views)
}
}
}
/**
* Handle a change notification coming over from [MediaPlayerController]
*/
fun notifyChange(context: Context, currentSong: Track?, playing: Boolean, setAlbum: Boolean) {
if (hasInstances(context)) {
performUpdate(context, currentSong, playing, setAlbum)
}
}
/**
* Check against [AppWidgetManager] if there are any instances of this widget.
*/
private fun hasInstances(context: Context): Boolean {
val manager = AppWidgetManager.getInstance(context)
if (manager != null) {
val appWidgetIds = manager.getAppWidgetIds(ComponentName(context, javaClass))
return appWidgetIds.isNotEmpty()
}
return false
}
/**
* Update all active widget instances by pushing changes
*/
private fun performUpdate(
context: Context,
currentSong: Track?,
playing: Boolean,
setAlbum: Boolean
) {
val res = context.resources
val views = RemoteViews(context.packageName, layoutId)
val title = currentSong?.title
val artist = currentSong?.artist
val album = currentSong?.album
var errorState: CharSequence? = null
// Show error message?
val status = Environment.getExternalStorageState()
if (status == Environment.MEDIA_SHARED || status == Environment.MEDIA_UNMOUNTED) {
errorState = res.getText(R.string.widget_sdcard_busy)
} else if (status == Environment.MEDIA_REMOVED) {
errorState = res.getText(R.string.widget_sdcard_missing)
} else if (currentSong == null) {
errorState = res.getText(R.string.widget_initial_text)
}
if (errorState != null) {
// Show error state to user
views.setTextViewText(R.id.title, null)
views.setTextViewText(R.id.artist, errorState)
if (setAlbum) {
views.setTextViewText(R.id.album, null)
}
views.setImageViewResource(R.id.appwidget_coverart, R.drawable.unknown_album)
} else {
// No error, so show normal titles
views.setTextViewText(R.id.title, title)
views.setTextViewText(R.id.artist, artist)
if (setAlbum) {
views.setTextViewText(R.id.album, album)
}
}
// Set correct drawable for pause state
if (playing) {
views.setImageViewResource(R.id.control_play, R.drawable.media_pause_normal_dark)
} else {
views.setImageViewResource(R.id.control_play, R.drawable.media_start_normal_dark)
}
// Set the cover art
try {
val bitmap =
if (currentSong == null) null else BitmapUtils.getAlbumArtBitmapFromDisk(
currentSong,
240
)
if (bitmap == null) {
// Set default cover art
views.setImageViewResource(R.id.appwidget_coverart, R.drawable.unknown_album)
} else {
views.setImageViewBitmap(R.id.appwidget_coverart, bitmap)
}
} catch (x: Exception) {
Timber.e(x, "Failed to load cover art")
views.setImageViewResource(R.id.appwidget_coverart, R.drawable.unknown_album)
}
// Link actions buttons to intents
linkButtons(context, views, currentSong != null)
pushUpdate(context, null, views)
}
companion object {
/**
* Link up various button actions using [PendingIntent].
*/
@SuppressLint("UnspecifiedImmutableFlag")
private fun linkButtons(context: Context, views: RemoteViews, playerActive: Boolean) {
var intent = Intent(
context,
NavigationActivity::class.java
).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
if (playerActive) intent.putExtra(Constants.INTENT_SHOW_PLAYER, true)
intent.action = "android.intent.action.MAIN"
intent.addCategory("android.intent.category.LAUNCHER")
var pendingIntent =
PendingIntent.getActivity(context, 10, intent, PendingIntent.FLAG_UPDATE_CURRENT)
views.setOnClickPendingIntent(R.id.appwidget_coverart, pendingIntent)
views.setOnClickPendingIntent(R.id.appwidget_top, pendingIntent)
// Emulate media button clicks.
intent = Intent(Constants.CMD_PROCESS_KEYCODE)
intent.component = ComponentName(context, MediaButtonIntentReceiver::class.java)
intent.putExtra(
Intent.EXTRA_KEY_EVENT,
KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)
)
pendingIntent = PendingIntent.getBroadcast(context, 11, intent, 0)
views.setOnClickPendingIntent(R.id.control_play, pendingIntent)
intent = Intent(Constants.CMD_PROCESS_KEYCODE)
intent.component = ComponentName(context, MediaButtonIntentReceiver::class.java)
intent.putExtra(
Intent.EXTRA_KEY_EVENT,
KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT)
)
pendingIntent = PendingIntent.getBroadcast(context, 12, intent, 0)
views.setOnClickPendingIntent(R.id.control_next, pendingIntent)
intent = Intent(Constants.CMD_PROCESS_KEYCODE)
intent.component = ComponentName(context, MediaButtonIntentReceiver::class.java)
intent.putExtra(
Intent.EXTRA_KEY_EVENT,
KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PREVIOUS)
)
pendingIntent = PendingIntent.getBroadcast(context, 13, intent, 0)
views.setOnClickPendingIntent(R.id.control_previous, pendingIntent)
}
}
}

View File

@ -0,0 +1,28 @@
/*
* UltrasonicAppWidgetProvider4X1.kt
* Copyright (C) 2009-2022 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
*/
package org.moire.ultrasonic.provider
import org.moire.ultrasonic.R
class UltrasonicAppWidgetProvider4X1 : UltrasonicAppWidgetProvider() {
companion object {
@get:Synchronized
var instance: UltrasonicAppWidgetProvider4X1? = null
get() {
if (field == null) {
field = UltrasonicAppWidgetProvider4X1()
}
return field
}
private set
}
init {
layoutId = R.layout.appwidget4x1
}
}

View File

@ -0,0 +1,28 @@
/*
* UltrasonicAppWidgetProvider4X2.kt
* Copyright (C) 2009-2022 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
*/
package org.moire.ultrasonic.provider
import org.moire.ultrasonic.R
class UltrasonicAppWidgetProvider4X2 : UltrasonicAppWidgetProvider() {
companion object {
@get:Synchronized
var instance: UltrasonicAppWidgetProvider4X2? = null
get() {
if (field == null) {
field = UltrasonicAppWidgetProvider4X2()
}
return field
}
private set
}
init {
layoutId = R.layout.appwidget4x2
}
}

View File

@ -0,0 +1,28 @@
/*
* UltrasonicAppWidgetProvider4X3.kt
* Copyright (C) 2009-2022 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
*/
package org.moire.ultrasonic.provider
import org.moire.ultrasonic.R
class UltrasonicAppWidgetProvider4X3 : UltrasonicAppWidgetProvider() {
companion object {
@get:Synchronized
var instance: UltrasonicAppWidgetProvider4X3? = null
get() {
if (field == null) {
field = UltrasonicAppWidgetProvider4X3()
}
return field
}
private set
}
init {
layoutId = R.layout.appwidget4x3
}
}

View File

@ -0,0 +1,28 @@
/*
* UltrasonicAppWidgetProvider4X4.kt
* Copyright (C) 2009-2022 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
*/
package org.moire.ultrasonic.provider
import org.moire.ultrasonic.R
class UltrasonicAppWidgetProvider4X4 : UltrasonicAppWidgetProvider() {
companion object {
@get:Synchronized
var instance: UltrasonicAppWidgetProvider4X4? = null
get() {
if (field == null) {
field = UltrasonicAppWidgetProvider4X4()
}
return field
}
private set
}
init {
layoutId = R.layout.appwidget4x4
}
}