From 0184355397f08452f89c29844d5a3f0fef73b23f Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Fri, 18 Oct 2013 15:01:26 +0200 Subject: [PATCH] Improved handling if wifi is disconnected. --- AndroidManifest.xml | 1 + res/values/strings.xml | 3 + .../nutomic/controldlna/gui/MainActivity.java | 56 +++++++++++++- .../controldlna/gui/RouteFragment.java | 21 +++-- .../controldlna/gui/ServerFragment.java | 76 ++++++++++++++++--- .../mediarouter/MediaRouterPlayService.java | 29 ++++++- .../controldlna/upnp/RemotePlayService.java | 59 +++++++++++++- .../utility/DeviceArrayAdapter.java | 9 ++- 8 files changed, 227 insertions(+), 27 deletions(-) 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