diff options
author | Scott Jackson <daneren2005@gmail.com> | 2015-04-25 17:03:02 -0700 |
---|---|---|
committer | Scott Jackson <daneren2005@gmail.com> | 2015-04-25 17:03:05 -0700 |
commit | cfd014d38cba03ba05f571597b361ab253bff578 (patch) | |
tree | 4256723561dec7ef3ed3507382eb7020724ec570 /app/src/main/java/github/daneren2005/dsub/provider | |
parent | 8a332a20ec272d59fe74520825b18017a8f0cac3 (diff) | |
download | dsub-cfd014d38cba03ba05f571597b361ab253bff578.tar.gz dsub-cfd014d38cba03ba05f571597b361ab253bff578.tar.bz2 dsub-cfd014d38cba03ba05f571597b361ab253bff578.zip |
Update to gradle
Diffstat (limited to 'app/src/main/java/github/daneren2005/dsub/provider')
12 files changed, 1407 insertions, 0 deletions
diff --git a/app/src/main/java/github/daneren2005/dsub/provider/DLNARouteProvider.java b/app/src/main/java/github/daneren2005/dsub/provider/DLNARouteProvider.java new file mode 100644 index 00000000..73d4b5de --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/provider/DLNARouteProvider.java @@ -0,0 +1,425 @@ +/* + 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 2014 (C) Scott Jackson +*/ +package github.daneren2005.dsub.provider; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.media.AudioManager; +import android.media.MediaRouter; +import android.os.IBinder; +import android.support.v7.media.MediaControlIntent; +import android.support.v7.media.MediaRouteDescriptor; +import android.support.v7.media.MediaRouteDiscoveryRequest; +import android.support.v7.media.MediaRouteProvider; +import android.support.v7.media.MediaRouteProviderDescriptor; +import android.util.Log; + +import org.eclipse.jetty.util.log.Logger; +import org.fourthline.cling.android.AndroidUpnpService; +import org.fourthline.cling.android.AndroidUpnpServiceImpl; +import org.fourthline.cling.model.action.ActionInvocation; +import org.fourthline.cling.model.message.UpnpResponse; +import org.fourthline.cling.model.meta.Device; +import org.fourthline.cling.model.meta.LocalDevice; +import org.fourthline.cling.model.meta.RemoteDevice; +import org.fourthline.cling.model.meta.StateVariable; +import org.fourthline.cling.model.meta.StateVariableAllowedValueRange; +import org.fourthline.cling.model.types.ServiceType; +import org.fourthline.cling.registry.Registry; +import org.fourthline.cling.registry.RegistryListener; +import org.fourthline.cling.support.renderingcontrol.callback.GetVolume; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import github.daneren2005.dsub.domain.DLNADevice; +import github.daneren2005.dsub.domain.RemoteControlState; +import github.daneren2005.dsub.service.DLNAController; +import github.daneren2005.dsub.service.DownloadService; +import github.daneren2005.dsub.service.RemoteController; + +public class DLNARouteProvider extends MediaRouteProvider { + private static final String TAG = DLNARouteProvider.class.getSimpleName(); + public static final String CATEGORY_DLNA = "github.daneren2005.dsub.DLNA"; + + private DownloadService downloadService; + private RemoteController controller; + + private HashMap<String, DLNADevice> devices = new HashMap<String, DLNADevice>(); + private List<String> adding = new ArrayList<String>(); + private List<String> removing = new ArrayList<String>(); + private AndroidUpnpService dlnaService; + private ServiceConnection dlnaServiceConnection; + private boolean searchOnConnect = false; + + public DLNARouteProvider(Context context) { + super(context); + + // Use custom logger + org.eclipse.jetty.util.log.Log.setLog(new JettyAndroidLog()); + + this.downloadService = (DownloadService) context; + dlnaServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + dlnaService = (AndroidUpnpService) service; + dlnaService.getRegistry().addListener(new RegistryListener() { + @Override + public void remoteDeviceDiscoveryStarted(Registry registry, RemoteDevice remoteDevice) { + + } + + @Override + public void remoteDeviceDiscoveryFailed(Registry registry, RemoteDevice remoteDevice, Exception e) { + // Error is displayed in log anyways under W/trieveRemoteDescriptors + } + + @Override + public void remoteDeviceAdded(Registry registry, RemoteDevice remoteDevice) { + deviceAdded(remoteDevice); + } + + @Override + public void remoteDeviceUpdated(Registry registry, RemoteDevice remoteDevice) { + deviceAdded(remoteDevice); + } + + @Override + public void remoteDeviceRemoved(Registry registry, RemoteDevice remoteDevice) { + deviceRemoved(remoteDevice); + } + + @Override + public void localDeviceAdded(Registry registry, LocalDevice localDevice) { + deviceAdded(localDevice); + } + + @Override + public void localDeviceRemoved(Registry registry, LocalDevice localDevice) { + deviceRemoved(localDevice); + } + + @Override + public void beforeShutdown(Registry registry) { + + } + + @Override + public void afterShutdown() { + + } + }); + + for (Device<?, ?, ?> device : dlnaService.getControlPoint().getRegistry().getDevices()) { + deviceAdded(device); + } + if(searchOnConnect) { + dlnaService.getControlPoint().search(); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + dlnaService = null; + } + }; + + if(!context.getApplicationContext().bindService(new Intent(context, AndroidUpnpServiceImpl.class), dlnaServiceConnection, Context.BIND_AUTO_CREATE)) { + Log.e(TAG, "Failed to bind to DLNA service"); + } + } + + private void broadcastDescriptors() { + // Create intents + IntentFilter routeIntentFilter = new IntentFilter(); + routeIntentFilter.addCategory(CATEGORY_DLNA); + routeIntentFilter.addAction(MediaControlIntent.ACTION_START_SESSION); + routeIntentFilter.addAction(MediaControlIntent.ACTION_GET_SESSION_STATUS); + routeIntentFilter.addAction(MediaControlIntent.ACTION_END_SESSION); + + // Create descriptor + MediaRouteProviderDescriptor.Builder providerBuilder = new MediaRouteProviderDescriptor.Builder(); + + // Create route descriptor + for(Map.Entry<String, DLNADevice> deviceEntry: devices.entrySet()) { + DLNADevice device = deviceEntry.getValue(); + + int volume; + if(device.volumeMax == 0) { + volume = 5; + } else { + int increments = device.volumeMax / 10; + volume = controller == null ? device.volume : (int) controller.getVolume(); + volume = volume / increments; + } + + MediaRouteDescriptor.Builder routeBuilder = new MediaRouteDescriptor.Builder(device.id, device.name); + routeBuilder.addControlFilter(routeIntentFilter) + .setPlaybackStream(AudioManager.STREAM_MUSIC) + .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE) + .setDescription(device.description) + .setVolume(volume) + .setVolumeMax(10) + .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE); + providerBuilder.addRoute(routeBuilder.build()); + } + + setDescriptor(providerBuilder.build()); + } + + @Override + public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) { + if (request != null && request.isActiveScan()) { + if(dlnaService != null) { + dlnaService.getControlPoint().search(); + } else { + searchOnConnect = true; + } + } + } + + @Override + public RouteController onCreateRouteController(String routeId) { + DLNADevice device = devices.get(routeId); + if(device == null) { + Log.w(TAG, "No device exists for " + routeId); + return null; + } + + return new DLNARouteController(device); + } + + private void deviceAdded(final Device device) { + final org.fourthline.cling.model.meta.Service renderingControl = device.findService(new ServiceType("schemas-upnp-org", "RenderingControl")); + if(renderingControl == null) { + return; + } + + final String id = device.getIdentity().getUdn().toString(); + // In the process of looking up it's details already + if(adding.contains(id)) { + return; + } + // Just a temp disconnect, already have it's info + if(removing.contains(id)) { + removing.remove(id); + return; + } + adding.add(id); + + if(device.getType().getType().equals("MediaRenderer") && device instanceof RemoteDevice) { + try { + dlnaService.getControlPoint().execute(new GetVolume(renderingControl) { + @Override + public void received(ActionInvocation actionInvocation, int currentVolume) { + int maxVolume = 100; + StateVariable volume = renderingControl.getStateVariable("Volume"); + if (volume != null) { + StateVariableAllowedValueRange volumeRange = volume.getTypeDetails().getAllowedValueRange(); + maxVolume = (int) volumeRange.getMaximum(); + } + + // Create a new DLNADevice to represent this item + String id = device.getIdentity().getUdn().toString(); + String name = device.getDetails().getFriendlyName(); + String displayName = device.getDisplayString(); + + DLNADevice newDevice = new DLNADevice(device, id, name, displayName, currentVolume, maxVolume); + devices.put(id, newDevice); + downloadService.post(new Runnable() { + @Override + public void run() { + broadcastDescriptors(); + } + }); + adding.remove(id); + } + + @Override + public void failure(ActionInvocation actionInvocation, UpnpResponse upnpResponse, String s) { + Log.w(TAG, "Failed to get default volume for DLNA route"); + Log.w(TAG, "Reason: " + s); + adding.remove(id); + } + }); + } catch(Exception e) { + Log.e(TAG, "Failed to add device", e); + } + } else { + adding.remove(id); + } + } + private void deviceRemoved(Device device) { + if(device.getType().getType().equals("MediaRenderer") && device instanceof RemoteDevice) { + final String id = device.getIdentity().getUdn().toString(); + removing.add(id); + + // Delay removal for a few seconds to make sure that it isn't just a temp disconnect + dlnaService.getControlPoint().search(); + downloadService.postDelayed(new Runnable() { + @Override + public void run() { + if(removing.contains(id)) { + devices.remove(id); + removing.remove(id); + broadcastDescriptors(); + } + } + }, 5000L); + } + } + + private class DLNARouteController extends RouteController { + private DLNADevice device; + + public DLNARouteController(DLNADevice device) { + this.device = device; + } + + @Override + public boolean onControlRequest(Intent intent, android.support.v7.media.MediaRouter.ControlRequestCallback callback) { + if (intent.hasCategory(CATEGORY_DLNA)) { + return true; + } else { + return false; + } + } + + @Override + public void onRelease() { + downloadService.setRemoteEnabled(RemoteControlState.LOCAL); + controller = null; + } + + @Override + public void onSelect() { + controller = new DLNAController(downloadService, dlnaService.getControlPoint(), device); + downloadService.setRemoteEnabled(RemoteControlState.DLNA, controller); + } + + @Override + public void onUnselect() { + downloadService.setRemoteEnabled(RemoteControlState.LOCAL); + controller = null; + } + + @Override + public void onUpdateVolume(int delta) { + if(controller != null) { + controller.updateVolume(delta > 0); + } + broadcastDescriptors(); + } + + @Override + public void onSetVolume(int volume) { + if(controller != null) { + controller.setVolume(volume); + } + broadcastDescriptors(); + } + } + + public static class JettyAndroidLog implements Logger { + final private static java.util.logging.Logger log = java.util.logging.Logger.getLogger("Jetty"); + + public static boolean __isIgnoredEnabled = false; + public String _name; + + public JettyAndroidLog() { + this (JettyAndroidLog.class.getName()); + } + + public JettyAndroidLog(String name) { + _name = name; + } + + public String getName () { + return _name; + } + + public void debug(Throwable th) { + // Log.d(TAG, "", th); + } + + public void debug(String msg, Throwable th) { + // Log.d(TAG, msg, th); + } + + public void debug(String msg, Object... args) { + // Log.d(TAG, msg); + } + + public Logger getLogger(String name) { + return new JettyAndroidLog(name); + } + + public void info(String msg, Object... args) { + // Log.i(TAG, msg); + } + + public void info(Throwable th) { + // Log.i(TAG, "", th); + } + + public void info(String msg, Throwable th) { + // Log.i(TAG, msg, th); + } + + public boolean isDebugEnabled() { + return false; + } + + public void warn(Throwable th) { + // Log.w(TAG, "", th); + } + + public void warn(String msg, Object... args) { + // Log.w(TAG, msg); + } + + public void warn(String msg, Throwable th) { + // Log.w(TAG, msg, th); + } + + public boolean isIgnoredEnabled () { + return __isIgnoredEnabled; + } + + + public void ignore(Throwable ignored) { + if (__isIgnoredEnabled) { + warn("IGNORED", ignored); + } + } + + public void setIgnoredEnabled(boolean enabled) { + __isIgnoredEnabled = enabled; + } + + public void setDebugEnabled(boolean enabled) { + + } + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/provider/DSubSearchProvider.java b/app/src/main/java/github/daneren2005/dsub/provider/DSubSearchProvider.java new file mode 100644 index 00000000..63bbaaa4 --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/provider/DSubSearchProvider.java @@ -0,0 +1,191 @@ +/* + 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 github.daneren2005.dsub.provider; + +import android.app.SearchManager; +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import github.daneren2005.dsub.R; +import github.daneren2005.dsub.domain.Artist; +import github.daneren2005.dsub.domain.MusicDirectory; +import github.daneren2005.dsub.domain.SearchCritera; +import github.daneren2005.dsub.domain.SearchResult; +import github.daneren2005.dsub.service.MusicService; +import github.daneren2005.dsub.service.MusicServiceFactory; +import github.daneren2005.dsub.util.Util; + +/** + * Provides search suggestions based on recent searches. + * + * @author Sindre Mehus + */ +public class DSubSearchProvider extends ContentProvider { + private static final String TAG = DSubSearchProvider.class.getSimpleName(); + + private static final String RESOURCE_PREFIX = "android.resource://github.daneren2005.dsub/"; + private static final String[] COLUMNS = {"_id", + SearchManager.SUGGEST_COLUMN_TEXT_1, + SearchManager.SUGGEST_COLUMN_TEXT_2, + SearchManager.SUGGEST_COLUMN_INTENT_DATA, + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA, + SearchManager.SUGGEST_COLUMN_ICON_1}; + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + String query = selectionArgs[0] + "*"; + SearchResult searchResult = search(query); + return createCursor(selectionArgs[0], searchResult); + } + + private SearchResult search(String query) { + MusicService musicService = MusicServiceFactory.getMusicService(getContext()); + if (musicService == null) { + return null; + } + + try { + return musicService.search(new SearchCritera(query, 5, 10, 10), getContext(), null); + } catch (Exception e) { + return null; + } + } + + private Cursor createCursor(String query, SearchResult searchResult) { + MatrixCursor cursor = new MatrixCursor(COLUMNS); + if (searchResult == null) { + return cursor; + } + + // Add all results into one pot + List<Object> results = new ArrayList<Object>(); + results.addAll(searchResult.getArtists()); + results.addAll(searchResult.getAlbums()); + results.addAll(searchResult.getSongs()); + + // For each, calculate its string distance to the query + for(Object obj: results) { + if(obj instanceof Artist) { + Artist artist = (Artist) obj; + artist.setCloseness(Util.getStringDistance(query, artist.getName())); + } else { + MusicDirectory.Entry entry = (MusicDirectory.Entry) obj; + entry.setCloseness(Util.getStringDistance(query, entry.getTitle())); + } + } + + // Sort based on the closeness paramater + Collections.sort(results, new Comparator<Object>() { + @Override + public int compare(Object lhs, Object rhs) { + // Get the closeness of the two objects + int left, right; + boolean leftArtist = lhs instanceof Artist; + boolean rightArtist = rhs instanceof Artist; + if (leftArtist) { + left = ((Artist) lhs).getCloseness(); + } else { + left = ((MusicDirectory.Entry) lhs).getCloseness(); + } + if (rightArtist) { + right = ((Artist) rhs).getCloseness(); + } else { + right = ((MusicDirectory.Entry) rhs).getCloseness(); + } + + if (left == right) { + if(leftArtist && rightArtist) { + return 0; + } else if(leftArtist) { + return -1; + } else if(rightArtist) { + return 1; + } else { + return 0; + } + } else if (left > right) { + return 1; + } else { + return -1; + } + } + }); + + // Done sorting, add results to cursor + for(Object obj: results) { + if(obj instanceof Artist) { + Artist artist = (Artist) obj; + String icon = RESOURCE_PREFIX + R.drawable.ic_action_artist; + cursor.addRow(new Object[]{artist.getId().hashCode(), artist.getName(), null, "ar-" + artist.getId(), artist.getName(), icon}); + } else { + MusicDirectory.Entry entry = (MusicDirectory.Entry) obj; + + if(entry.isDirectory()) { + String icon = RESOURCE_PREFIX + R.drawable.ic_action_album; + cursor.addRow(new Object[]{entry.getId().hashCode(), entry.getTitle(), entry.getArtist(), entry.getId(), entry.getTitle(), icon}); + } else { + String icon = RESOURCE_PREFIX + R.drawable.ic_action_song; + String id; + if(Util.isTagBrowsing(getContext())) { + id = entry.getAlbumId(); + } else { + id = entry.getParent(); + } + cursor.addRow(new Object[]{entry.getId().hashCode(), entry.getTitle(), entry.getArtist(), "so-" + id, entry.getTitle(), icon}); + } + } + } + return cursor; + } + + @Override + public boolean onCreate() { + return false; + } + + @Override + public String getType(Uri uri) { + return null; + } + + @Override + public Uri insert(Uri uri, ContentValues contentValues) { + return null; + } + + @Override + public int delete(Uri uri, String s, String[] strings) { + return 0; + } + + @Override + public int update(Uri uri, ContentValues contentValues, String s, String[] strings) { + return 0; + } + +} diff --git a/app/src/main/java/github/daneren2005/dsub/provider/DSubWidget4x1.java b/app/src/main/java/github/daneren2005/dsub/provider/DSubWidget4x1.java new file mode 100644 index 00000000..c78257c4 --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/provider/DSubWidget4x1.java @@ -0,0 +1,28 @@ +/* + 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 github.daneren2005.dsub.provider; + +import github.daneren2005.dsub.R; + +public class DSubWidget4x1 extends DSubWidgetProvider { + @Override + protected int getLayout() { + return R.layout.appwidget4x1; + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/provider/DSubWidget4x2.java b/app/src/main/java/github/daneren2005/dsub/provider/DSubWidget4x2.java new file mode 100644 index 00000000..4c7d637c --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/provider/DSubWidget4x2.java @@ -0,0 +1,28 @@ +/* + 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 github.daneren2005.dsub.provider; + +import github.daneren2005.dsub.R; + +public class DSubWidget4x2 extends DSubWidgetProvider { + @Override + protected int getLayout() { + return R.layout.appwidget4x2; + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/provider/DSubWidget4x3.java b/app/src/main/java/github/daneren2005/dsub/provider/DSubWidget4x3.java new file mode 100644 index 00000000..b4f91a45 --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/provider/DSubWidget4x3.java @@ -0,0 +1,28 @@ +/* + 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 github.daneren2005.dsub.provider; + +import github.daneren2005.dsub.R; + +public class DSubWidget4x3 extends DSubWidgetProvider { + @Override + protected int getLayout() { + return R.layout.appwidget4x3; + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/provider/DSubWidget4x4.java b/app/src/main/java/github/daneren2005/dsub/provider/DSubWidget4x4.java new file mode 100644 index 00000000..a591ff29 --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/provider/DSubWidget4x4.java @@ -0,0 +1,28 @@ +/* + 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 github.daneren2005.dsub.provider; + +import github.daneren2005.dsub.R; + +public class DSubWidget4x4 extends DSubWidgetProvider { + @Override + protected int getLayout() { + return R.layout.appwidget4x4; + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/provider/DSubWidgetProvider.java b/app/src/main/java/github/daneren2005/dsub/provider/DSubWidgetProvider.java new file mode 100644 index 00000000..444b6cff --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/provider/DSubWidgetProvider.java @@ -0,0 +1,304 @@ +/* + 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 github.daneren2005.dsub.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.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.RectF; +import android.os.Environment; +import android.util.Log; +import android.view.View; +import android.widget.RemoteViews; +import github.daneren2005.dsub.R; +import github.daneren2005.dsub.activity.DownloadActivity; +import github.daneren2005.dsub.activity.SubsonicActivity; +import github.daneren2005.dsub.activity.SubsonicFragmentActivity; +import github.daneren2005.dsub.domain.MusicDirectory; +import github.daneren2005.dsub.domain.PlayerQueue; +import github.daneren2005.dsub.service.DownloadService; +import github.daneren2005.dsub.service.DownloadServiceLifecycleSupport; +import github.daneren2005.dsub.util.Constants; +import github.daneren2005.dsub.util.FileUtil; +import github.daneren2005.dsub.util.ImageLoader; +import github.daneren2005.dsub.util.Util; + +/** + * Simple widget to show currently playing album art along + * with play/pause and next track buttons. + * <p/> + * Based on source code from the stock Android Music app. + * + * @author Sindre Mehus + */ +public class DSubWidgetProvider extends AppWidgetProvider { + private static final String TAG = DSubWidgetProvider.class.getSimpleName(); + private static DSubWidget4x1 instance4x1; + private static DSubWidget4x2 instance4x2; + private static DSubWidget4x3 instance4x3; + private static DSubWidget4x4 instance4x4; + + public static synchronized void notifyInstances(Context context, DownloadService service, boolean playing) { + if(instance4x1 == null) { + instance4x1 = new DSubWidget4x1(); + } + if(instance4x2 == null) { + instance4x2 = new DSubWidget4x2(); + } + if(instance4x3 == null) { + instance4x3 = new DSubWidget4x3(); + } + if(instance4x4 == null) { + instance4x4 = new DSubWidget4x4(); + } + + instance4x1.notifyChange(context, service, playing); + instance4x2.notifyChange(context, service, playing); + instance4x3.notifyChange(context, service, playing); + instance4x4.notifyChange(context, service, playing); + } + + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + defaultAppWidget(context, appWidgetIds); + } + + @Override + public void onEnabled(Context context) { + notifyInstances(context, DownloadService.getInstance(), false); + } + + protected int getLayout() { + return 0; + } + + /** + * Initialize given widgets to default state, where we launch Subsonic 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(), getLayout()); + + views.setTextViewText(R.id.artist, res.getText(R.string.widget_initial_text)); + if(getLayout() == R.layout.appwidget4x2) { + views.setTextViewText(R.id.album, ""); + } + + linkButtons(context, views, false); + performUpdate(context, null, appWidgetIds, false); + } + + 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 (appWidgetIds != null) { + manager.updateAppWidget(appWidgetIds, views); + } else { + manager.updateAppWidget(new ComponentName(context, this.getClass()), views); + } + } + + /** + * Handle a change notification coming over from {@link DownloadService} + */ + public void notifyChange(Context context, DownloadService service, boolean playing) { + if (hasInstances(context)) { + performUpdate(context, service, null, playing); + } + } + + /** + * Check against {@link AppWidgetManager} if there are any instances of this widget. + */ + private boolean hasInstances(Context context) { + AppWidgetManager manager = AppWidgetManager.getInstance(context); + int[] appWidgetIds = manager.getAppWidgetIds(new ComponentName(context, getClass())); + return (appWidgetIds.length > 0); + } + + /** + * Update all active widget instances by pushing changes + */ + private void performUpdate(Context context, DownloadService service, int[] appWidgetIds, boolean playing) { + final Resources res = context.getResources(); + final RemoteViews views = new RemoteViews(context.getPackageName(), getLayout()); + + if(playing) { + views.setViewVisibility(R.id.widget_root, View.VISIBLE); + } else { + // Hide widget + SharedPreferences prefs = Util.getPreferences(context); + if(prefs.getBoolean(Constants.PREFERENCES_KEY_HIDE_WIDGET, false)) { + views.setViewVisibility(R.id.widget_root, View.GONE); + } + } + + // Get Entry from current playing DownloadFile + MusicDirectory.Entry currentPlaying = null; + if(service == null) { + // Deserialize from playling list to setup + PlayerQueue state = FileUtil.deserialize(context, DownloadServiceLifecycleSupport.FILENAME_DOWNLOADS_SER, PlayerQueue.class); + if(state != null && state.currentPlayingIndex != -1) { + currentPlaying = state.songs.get(state.currentPlayingIndex); + } + } else { + currentPlaying = service.getCurrentPlaying() == null ? null : service.getCurrentPlaying().getSong(); + } + + String title = currentPlaying == null ? null : currentPlaying.getTitle(); + CharSequence artist = currentPlaying == null ? null : currentPlaying.getArtist(); + CharSequence album = currentPlaying == null ? null : currentPlaying.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 (currentPlaying == 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); + views.setTextViewText(R.id.album, ""); + if(getLayout() != R.layout.appwidget4x1) { + views.setImageViewResource(R.id.appwidget_coverart, R.drawable.appwidget_art_default); + } + } else { + // No error, so show normal titles + views.setTextViewText(R.id.title, title); + views.setTextViewText(R.id.artist, artist); + if(getLayout() != R.layout.appwidget4x1) { + views.setTextViewText(R.id.album, album); + } + } + + // Set correct drawable for pause state + if (playing) { + views.setImageViewResource(R.id.control_play, R.drawable.ic_appwidget_music_pause); + } else { + views.setImageViewResource(R.id.control_play, R.drawable.ic_appwidget_music_play); + } + + // Set the cover art + try { + boolean large = false; + if(getLayout() != R.layout.appwidget4x1 && getLayout() != R.layout.appwidget4x2) { + large = true; + } + ImageLoader imageLoader = SubsonicActivity.getStaticImageLoader(context); + Bitmap bitmap = imageLoader == null ? null : imageLoader.getCachedImage(context, currentPlaying, large); + + if (bitmap == null) { + // Set default cover art + views.setImageViewResource(R.id.appwidget_coverart, R.drawable.appwidget_art_unknown); + } else { + bitmap = getRoundedCornerBitmap(bitmap); + views.setImageViewBitmap(R.id.appwidget_coverart, bitmap); + } + } catch (Exception x) { + Log.e(TAG, "Failed to load cover art", x); + views.setImageViewResource(R.id.appwidget_coverart, R.drawable.appwidget_art_unknown); + } + + // Link actions buttons to intents + linkButtons(context, views, currentPlaying != null); + + pushUpdate(context, appWidgetIds, views); + } + + /** + * Round the corners of a bitmap for the cover art image + */ + private static Bitmap getRoundedCornerBitmap(Bitmap bitmap) { + Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888); + Canvas canvas = new Canvas(output); + + final int color = 0xff424242; + final Paint paint = new Paint(); + final float roundPx = 10; + + // Add extra width to the rect so the right side wont be rounded. + final Rect rect = new Rect(0, 0, bitmap.getWidth() + (int) roundPx, bitmap.getHeight()); + final RectF rectF = new RectF(rect); + + paint.setAntiAlias(true); + canvas.drawARGB(0, 0, 0, 0); + paint.setColor(color); + canvas.drawRoundRect(rectF, roundPx, roundPx, paint); + + paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN)); + canvas.drawBitmap(bitmap, rect, rect, paint); + + return output; + } + + /** + * Link up various button actions using {@link PendingIntent}. + * + * @param playerActive True if player is active in background, which means + * widget click will launch {@link DownloadActivity}, + * otherwise we launch {@link github.daneren2005.dsub.activity.SubsonicFragmentActivity}. + */ + private void linkButtons(Context context, RemoteViews views, boolean playerActive) { + Intent intent = new Intent(context, SubsonicFragmentActivity.class); + intent.putExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD, true); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0); + views.setOnClickPendingIntent(R.id.appwidget_coverart, pendingIntent); + views.setOnClickPendingIntent(R.id.appwidget_top, pendingIntent); + + // Emulate media button clicks. + intent = new Intent("DSub.PLAY_PAUSE"); + intent.setComponent(new ComponentName(context, DownloadService.class)); + intent.setAction(DownloadService.CMD_TOGGLEPAUSE); + pendingIntent = PendingIntent.getService(context, 0, intent, 0); + views.setOnClickPendingIntent(R.id.control_play, pendingIntent); + + intent = new Intent("DSub.NEXT"); // Use a unique action name to ensure a different PendingIntent to be created. + intent.setComponent(new ComponentName(context, DownloadService.class)); + intent.setAction(DownloadService.CMD_NEXT); + pendingIntent = PendingIntent.getService(context, 0, intent, 0); + views.setOnClickPendingIntent(R.id.control_next, pendingIntent); + + intent = new Intent("DSub.PREVIOUS"); // Use a unique action name to ensure a different PendingIntent to be created. + intent.setComponent(new ComponentName(context, DownloadService.class)); + intent.setAction(DownloadService.CMD_PREVIOUS); + pendingIntent = PendingIntent.getService(context, 0, intent, 0); + views.setOnClickPendingIntent(R.id.control_previous, pendingIntent); + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/provider/JukeboxRouteProvider.java b/app/src/main/java/github/daneren2005/dsub/provider/JukeboxRouteProvider.java new file mode 100644 index 00000000..0d2a5ff5 --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/provider/JukeboxRouteProvider.java @@ -0,0 +1,131 @@ +/* + 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 2014 (C) Scott Jackson +*/ +package github.daneren2005.dsub.provider; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.media.MediaRouter; +import android.support.v7.media.MediaControlIntent; +import android.support.v7.media.MediaRouteDescriptor; +import android.support.v7.media.MediaRouteProvider; +import android.support.v7.media.MediaRouteProviderDescriptor; + +import github.daneren2005.dsub.domain.RemoteControlState; +import github.daneren2005.dsub.service.DownloadService; +import github.daneren2005.dsub.service.RemoteController; + +/** + * Created by Scott on 11/28/13. + */ +public class JukeboxRouteProvider extends MediaRouteProvider { + public static final String CATEGORY_JUKEBOX_ROUTE = "github.daneren2005.dsub.SERVER_JUKEBOX"; + private RemoteController controller; + private static final int MAX_VOLUME = 10; + + private DownloadService downloadService; + + public JukeboxRouteProvider(Context context) { + super(context); + this.downloadService = (DownloadService) context; + + broadcastDescriptor(); + } + + private void broadcastDescriptor() { + // Create intents + IntentFilter routeIntentFilter = new IntentFilter(); + routeIntentFilter.addCategory(CATEGORY_JUKEBOX_ROUTE); + routeIntentFilter.addAction(MediaControlIntent.ACTION_START_SESSION); + routeIntentFilter.addAction(MediaControlIntent.ACTION_GET_SESSION_STATUS); + routeIntentFilter.addAction(MediaControlIntent.ACTION_END_SESSION); + + // Create route descriptor + MediaRouteDescriptor.Builder routeBuilder = new MediaRouteDescriptor.Builder("Jukebox Route", "Subsonic Jukebox"); + routeBuilder.addControlFilter(routeIntentFilter) + .setPlaybackStream(AudioManager.STREAM_MUSIC) + .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE) + .setDescription("Subsonic Jukebox") + .setVolume(controller == null ? 5 : (int) (controller.getVolume() * 10)) + .setVolumeMax(MAX_VOLUME) + .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE); + + // Create descriptor + MediaRouteProviderDescriptor.Builder providerBuilder = new MediaRouteProviderDescriptor.Builder(); + providerBuilder.addRoute(routeBuilder.build()); + setDescriptor(providerBuilder.build()); + } + + @Override + public MediaRouteProvider.RouteController onCreateRouteController(String routeId) { + return new JukeboxRouteController(downloadService); + } + + private class JukeboxRouteController extends RouteController { + private DownloadService downloadService; + + public JukeboxRouteController(DownloadService downloadService) { + this.downloadService = downloadService; + } + + @Override + public boolean onControlRequest(Intent intent, android.support.v7.media.MediaRouter.ControlRequestCallback callback) { + if (intent.hasCategory(CATEGORY_JUKEBOX_ROUTE)) { + return true; + } else { + return false; + } + } + + @Override + public void onRelease() { + downloadService.setRemoteEnabled(RemoteControlState.LOCAL); + controller = null; + } + + @Override + public void onSelect() { + downloadService.setRemoteEnabled(RemoteControlState.JUKEBOX_SERVER); + controller = downloadService.getRemoteController(); + } + + @Override + public void onUnselect() { + downloadService.setRemoteEnabled(RemoteControlState.LOCAL); + controller = null; + } + + @Override + public void onUpdateVolume(int delta) { + if(controller != null) { + controller.updateVolume(delta > 0); + } + broadcastDescriptor(); + } + + @Override + public void onSetVolume(int volume) { + if(controller != null) { + controller.setVolume(volume); + } + broadcastDescriptor(); + } + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/provider/MostRecentStubProvider.java b/app/src/main/java/github/daneren2005/dsub/provider/MostRecentStubProvider.java new file mode 100644 index 00000000..80dd93cc --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/provider/MostRecentStubProvider.java @@ -0,0 +1,61 @@ +/* + 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 2009 (C) Sindre Mehus + */ + +package github.daneren2005.dsub.provider; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; + +/** + * Created by Scott on 8/28/13. + */ + +public class MostRecentStubProvider extends ContentProvider { + @Override + public boolean onCreate() { + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + return null; + } + + @Override + public String getType(Uri uri) { + return ""; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + return null; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + return 0; + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/provider/PlaylistStubProvider.java b/app/src/main/java/github/daneren2005/dsub/provider/PlaylistStubProvider.java new file mode 100644 index 00000000..1481557f --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/provider/PlaylistStubProvider.java @@ -0,0 +1,61 @@ +/* + 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 2009 (C) Sindre Mehus + */ + +package github.daneren2005.dsub.provider; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; + +/** + * Created by Scott on 8/28/13. + */ + +public class PlaylistStubProvider extends ContentProvider { + @Override + public boolean onCreate() { + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + return null; + } + + @Override + public String getType(Uri uri) { + return ""; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + return null; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + return 0; + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/provider/PodcastStubProvider.java b/app/src/main/java/github/daneren2005/dsub/provider/PodcastStubProvider.java new file mode 100644 index 00000000..7d9bfca1 --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/provider/PodcastStubProvider.java @@ -0,0 +1,61 @@ +/* + 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 2009 (C) Sindre Mehus + */ + +package github.daneren2005.dsub.provider; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; + +/** + * Created by Scott on 8/28/13. + */ + +public class PodcastStubProvider extends ContentProvider { + @Override + public boolean onCreate() { + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + return null; + } + + @Override + public String getType(Uri uri) { + return ""; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + return null; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + return 0; + } +} diff --git a/app/src/main/java/github/daneren2005/dsub/provider/StarredStubProvider.java b/app/src/main/java/github/daneren2005/dsub/provider/StarredStubProvider.java new file mode 100644 index 00000000..f638c348 --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/provider/StarredStubProvider.java @@ -0,0 +1,61 @@ +/* + 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 2009 (C) Sindre Mehus + */ + +package github.daneren2005.dsub.provider; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; + +/** + * Created by Scott on 8/28/13. + */ + +public class StarredStubProvider extends ContentProvider { + @Override + public boolean onCreate() { + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + return null; + } + + @Override + public String getType(Uri uri) { + return ""; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + return null; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + return 0; + } +} |