Added proper screen rotation handling.

This commit is contained in:
Felix Ableitner 2013-10-07 16:40:26 +02:00
parent 838ad69360
commit 8ec6b90e92
6 changed files with 227 additions and 60 deletions

View file

@ -33,6 +33,7 @@ import org.teleal.cling.support.model.item.Item;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewPager;
@ -66,7 +67,7 @@ public class MainActivity extends ActionBarActivity {
public Fragment getItem(int position) {
switch (position) {
case 0: return mServerFragment;
case 1: return mRendererFragment;
case 1: return mRouteFragment;
default: return null;
}
}
@ -78,9 +79,9 @@ public class MainActivity extends ActionBarActivity {
};
private ServerFragment mServerFragment = new ServerFragment();
private ServerFragment mServerFragment;
private RouteFragment mRendererFragment = new RouteFragment();
private RouteFragment mRouteFragment;
ViewPager mViewPager;
@ -126,6 +127,29 @@ public class MainActivity extends ActionBarActivity {
actionBar.addTab(actionBar.newTab()
.setText(R.string.title_renderer)
.setTabListener(tabListener));
if (savedInstanceState != null) {
FragmentManager fm = getSupportFragmentManager();
mServerFragment = (ServerFragment) fm.getFragment(
savedInstanceState, ServerFragment.class.getName());
mRouteFragment = (RouteFragment) fm.getFragment(
savedInstanceState, RouteFragment.class.getName());
}
else {
mServerFragment = new ServerFragment();
mRouteFragment = new RouteFragment();
}
}
/**
* Saves fragments.
*/
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
FragmentManager fm = getSupportFragmentManager();
fm.putFragment(outState, ServerFragment.class.getName(), mServerFragment);
fm.putFragment(outState, RouteFragment.class.getName(), mRouteFragment);
}
/**
@ -148,11 +172,11 @@ public class MainActivity extends ActionBarActivity {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_VOLUME_UP:
if (event.getAction() == KeyEvent.ACTION_DOWN)
mRendererFragment.increaseVolume();
mRouteFragment.increaseVolume();
return true;
case KeyEvent.KEYCODE_VOLUME_DOWN:
if (event.getAction() == KeyEvent.ACTION_DOWN)
mRendererFragment.decreaseVolume();
mRouteFragment.decreaseVolume();
return true;
default:
return super.dispatchKeyEvent(event);
@ -164,7 +188,7 @@ public class MainActivity extends ActionBarActivity {
*/
public void play(List<Item> playlist, int start) {
mViewPager.setCurrentItem(1);
mRendererFragment.play(playlist, start);
mRouteFragment.play(playlist, start);
}
}

View file

@ -107,7 +107,8 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
public void onServiceConnected(ComponentName className, IBinder service) {
mMediaRouterPlayService = (MediaRouterPlayServiceBinder) service;
mMediaRouterPlayService.getService().setRendererFragment(RouteFragment.this);
mMediaRouterPlayService.getService().setRouterFragment(RouteFragment.this);
mPlaylistAdapter.addAll(mMediaRouterPlayService.getService().getPlaylist());
}
public void onServiceDisconnected(ComponentName className) {
@ -140,6 +141,8 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
super.onActivityCreated(savedInstanceState);
mRouteAdapter = new RouteAdapter(getActivity());
mRouteAdapter.addAll(MediaRouter.getInstance(getActivity()).getRoutes());
mRouteAdapter.remove(MediaRouter.getInstance(getActivity()).getDefaultRoute());
mPlaylistAdapter = new FileArrayAdapter(getActivity());
mListView = (ListView) getView().findViewById(R.id.listview);
@ -163,11 +166,36 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
mPlayPause.setOnClickListener(this);
mPlayPause.setImageResource(R.drawable.ic_media_play);
getActivity().bindService(
getActivity().getApplicationContext().startService(
new Intent(getActivity(), MediaRouterPlayService.class));
getActivity().getApplicationContext().bindService(
new Intent(getActivity(), MediaRouterPlayService.class),
mPlayServiceConnection,
Context.BIND_AUTO_CREATE
);
if (savedInstanceState != null) {
mListView.onRestoreInstanceState(savedInstanceState.getParcelable("list_state"));
if (savedInstanceState.getBoolean("route_selected")) {
mRouteSelected = true;
mListView.setAdapter(mPlaylistAdapter);
mControls.setVisibility(View.VISIBLE);
}
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("route_selected", mRouteSelected);
outState.putParcelable("list_state", mListView.onSaveInstanceState());
}
@Override
public void onDestroy() {
super.onDestroy();
getActivity().getApplicationContext().unbindService(mPlayServiceConnection);
}
/**
@ -241,9 +269,7 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
mRouteSelected = true;
mListView.setAdapter(mPlaylistAdapter);
mControls.setVisibility(View.VISIBLE);
if (mStartPlayingOnSelect == -1)
mPlaylistAdapter.clear();
else {
if (mStartPlayingOnSelect != -1) {
mMediaRouterPlayService.getService().play(mStartPlayingOnSelect);
mStartPlayingOnSelect = -1;
}
@ -256,7 +282,7 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
* Sets colored background on the item that is currently playing.
*/
private void enableTrackHighlight() {
if (mListView.getAdapter() == mRouteAdapter)
if (mListView.getAdapter() == mRouteAdapter || mMediaRouterPlayService == null || !isVisible())
return;
disableTrackHighlight();

View file

@ -31,18 +31,28 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import org.teleal.cling.android.AndroidUpnpService;
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;
import org.teleal.cling.support.contentdirectory.callback.Browse;
import org.teleal.cling.support.model.BrowseFlag;
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.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcelable;
import android.support.v4.app.ListFragment;
import android.util.Log;
@ -50,7 +60,6 @@ import android.view.View;
import android.widget.ListView;
import com.github.nutomic.controldlna.gui.MainActivity.OnBackPressedListener;
import com.github.nutomic.controldlna.upnp.UpnpController;
import com.github.nutomic.controldlna.utility.DeviceArrayAdapter;
import com.github.nutomic.controldlna.utility.FileArrayAdapter;
@ -78,6 +87,8 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
*/
private Device<?, ?, ?> mCurrentServer;
private String mRestoreServer;
/**
* ListView adapter for showing a list of files/folders.
*/
@ -94,10 +105,40 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
*/
private Stack<Parcelable> mListState = new Stack<Parcelable>();
protected AndroidUpnpService mUpnpService;
private ServiceConnection mUpnpServiceConnection = new ServiceConnection() {
/**
* Manages all UPNP connections including playback.
* Registers DeviceListener, adds known devices and starts search if requested.
*/
private UpnpController mController = new UpnpController();
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");
mUpnpService.getControlPoint().search();
if (mRestoreServer != null) {
mCurrentServer = mUpnpService.getControlPoint().getRegistry()
.getDevice(new UDN(mRestoreServer.replace("uuid:", "")), false);
if (mCurrentServer != null) {
setListAdapter(mFileAdapter);
getFiles(true);
}
getListView().onRestoreInstanceState(mListState.lastElement());
}
}
public void onServiceDisconnected(ComponentName className) {
mUpnpService = null;
}
};
/**
* Initializes ListView adapters, launches Cling UPNP service.
@ -110,26 +151,44 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
mServerAdapter = new DeviceArrayAdapter(
getActivity(), DeviceArrayAdapter.SERVER);
setListAdapter(mServerAdapter);
mController.open(getActivity());
mController.addCallback(mServerAdapter);
getActivity().getApplicationContext().bindService(
new Intent(getActivity(), AndroidUpnpServiceImpl.class),
mUpnpServiceConnection,
Context.BIND_AUTO_CREATE
);
if (savedInstanceState != null) {
mRestoreServer = savedInstanceState.getString("current_server");
mCurrentPath.addAll(savedInstanceState.getStringArrayList("path"));
mListState.addAll(savedInstanceState.getParcelableArrayList("list_state"));
}
else
mListState.push(getListView().onSaveInstanceState());
}
@Override
public void onResume() {
super.onResume();
mController.startSearch();
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("current_server", (mCurrentServer != null)
? mCurrentServer.getIdentity().getUdn().toString()
: "");
outState.putStringArrayList("path", new ArrayList<String>(mCurrentPath));
mListState.pop();
mListState.push(getListView().onSaveInstanceState());
outState.putParcelableArrayList("list_state", new ArrayList<Parcelable>(mListState));
}
@Override
public void onPause() {
super.onPause();
mController.stopSearch();
mListState.pop();
mListState.push(getListView().onSaveInstanceState());
}
@Override
public void onDestroy() {
super.onDestroy();
mController.close(getActivity());
getActivity().getApplicationContext().unbindService(mUpnpServiceConnection);
}
/**
@ -175,7 +234,7 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
private void getFiles(final boolean restoreListState) {
Service<?, ?> service = mCurrentServer.findService(
new ServiceType("schemas-upnp-org", "ContentDirectory"));
mController.execute(new Browse(service,
mUpnpService.getControlPoint().execute(new Browse(service,
mCurrentPath.peek(), BrowseFlag.DIRECT_CHILDREN) {
@SuppressWarnings("rawtypes")
@ -192,7 +251,7 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
for (Item i : didl.getItems())
mFileAdapter.add(i);
if (restoreListState)
getListView().onRestoreInstanceState(mListState.pop());
getListView().onRestoreInstanceState(mListState.peek());
else
getListView().setSelectionFromTop(0, 0);
}
@ -223,14 +282,14 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
return false;
mCurrentPath.pop();
mListState.pop();
if (mCurrentPath.empty()) {
setListAdapter(mServerAdapter);
getListView().onRestoreInstanceState(mListState.pop());
getListView().onRestoreInstanceState(mListState.peek());
mCurrentServer = null;
}
else {
else
getFiles(true);
}
return true;
}

View file

@ -90,10 +90,12 @@ public class MediaRouterPlayService extends Service {
private String mSessionId;
private WeakReference<RouteFragment> mRendererFragment = new WeakReference<RouteFragment>(null);
private WeakReference<RouteFragment> mRouterFragment = new WeakReference<RouteFragment>(null);
private boolean mPollingStatus = false;
private boolean mBound;
/**
* Creates a notification after the icon bitmap is loaded.
*/
@ -118,10 +120,8 @@ public class MediaRouterPlayService extends Service {
.setLargeIcon(result)
.setSmallIcon(R.drawable.ic_launcher)
.build();
NotificationManager notificationManager =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(NOTIFICATION_ID, notification);
notification.flags |= Notification.FLAG_ONGOING_EVENT;
startForeground(NOTIFICATION_ID, notification);
}
}
@ -136,11 +136,28 @@ public class MediaRouterPlayService extends Service {
@Override
public IBinder onBind(Intent intent) {
mBound = true;
return mBinder;
}
public void setRendererFragment(RouteFragment rf) {
mRendererFragment = new WeakReference<RouteFragment>(rf);
/**
* Stops service after a delay if no media is playing (delay in case the
* fragment is recreated for screen rotation).
*/
@Override
public boolean onUnbind(Intent intent) {
new Handler().postDelayed(new Runnable() {
public void run() {
if (!mPollingStatus && !mBound)
stopSelf();
}
}, 5000);
mBound = false;
return super.onUnbind(intent);
}
public void setRouterFragment(RouteFragment rf) {
mRouterFragment = new WeakReference<RouteFragment>(rf);
}
public void selectRoute(RouteInfo route) {
@ -256,11 +273,6 @@ public class MediaRouterPlayService extends Service {
public int getCurrentTrack() {
return mCurrentTrack;
}
public RouteInfo getDefaultRoute() {
return mMediaRouter.getDefaultRoute();
}
/**
* Requests playback information every second, as long as RendererFragment
* is attached or media is playing.
@ -276,13 +288,15 @@ public class MediaRouterPlayService extends Service {
@Override
public void onResult(Bundle data) {
MediaItemStatus status = MediaItemStatus.fromBundle(data);
if (mRendererFragment.get() != null)
mRendererFragment.get().receivePlaybackStatus(status);
if (mRouterFragment.get() != null)
mRouterFragment.get().receivePlaybackStatus(status);
if (status.getPlaybackState() == MediaItemStatus.PLAYBACK_STATE_FINISHED) {
if (mCurrentTrack + 1 < mPlaylist.size())
playNext();
else {
if (!mBound)
stopSelf();
mPollingStatus = false;
NotificationManager notificationManager =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
@ -310,4 +324,8 @@ public class MediaRouterPlayService extends Service {
mMediaRouter.getSelectedRoute().requestUpdateVolume(-1);
}
public List<Item> getPlaylist() {
return mPlaylist;
}
}

View file

@ -97,11 +97,8 @@ public class UpnpController implements RegistryListener {
else
remoteDeviceAdded(mUpnpService.getRegistry(), (RemoteDevice) d);
}
if (mStartSearchImmediately) {
Log.i(TAG, "Starting device search");
mUpnpService.getControlPoint().search();
mStartSearchImmediately = false;
}
if (mStartSearchImmediately)
startSearch();
}
public void onServiceDisconnected(ComponentName className) {
@ -139,6 +136,7 @@ public class UpnpController implements RegistryListener {
if (mUpnpService != null) {
Log.i(TAG, "Starting device search");
mUpnpService.getControlPoint().search();
mStartSearchImmediately = false;
}
else
mStartSearchImmediately = true;

View file

@ -31,7 +31,10 @@ import java.net.URI;
import java.net.URISyntaxException;
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.registry.Registry;
import org.teleal.cling.registry.RegistryListener;
import android.app.Activity;
import android.content.Context;
@ -43,7 +46,6 @@ import android.widget.ArrayAdapter;
import android.widget.TextView;
import com.github.nutomic.controldlna.R;
import com.github.nutomic.controldlna.upnp.UpnpController.DeviceListenerCallback;
/**
@ -54,7 +56,7 @@ import com.github.nutomic.controldlna.upnp.UpnpController.DeviceListenerCallback
*
*/
public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
implements DeviceListenerCallback {
implements RegistryListener {
private static final String TAG = "DeviceArrayAdapter";
@ -106,8 +108,7 @@ public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
/**
* Adds a new device to the list if its type equals mDeviceType.
*/
@Override
public void deviceAdded(final Device<?, ?, ?> device) {
private void deviceAdded(final Device<?, ?, ?> device) {
mActivity.runOnUiThread(new Runnable() {
@Override
@ -121,8 +122,7 @@ public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
/**
* Removes the device from the list (if it is an element).
*/
@Override
public void deviceRemoved(final Device<?, ?, ?> device) {
private void deviceRemoved(final Device<?, ?, ?> device) {
mActivity.runOnUiThread(new Runnable() {
@Override
@ -133,8 +133,7 @@ public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
});
}
@Override
public void deviceUpdated(Device<?, ?, ?> device) {
private void deviceUpdated(Device<?, ?, ?> device) {
mActivity.runOnUiThread(new Runnable() {
@Override
@ -143,4 +142,47 @@ public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
}
});
}
@Override
public void beforeShutdown(Registry registry) {
}
@Override
public void localDeviceAdded(Registry registry, LocalDevice device) {
deviceAdded(device);
}
@Override
public void localDeviceRemoved(Registry registry, LocalDevice device) {
deviceRemoved(device);
}
@Override
public void remoteDeviceAdded(Registry registry, RemoteDevice device) {
deviceAdded(device);
}
@Override
public void remoteDeviceDiscoveryFailed(Registry registry, RemoteDevice device,
Exception exception) {
Log.w(TAG, "Remote device discovery failed", exception);
}
@Override
public void remoteDeviceDiscoveryStarted(Registry registry, RemoteDevice device) {
}
@Override
public void remoteDeviceRemoved(Registry registry, RemoteDevice device) {
deviceRemoved(device);
}
@Override
public void remoteDeviceUpdated(Registry registry, RemoteDevice device) {
deviceUpdated(device);
}
@Override
public void afterShutdown() {
}
}