diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 241b6bc..cff6deb 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -6,6 +6,7 @@
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f5e0544..e0159ca 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -12,5 +12,8 @@
Please select a renderer
Album Art
DLNA Playback
+ Wifi needs to be connected for playback
+ Enable Wifi automatically on start
+ "Do not show this dialog again"
diff --git a/src/com/github/nutomic/controldlna/gui/MainActivity.java b/src/com/github/nutomic/controldlna/gui/MainActivity.java
index 9b8bf00..249ae57 100644
--- a/src/com/github/nutomic/controldlna/gui/MainActivity.java
+++ b/src/com/github/nutomic/controldlna/gui/MainActivity.java
@@ -31,6 +31,11 @@ import java.util.List;
import org.teleal.cling.support.model.item.Item;
+import android.app.AlertDialog;
+import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
@@ -42,6 +47,10 @@ import android.support.v7.app.ActionBar.Tab;
import android.support.v7.app.ActionBar.TabListener;
import android.support.v7.app.ActionBarActivity;
import android.view.KeyEvent;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.LinearLayout;
import com.github.nutomic.controldlna.R;
@@ -86,7 +95,8 @@ public class MainActivity extends ActionBarActivity {
ViewPager mViewPager;
/**
- * Initializes tab navigation.
+ * Initializes tab navigation. If wifi is not connected,
+ * shows a warning dialog.
*/
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -128,6 +138,50 @@ public class MainActivity extends ActionBarActivity {
.setText(R.string.title_renderer)
.setTabListener(tabListener));
+ ConnectivityManager connManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
+ NetworkInfo wifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+ final WifiManager wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
+ final SharedPreferences prefs = getSharedPreferences("preferences.db", 0);
+
+ if (!wifi.isConnected() && !prefs.getBoolean("wifi_skip_dialog", false) &&
+ !prefs.getBoolean("wifi_auto_enable", false)) {
+ LinearLayout layout = new LinearLayout(this);
+ layout.setOrientation(LinearLayout.VERTICAL);
+
+ CheckBox cbAutoEnable = new CheckBox(this);
+ cbAutoEnable.setText(R.string.auto_enable_wifi);
+ cbAutoEnable.setChecked(prefs.getBoolean("wifi_auto_enable", false));
+ cbAutoEnable.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ prefs.edit().putBoolean("wifi_auto_enable", isChecked)
+ .commit();
+ if (isChecked)
+ wifiManager.setWifiEnabled(true);
+ }
+ });
+ layout.addView(cbAutoEnable);
+
+ CheckBox cbShowOnce = new CheckBox(this);
+ cbShowOnce.setText(R.string.dont_show_dialog_again);
+ cbShowOnce.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ prefs.edit().putBoolean("wifi_skip_dialog", isChecked)
+ .commit();
+ }
+ });
+ layout.addView(cbShowOnce);
+
+ new AlertDialog.Builder(this)
+ .setView(layout)
+ .setTitle(R.string.warning_wifi_not_connected)
+ .setPositiveButton(android.R.string.ok, null)
+ .show();
+ }
+ else if (!wifiManager.isWifiEnabled() && prefs.getBoolean("wifi_auto_enable", false))
+ wifiManager.setWifiEnabled(true);
+
if (savedInstanceState != null) {
FragmentManager fm = getSupportFragmentManager();
mServerFragment = (ServerFragment) fm.getFragment(
diff --git a/src/com/github/nutomic/controldlna/gui/RouteFragment.java b/src/com/github/nutomic/controldlna/gui/RouteFragment.java
index a77db44..a94ee14 100644
--- a/src/com/github/nutomic/controldlna/gui/RouteFragment.java
+++ b/src/com/github/nutomic/controldlna/gui/RouteFragment.java
@@ -94,7 +94,7 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
private FileArrayAdapter mPlaylistAdapter;
- private boolean mRouteSelected = false;
+ private RouteInfo mSelectedRoute;
/**
* If true, the item at this position will be played as soon as a route is selected.
@@ -177,7 +177,8 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
if (savedInstanceState != null) {
mListView.onRestoreInstanceState(savedInstanceState.getParcelable("list_state"));
if (savedInstanceState.getBoolean("route_selected")) {
- mRouteSelected = true;
+ mSelectedRoute = MediaRouter.getInstance(getActivity())
+ .getSelectedRoute();
mListView.setAdapter(mPlaylistAdapter);
mControls.setVisibility(View.VISIBLE);
}
@@ -188,7 +189,7 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- outState.putBoolean("route_selected", mRouteSelected);
+ outState.putBoolean("route_selected", mSelectedRoute != null);
outState.putParcelable("list_state", mListView.onSaveInstanceState());
}
@@ -225,6 +226,10 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
@Override
public void onRouteRemoved(MediaRouter router, RouteInfo route) {
mRouteAdapter.remove(route);
+ if (route.equals(mSelectedRoute)) {
+ mPlaying = false;
+ onBackPressed();
+ }
}
@Override
@@ -265,8 +270,8 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
@Override
public void onItemClick(AdapterView> a, View v, final int position, long id) {
if (mListView.getAdapter() == mRouteAdapter) {
- mMediaRouterPlayService.getService().selectRoute(mRouteAdapter.getItem(position));
- mRouteSelected = true;
+ mSelectedRoute = mRouteAdapter.getItem(position);
+ mMediaRouterPlayService.getService().selectRoute(mSelectedRoute);
mListView.setAdapter(mPlaylistAdapter);
mControls.setVisibility(View.VISIBLE);
if (mStartPlayingOnSelect != -1) {
@@ -321,7 +326,7 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
mControls.setVisibility(View.GONE);
mListView.setAdapter(mRouteAdapter);
disableTrackHighlight();
- mRouteSelected = false;
+ mSelectedRoute = null;
}
})
.setNegativeButton(android.R.string.no, null)
@@ -331,7 +336,7 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
mControls.setVisibility(View.GONE);
mListView.setAdapter(mRouteAdapter);
disableTrackHighlight();
- mRouteSelected = false;
+ mSelectedRoute = null;
}
return true;
}
@@ -406,7 +411,7 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
mPlaylistAdapter.addAll(playlist);
mMediaRouterPlayService.getService().setPlaylist(playlist);
- if (mRouteSelected)
+ if (mSelectedRoute != null)
mMediaRouterPlayService.getService().play(start);
else {
Toast.makeText(getActivity(), R.string.select_renderer, Toast.LENGTH_SHORT)
diff --git a/src/com/github/nutomic/controldlna/gui/ServerFragment.java b/src/com/github/nutomic/controldlna/gui/ServerFragment.java
index 54b153c..59f044a 100644
--- a/src/com/github/nutomic/controldlna/gui/ServerFragment.java
+++ b/src/com/github/nutomic/controldlna/gui/ServerFragment.java
@@ -36,8 +36,6 @@ import org.teleal.cling.android.AndroidUpnpServiceImpl;
import org.teleal.cling.model.action.ActionInvocation;
import org.teleal.cling.model.message.UpnpResponse;
import org.teleal.cling.model.meta.Device;
-import org.teleal.cling.model.meta.LocalDevice;
-import org.teleal.cling.model.meta.RemoteDevice;
import org.teleal.cling.model.meta.Service;
import org.teleal.cling.model.types.ServiceType;
import org.teleal.cling.model.types.UDN;
@@ -47,10 +45,15 @@ import org.teleal.cling.support.model.DIDLContent;
import org.teleal.cling.support.model.container.Container;
import org.teleal.cling.support.model.item.Item;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.ServiceConnection;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcelable;
@@ -115,13 +118,8 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
public void onServiceConnected(ComponentName className, IBinder service) {
mUpnpService = (AndroidUpnpService) service;
mUpnpService.getRegistry().addListener(mServerAdapter);
- for (Device, ?, ?> d : mUpnpService.getControlPoint().getRegistry().getDevices()) {
- if (d instanceof LocalDevice)
- mServerAdapter.localDeviceAdded(mUpnpService.getRegistry(), (LocalDevice) d);
- else
- mServerAdapter.remoteDeviceAdded(mUpnpService.getRegistry(), (RemoteDevice) d);
- }
- Log.i(TAG, "Starting device search");
+ for (Device, ?, ?> d : mUpnpService.getControlPoint().getRegistry().getDevices())
+ mServerAdapter.deviceAdded(d);
mUpnpService.getControlPoint().search();
if (mRestoreServer != null) {
@@ -141,7 +139,8 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
};
/**
- * Initializes ListView adapters, launches Cling UPNP service.
+ * Initializes ListView adapters, launches Cling UPNP service, registers
+ * wifi state change listener and restores instance state if possible.
*/
@Override
public void onActivityCreated(Bundle savedInstanceState) {
@@ -157,6 +156,11 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
Context.BIND_AUTO_CREATE
);
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ getActivity().registerReceiver(mWifiReceiver, filter);
+
if (savedInstanceState != null) {
mRestoreServer = savedInstanceState.getString("current_server");
mCurrentPath.addAll(savedInstanceState.getStringArrayList("path"));
@@ -166,6 +170,9 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
mListState.push(getListView().onSaveInstanceState());
}
+ /**
+ * Stores current server and path/list state stacks.
+ */
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
@@ -232,6 +239,9 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
* which means we restore scroll position etc.
*/
private void getFiles(final boolean restoreListState) {
+ if (mCurrentServer == null)
+ return;
+
Service, ?> service = mCurrentServer.findService(
new ServiceType("schemas-upnp-org", "ContentDirectory"));
mUpnpService.getControlPoint().execute(new Browse(service,
@@ -274,8 +284,8 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
}
/**
- * Handles back button press to traverse directories (while in directory
- * browsing mode).
+ * Handles back button press to traverse directories (while browsing
+ * directories).
*/
public boolean onBackPressed() {
if (getListAdapter() == mServerAdapter)
@@ -292,5 +302,47 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
getFiles(true);
return true;
}
+
+ /**
+ * Starts device search on wifi connect, removes unreachable
+ * devices on wifi disconnect.
+ */
+ private BroadcastReceiver mWifiReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ getActivity();
+ ConnectivityManager connManager = (ConnectivityManager)
+ getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo wifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+
+ if (wifi.isConnected()) {
+ if (mUpnpService != null) {
+ for (Device, ?, ?> d : mUpnpService.getControlPoint()
+ .getRegistry().getDevices())
+ mServerAdapter.deviceAdded(d);
+ mUpnpService.getControlPoint().search();
+ }
+ }
+ else {
+ for (int i = 0; i < mServerAdapter.getCount(); i++) {
+ Device, ?, ?> d = mServerAdapter.getItem(i);
+ UDN udn = new UDN(d.getIdentity().getUdn().toString());
+ if (mUpnpService.getControlPoint().getRegistry()
+ .getDevice(udn, false) == null) {
+ mServerAdapter.deviceRemoved(d);
+ if (d.equals(mCurrentServer)) {
+ mCurrentPath.clear();
+ mListState.setSize(1);
+ setListAdapter(mServerAdapter);
+ getListView().onRestoreInstanceState(mListState.firstElement());
+ mCurrentServer = null;
+ }
+ i--;
+ }
+ }
+ }
+ }
+ };
}
diff --git a/src/com/github/nutomic/controldlna/mediarouter/MediaRouterPlayService.java b/src/com/github/nutomic/controldlna/mediarouter/MediaRouterPlayService.java
index a9a863c..1488719 100644
--- a/src/com/github/nutomic/controldlna/mediarouter/MediaRouterPlayService.java
+++ b/src/com/github/nutomic/controldlna/mediarouter/MediaRouterPlayService.java
@@ -49,6 +49,7 @@ import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.support.v7.media.MediaControlIntent;
import android.support.v7.media.MediaItemStatus;
+import android.support.v7.media.MediaRouteSelector;
import android.support.v7.media.MediaRouter;
import android.support.v7.media.MediaRouter.ControlRequestCallback;
import android.support.v7.media.MediaRouter.RouteInfo;
@@ -89,12 +90,31 @@ public class MediaRouterPlayService extends Service {
private String mSessionId;
- private WeakReference mRouterFragment = new WeakReference(null);
+ private WeakReference mRouterFragment =
+ new WeakReference(null);
private boolean mPollingStatus = false;
private boolean mBound;
+ /**
+ * Route that is currently being played to. May be invalid.
+ */
+ private RouteInfo mCurrentRoute;
+
+ /*
+ * Stops foreground mode and notification if the current route
+ * has been removed.
+ */
+ private MediaRouter.Callback mRouteRemovedCallback =
+ new MediaRouter.Callback() {
+ @Override
+ public void onRouteRemoved(MediaRouter router, RouteInfo route) {
+ if (route.equals(mCurrentRoute))
+ stopForeground(true);
+ }
+};
+
/**
* Creates a notification after the icon bitmap is loaded.
*/
@@ -160,7 +180,14 @@ public class MediaRouterPlayService extends Service {
}
public void selectRoute(RouteInfo route) {
+ mMediaRouter.removeCallback(mRouteRemovedCallback);
mMediaRouter.selectRoute(route);
+ MediaRouteSelector selector = new MediaRouteSelector.Builder()
+ .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
+ .build();
+
+ mMediaRouter.addCallback(selector, mRouteRemovedCallback, 0);
+ mCurrentRoute = route;
}
public void sendControlRequest(Intent intent) {
diff --git a/src/com/github/nutomic/controldlna/upnp/RemotePlayService.java b/src/com/github/nutomic/controldlna/upnp/RemotePlayService.java
index 63c2c10..f7e4d41 100644
--- a/src/com/github/nutomic/controldlna/upnp/RemotePlayService.java
+++ b/src/com/github/nutomic/controldlna/upnp/RemotePlayService.java
@@ -29,6 +29,7 @@ package com.github.nutomic.controldlna.upnp;
import java.util.HashMap;
import java.util.Map;
+import java.util.Map.Entry;
import org.teleal.cling.android.AndroidUpnpService;
import org.teleal.cling.android.AndroidUpnpServiceImpl;
@@ -43,6 +44,7 @@ import org.teleal.cling.model.meta.RemoteDevice;
import org.teleal.cling.model.meta.StateVariableAllowedValueRange;
import org.teleal.cling.model.state.StateVariableValue;
import org.teleal.cling.model.types.ServiceType;
+import org.teleal.cling.model.types.UDN;
import org.teleal.cling.registry.Registry;
import org.teleal.cling.registry.RegistryListener;
import org.teleal.cling.support.avtransport.callback.GetPositionInfo;
@@ -60,10 +62,15 @@ import org.teleal.cling.support.renderingcontrol.callback.GetVolume;
import org.teleal.cling.support.renderingcontrol.callback.SetVolume;
import android.app.Service;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.ServiceConnection;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiManager;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
@@ -109,7 +116,6 @@ public class RemotePlayService extends Service implements RegistryListener {
else
remoteDeviceAdded(mUpnpService.getRegistry(), (RemoteDevice) d);
}
- Log.i(TAG, "Starting device search");
mUpnpService.getControlPoint().search();
}
@@ -173,7 +179,7 @@ public class RemotePlayService extends Service implements RegistryListener {
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_CANCELED;
}
else
- mPlaybackState = MediaItemStatus.PLAYBACK_STATE_FINISHED;
+ mPlaybackState = MediaItemStatus.PLAYBACK_STATE_FINISHED;
break;
case TRANSITIONING:
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PENDING;
@@ -204,9 +210,13 @@ public class RemotePlayService extends Service implements RegistryListener {
mUpnpService.getControlPoint().execute(mSubscriptionCallback);
}
+ /**
+ * Ends selection, stops playback if possible.
+ */
@Override
public void unselectRenderer(String sessionId) throws RemoteException {
- stop(sessionId);
+ if (mDevices.get(sessionId) != null)
+ stop(sessionId);
mSubscriptionCallback.end();
mCurrentRenderer = null;
}
@@ -398,6 +408,9 @@ public class RemotePlayService extends Service implements RegistryListener {
return mBinder;
}
+ /**
+ * Binds to cling service, registers wifi state change listener.
+ */
@Override
public void onCreate() {
super.onCreate();
@@ -406,6 +419,11 @@ public class RemotePlayService extends Service implements RegistryListener {
mUpnpServiceConnection,
Context.BIND_AUTO_CREATE
);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ registerReceiver(mWifiReceiver, filter);
}
@Override
@@ -413,6 +431,38 @@ public class RemotePlayService extends Service implements RegistryListener {
super.onDestroy();
unbindService(mUpnpServiceConnection);
}
+
+ /**
+ * Starts device search on wifi connect, removes unreachable
+ * devices on wifi disconnect.
+ */
+ private BroadcastReceiver mWifiReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ ConnectivityManager connManager = (ConnectivityManager)
+ getSystemService(CONNECTIVITY_SERVICE);
+ NetworkInfo wifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+
+ if (wifi.isConnected()) {
+ if (mUpnpService != null) {
+ for (Device, ?, ?> d : mUpnpService.getControlPoint().getRegistry().getDevices())
+ deviceAdded(d);
+ mUpnpService.getControlPoint().search();
+ }
+ }
+ else {
+ for (Entry> d : mDevices.entrySet()) {
+ if (mUpnpService.getControlPoint().getRegistry()
+ .getDevice(new UDN(d.getKey()), false) == null) {
+ deviceRemoved(d.getValue());
+ if (d.getValue().equals(mCurrentRenderer))
+ mCurrentRenderer = null;
+ }
+ }
+ }
+ }
+ };
/**
* Returns a device service by name for direct queries.
@@ -427,6 +477,9 @@ public class RemotePlayService extends Service implements RegistryListener {
* Gather device data and send it to Provider.
*/
private void deviceAdded(final Device, ?, ?> device) {
+ if (mDevices.containsValue(device))
+ return;
+
final org.teleal.cling.model.meta.Service, ?> rc =
getService(device, "RenderingControl");
if (rc == null || mListener == null)
diff --git a/src/com/github/nutomic/controldlna/utility/DeviceArrayAdapter.java b/src/com/github/nutomic/controldlna/utility/DeviceArrayAdapter.java
index 7e1d7a2..41e5c9d 100644
--- a/src/com/github/nutomic/controldlna/utility/DeviceArrayAdapter.java
+++ b/src/com/github/nutomic/controldlna/utility/DeviceArrayAdapter.java
@@ -108,7 +108,12 @@ public class DeviceArrayAdapter extends ArrayAdapter>
/**
* Adds a new device to the list if its type equals mDeviceType.
*/
- private void deviceAdded(final Device, ?, ?> device) {
+ public void deviceAdded(final Device, ?, ?> device) {
+ for (int i = 0; i < getCount(); i++) {
+ if (getItem(i).equals(device))
+ return;
+ }
+
mActivity.runOnUiThread(new Runnable() {
@Override
@@ -122,7 +127,7 @@ public class DeviceArrayAdapter extends ArrayAdapter>
/**
* Removes the device from the list (if it is an element).
*/
- private void deviceRemoved(final Device, ?, ?> device) {
+ public void deviceRemoved(final Device, ?, ?> device) {
mActivity.runOnUiThread(new Runnable() {
@Override