net.evendanan.android.thumbremote.ui.RemoteUiActivity.java Source code

Java tutorial

Introduction

Here is the source code for net.evendanan.android.thumbremote.ui.RemoteUiActivity.java

Source

/* The following code was written by Menny Even Danan
 * and is released under the APACHE 2.0 license
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 */

package net.evendanan.android.thumbremote.ui;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

import net.evendanan.android.thumbremote.R;
import net.evendanan.android.thumbremote.RemoteApplication;
import net.evendanan.android.thumbremote.ServerAddress;
import net.evendanan.android.thumbremote.ServerState;
import net.evendanan.android.thumbremote.ShakeListener.OnShakeListener;
import net.evendanan.android.thumbremote.UiView;
import net.evendanan.android.thumbremote.boxee.BoxeeDiscovererThread;
import net.evendanan.android.thumbremote.network.HttpRequest;
import net.evendanan.android.thumbremote.service.ServerRemoteService;
import net.evendanan.android.thumbremote.service.State;
import android.app.Dialog;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.text.Editable;
import android.text.TextUtils;
import android.text.method.KeyListener;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.view.SubMenu;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.InputMethodManager;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import android.widget.ViewFlipper;

import com.example.android.actionbarcompat.ActionBarHelperBase;

public class RemoteUiActivity extends FragmentActivity implements OnSharedPreferenceChangeListener, OnClickListener,
        OnShakeListener, UiView, OnSeekBarChangeListener, BoxeeDiscovererThread.Receiver {

    private BoxeeDiscovererThread mServerDiscoverer;
    public final static String TAG = RemoteUiActivity.class.toString();

    //
    private final static HashSet<Character> msPunctuation = new HashSet<Character>();
    static {
        String punctuation = "!@#$%^&*()[]{}/?|'\",.<>\n ";
        for (char c : punctuation.toCharArray())
            msPunctuation.add(c);
    }

    // ViewFlipper
    private static final int PAGE_NOTPLAYING = 0;
    private static final int PAGE_NOWPLAYING = 1;
    private ViewFlipper mFlipper;

    final ActionBarHelperBase mActionBarHelper = ActionBarHelperBase.createInstance(this);

    // Other Views
    ImageView mImageThumbnail;
    ImageView mButtonPlayPause;
    TextView mTextMainTitle;
    TextView mTextSubTitle;
    TextView mUserMessage;
    String mUserMessageString = "";
    TextView mTextElapsed;
    TextView mDuration;
    ProgressBar mElapsedBar;
    TextView mMediaDetails = null;
    InputMethodManager mImeManager;
    TextView mKeyboardText;
    SubMenu mServersGroup;
    private HashMap<String, ServerAddress> mServers;

    private double mSeekToPercentRequest = -1;
    private int mTotalDuration = 0;
    private int mCurrentTime = 0;
    private boolean mMediaSeekBarInUserChange = false;
    SeekBar mMediaSeekBar;
    private TextView mMediaSeekDialogTime;

    boolean mThisAcitivityPaused = true;

    // Not ready for prime time
    // private ShakeListener mShakeDetector;

    private Point mTouchPoint = new Point();
    private boolean mDragged = false;
    private boolean mIsMediaActive = false;
    // private ProgressDialog mPleaseWaitDialog;

    private Dialog mOpenDialog;

    private Handler mHandler;

    /*
     * private final Runnable mRequestStatusUpdateRunnable = new Runnable() {
     * @Override public void run() { if (mStatePoller != null)
     * mStatePoller.checkStateNow(); } };
     */

    private Animation mInAnimation;

    private Animation mOutAnimation;

    private boolean mIsBound = false;
    private ServerRemoteService mBoundService;

    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            Log.i(TAG, "Connected to " + className);
            // This is called when the connection with the service has been
            // established, giving us the service object we can use to
            // interact with the service. Because we have bound to a explicit
            // service that we know is running in our own process, we can
            // cast its IBinder to a concrete class and directly access it.
            mBoundService = ((ServerRemoteService.LocalBinder) service).getService();
            mBoundService.setUiView(RemoteUiActivity.this);
        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            // Because it is running in our same process, we should never
            // see this happen.
            if (mBoundService != null)
                mBoundService.setUiView(null);
            mBoundService = null;
        }
    };
    private MenuItem mServersMenu;

    void doBindService() {
        startService(new Intent(getApplicationContext(), ServerRemoteService.class));
        // Establish a connection with the service. We use an explicit
        // class name because we want a specific service implementation that
        // we know will be running in our own process (and thus won't be
        // supporting component replacement by other applications).
        mIsBound = bindService(new Intent(getApplicationContext(), ServerRemoteService.class), mConnection, 0);
    }

    void doUnbindService() {
        if (mIsBound) {
            if (mBoundService != null)
                mBoundService.setUiView(null);
            // Detach our existing connection.
            unbindService(mConnection);
            mIsBound = false;
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        setRequestedOrientation(RemoteApplication.getConfig().getRequestedScreenOrientation());
        //http://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#FLAG_DISMISS_KEYGUARD
        //http://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#FLAG_SHOW_WHEN_LOCKED
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
        super.onCreate(savedInstanceState);
        mActionBarHelper.onCreate(savedInstanceState);

        mHandler = new Handler();
        mInAnimation = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.boxee_dialog_in);
        mOutAnimation = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.boxee_dialog_out);
        mOutAnimation.setAnimationListener(new AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                mUserMessage.setText("");
                mUserMessage.setVisibility(View.INVISIBLE);
            }
        });

        // mRemote = new BoxeeConnector();

        setContentView(R.layout.main);

        // Setup flipper
        mFlipper = (ViewFlipper) findViewById(R.id.now_playing_flipper);
        mFlipper.setInAnimation(this, R.anim.flip_in);
        mFlipper.setOutAnimation(this, R.anim.flip_out);

        // Find other views
        // mServerTitle = (TextView) findViewById(R.id.serverTitle);

        mImageThumbnail = (ImageView) findViewById(R.id.thumbnail);
        mButtonPlayPause = (ImageView) findViewById(R.id.buttonPlayPause);
        mTextMainTitle = (TextView) findViewById(R.id.textNowPlayingMainTitle);
        mTextSubTitle = (TextView) findViewById(R.id.textNowPlayingSubTitle);
        mUserMessage = (TextView) findViewById(R.id.textMessages);
        mTextElapsed = (TextView) findViewById(R.id.textElapsed);
        mDuration = (TextView) findViewById(R.id.textDuration);
        mElapsedBar = (ProgressBar) findViewById(R.id.progressTimeBar);
        findViewById(R.id.layoutTimeBar).setOnClickListener(this);
        mMediaDetails = (TextView) findViewById(R.id.textMediaDetails);
        mImeManager = (InputMethodManager) getSystemService(Service.INPUT_METHOD_SERVICE);
        mKeyboardText = (TextView) findViewById(R.id.keyboard_text);
        mKeyboardText.setKeyListener(new KeyListener() {

            @Override
            public boolean onKeyUp(View view, Editable text, int keyCode, KeyEvent event) {
                // I'm returning false here so the activity will handle the
                // onKeyUp
                return false;
            }

            @Override
            public boolean onKeyOther(View view, Editable text, KeyEvent event) {
                // I'm returning false here so the activity will handle the
                // onKeyOther
                return false;
            }

            @Override
            public boolean onKeyDown(View view, Editable text, int keyCode, KeyEvent event) {
                // Log.d(TAG,
                // "KeyListener.onKeyDown: "+(char)event.getUnicodeChar());
                // I'm returning false here so the activity will handle the
                // onKeyDown
                return false;
            }

            @Override
            public int getInputType() {
                return 0;
            }

            @Override
            public void clearMetaKeyState(View view, Editable content, int states) {
            }
        });

        setButtonAction(R.id.back, KeyEvent.KEYCODE_BACK);
        setButtonAction(R.id.buttonPlayPause, 0);
        setButtonAction(R.id.buttonStop, 0);
        setButtonAction(R.id.buttonSmallSkipBack, 0);
        setButtonAction(R.id.buttonSmallSkipFwd, 0);
        setButtonAction(R.id.buttonPreviousMedia, 0);
        setButtonAction(R.id.buttonNextMedia, 0);

        setButtonAction(R.id.buttonOpenKeyboard, 0);
        // mShakeDetector = new ShakeListener(getApplicationContext());
        // mShakeDetector.setOnShakeListener(this);
        mServersGroup = (SubMenu) findViewById(R.id.servers_group);
        mServers = new HashMap<String, ServerAddress>();

        startHelpOnFirstRun();
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        mActionBarHelper.onPostCreate(savedInstanceState);
    }

    protected ActionBarHelperBase getActionBarHelper() {
        return mActionBarHelper;
    }

    /** {@inheritDoc} */
    @Override
    public MenuInflater getMenuInflater() {
        return mActionBarHelper.getMenuInflater(super.getMenuInflater());
    }

    private void startHelpOnFirstRun() {
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
        final boolean ranBefore = preferences.getBoolean("has_ran_before", false);
        if (!ranBefore) {
            Editor e = preferences.edit();
            e.putBoolean("has_ran_before", true);
            e.commit();

            startHelpActivity();
        }
    }

    private void startHelpActivity() {
        Intent i = new Intent(getApplicationContext(), HelpUiActivity.class);
        startActivity(i);
    }

    public void startSetupActivity() {
        Intent i = new Intent(getApplicationContext(), SettingsActivity.class);
        startActivity(i);
    }

    @Override
    protected void onPause() {
        mSwipeStepSize = -1;
        mThisAcitivityPaused = true;
        // mShakeDetector.pause();
        RemoteApplication.getConfig().unlisten(this);
        // mHandler.removeCallbacks(mRequestStatusUpdateRunnable);

        removeAnyDialogs();

        doUnbindService();
        super.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        mThisAcitivityPaused = false;

        RemoteApplication.getConfig().listen(this);
        loadPreferences();

        // mShakeDetector.resume();

        mImageThumbnail.setKeepScreenOn(RemoteApplication.getConfig().getKeepScreenOn());

        doBindService();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.menu, menu);
        boolean retValue = false;
        retValue |= mActionBarHelper.onCreateOptionsMenu(menu);
        retValue |= super.onCreateOptionsMenu(menu);
        mServersMenu = menu.getItem(0);
        scanForServersMenu();
        return retValue;
    }

    @Override
    public boolean onMenuItemSelected(int featureId, MenuItem item) {
        switch (item.getItemId()) {
        case R.id.menu_exit_app:
            if (mBoundService != null)
                mBoundService.forceStop();
            finish();
            return true;
        case R.id.menu_help:
            startHelpActivity();
            return true;
        case R.id.menu_settings:
            startSetupActivity();
            return true;
        case R.id.menu_rescan_servers:
            rescanForServers();
            return true;
        default:
            return super.onMenuItemSelected(featureId, item);
        }
    }

    @Override
    public void onClick(View v) {
        final int id = v.getId();
        switch (id) {

        case R.id.buttonPlayPause:
            if (mBoundService != null)
                mBoundService.remoteFlipPlayPause();
            break;

        case R.id.buttonStop:
            if (mBoundService != null)
                mBoundService.remoteStop();
            break;

        case R.id.buttonSmallSkipBack:
        case R.id.buttonSmallSkipFwd:
            if (mBoundService != null)
                mBoundService.remoteSeekOffset((id == R.id.buttonSmallSkipFwd) ? 30f : -30f);
            break;
        case R.id.back:
            if (mBoundService != null)
                mBoundService.remoteBack();
            break;
        case R.id.buttonOpenKeyboard:
            showOnScreenKeyboard();
            break;
        case R.id.progressTimeBar:
        case R.id.layoutTimeBar:
        case R.id.textElapsed:
        case R.id.textDuration:
            showFragmentDialog(FragmentAlertDialogSupport.DIALOG_MEDIA_TIME_SEEK);
            break;
        }

    }

    private void showFragmentDialog(final int dialog_id) {
        removeAnyDialogs();

        DialogFragment newFragment = FragmentAlertDialogSupport.newInstance(dialog_id);
        if (newFragment == null)
            return;

        newFragment.show(getSupportFragmentManager(), "dialog");
    }

    public void onFragmentDialogReady(Dialog dialog, final int dialog_id) {
        removeAnyDialogs();

        mOpenDialog = dialog;

        if (dialog_id == FragmentAlertDialogSupport.DIALOG_MEDIA_TIME_SEEK) {
            mMediaSeekBarInUserChange = false;
            mSeekToPercentRequest = -1;
            mMediaSeekBar = (SeekBar) mOpenDialog.findViewById(R.id.time_seek_bar);
            mMediaSeekBar.setMax(mTotalDuration);
            mMediaSeekBar.setProgress(mCurrentTime);
            mMediaSeekDialogTime = (TextView) mOpenDialog.findViewById(R.id.time_string);
            mMediaSeekDialogTime.setText(mTextElapsed.getText());
        }
    }

    public void removeAnyDialogs() {
        // DialogFragment.show() will take care of adding the fragment
        // in a transaction. We also want to remove any currently showing
        // dialog, so make our own transaction and take care of that here.
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        Fragment prev = getSupportFragmentManager().findFragmentByTag("dialog");
        if (prev != null) {
            ft.remove(prev);
        }
        ft.addToBackStack(null);

        if (mOpenDialog != null)
            mOpenDialog.dismiss();

        mOpenDialog = null;
    }

    private void flipTo(int page) {
        if (mFlipper.getDisplayedChild() != page)
            mFlipper.setDisplayedChild(page);
    }

    @Override
    public void hello(final ServerState serverState) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                refreshPlayingStateChanged(serverState, true);
                if (serverState.isMediaActive()) {
                    refreshMetadataChanged(serverState);
                }
            }
        });
    }

    @Override
    public void onMediaPlayingStateChanged(final ServerState serverState) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                refreshPlayingStateChanged(serverState, false);
            }
        });
    }

    @Override
    public void onMediaPlayingProgressChanged(final ServerState serverState) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                refreshPlayingProgressChanged(serverState);
            }
        });
    }

    @Override
    public void onMediaMetadataChanged(final ServerState serverState) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                refreshMetadataChanged(serverState);
            }
        });
    }

    @Override
    public void onKeyboardStateChanged(final ServerState serverState) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (serverState.isKeyboardActive() && mKeyboardText.getVisibility() != View.VISIBLE) {
                    Log.d(TAG, "Keyboard is active. Showing textbox");
                    showOnScreenKeyboard();
                } else if (!serverState.isKeyboardActive() && mKeyboardText.getVisibility() == View.VISIBLE) {
                    Log.d(TAG, "Keyboard is inactive. Hiding textbox");
                    mKeyboardText.setVisibility(View.INVISIBLE);
                    mImeManager.hideSoftInputFromWindow(mKeyboardText.getWindowToken(), 0);
                }
            }
        });
    }

    private void refreshPlayingStateChanged(ServerState serverState, boolean forceChanged) {
        final boolean isPlaying = serverState.isMediaPlaying();
        final boolean newIsMediaActive = serverState.isMediaActive();
        final boolean mediaDataChanged = forceChanged || (newIsMediaActive != mIsMediaActive);
        mIsMediaActive = newIsMediaActive;

        if (mIsMediaActive)
            mButtonPlayPause
                    .setImageResource(isPlaying ? R.drawable.icon_osd_pause_on : R.drawable.icon_osd_play_on);
        if (!mediaDataChanged)
            return;

        if (mIsMediaActive) {
            setMediaTitle(serverState);
            if (mMediaDetails != null)
                mMediaDetails.setText(serverState.getMediaPlot());
            refreshPlayingProgressChanged(serverState);

            flipTo(PAGE_NOWPLAYING);

            // if (mBoundService!=null)
            // mBoundService.showPlayingNotification(title);
            // Intent i = new Intent(this, ServerRemoteService.class);
            // i.putExtra(ServerRemoteService.KEY_DATA_TITLE, title);
            // startService(i);
        } else {
            flipTo(PAGE_NOTPLAYING);

            mTextMainTitle.setText("");
            mTextSubTitle.setText("");
            if (mMediaDetails != null)
                mMediaDetails.setText("");
            mImageThumbnail.setImageResource(R.drawable.no_media_background);
        }
    }

    private void refreshPlayingProgressChanged(ServerState serverState) {
        mDuration.setText(serverState.getMediaTotalTime());
        mTotalDuration = ServerRemoteService.hmsToSeconds(serverState.getMediaTotalTime());
        mTextElapsed.setText(serverState.getMediaCurrentTime());
        mCurrentTime = ServerRemoteService.hmsToSeconds(serverState.getMediaCurrentTime());

        mElapsedBar.setProgress(serverState.getMediaProgressPercent());

        if (!mMediaSeekBarInUserChange) {
            if (mMediaSeekBar != null) {
                mMediaSeekBar.setProgress(mCurrentTime);
            }
            if (mMediaSeekDialogTime != null) {
                mMediaSeekDialogTime.setText(serverState.getMediaCurrentTime());
            }
        }

    }

    private void refreshMetadataChanged(ServerState serverState) {
        mIsMediaActive = serverState.isMediaActive();
        if (mIsMediaActive) {
            Bitmap poster = serverState.getMediaPoster();
            if (poster == null)
                mImageThumbnail.setImageResource(R.drawable.no_media_background);
            else
                mImageThumbnail.setImageBitmap(poster);
            setMediaTitle(serverState);
            if (mMediaDetails != null)
                mMediaDetails.setText(serverState.getMediaPlot());
        } else {
            mImageThumbnail.setImageResource(R.drawable.no_media_background);
            mTextMainTitle.setText("");
            mTextSubTitle.setText("");
            if (mMediaDetails != null)
                mMediaDetails.setText("");
        }
    }

    private void setMediaTitle(ServerState serverState) {
        String showTitle = serverState.getShowTitle();
        String title = serverState.getMediaTitle();
        String filename = serverState.getMediaFilename();
        String season = serverState.getShowSeason();
        String episode = serverState.getShowEpisode();
        String mainTitle = "";
        String subTitle = "";
        if (!TextUtils.isEmpty(showTitle)) {
            mainTitle = showTitle;
            if (!TextUtils.isEmpty(season))
                subTitle = "S" + season;
            if (!TextUtils.isEmpty(episode))
                subTitle += "E" + episode;
        }

        if (!TextUtils.isEmpty(title)) {
            if (TextUtils.isEmpty(mainTitle)) {
                mainTitle = title;
            } else {
                subTitle += ": ";
                subTitle += title;
            }
        }

        if (TextUtils.isEmpty(mainTitle))
            mainTitle = filename;

        mTextMainTitle.setText(mainTitle);
        mTextSubTitle.setText(subTitle);
    }

    /**
     * Handler an android keypress and send it to boxee if appropriate.
     */
    @Override
    public boolean onKeyDown(final int keyCode, final KeyEvent event) {
        switch (keyCode) {
        case KeyEvent.KEYCODE_BACK:
            if (RemoteApplication.getConfig().getHandleBack()) {
                Log.d(TAG, "Will handle back");
                if (mBoundService != null)
                    mBoundService.remoteBack();
                return true;
            } else {
                Log.d(TAG, "Will NOT handle back");
                return super.onKeyDown(keyCode, event);
            }

        case KeyEvent.KEYCODE_DPAD_CENTER:
            if (mBoundService != null)
                mBoundService.remoteSelect();
            return true;

        case KeyEvent.KEYCODE_DPAD_DOWN:
            if (mBoundService != null)
                mBoundService.remoteDown();
            return true;

        case KeyEvent.KEYCODE_DPAD_UP:
            if (mBoundService != null)
                mBoundService.remoteUp();
            return true;

        case KeyEvent.KEYCODE_DPAD_LEFT:
            if (mBoundService != null)
                mBoundService.remoteLeft();
            return true;

        case KeyEvent.KEYCODE_DPAD_RIGHT:
            if (mBoundService != null)
                mBoundService.remoteRight();
            return true;

        case KeyEvent.KEYCODE_VOLUME_UP:
        case KeyEvent.KEYCODE_VOLUME_DOWN:
            final int volumeFactor = (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) ? -1 : 1;
            final int volumeOffset = (volumeFactor * RemoteApplication.getConfig().getVolumeStep());
            if (mBoundService != null)
                mBoundService.remoteVolumeOffset(volumeOffset);
            return true;
        case KeyEvent.KEYCODE_DEL:
            if (mBoundService != null)
                mBoundService.remoteKeypress((char) 8);
            return true;
        default:
            final char unicodeChar = (char) event.getUnicodeChar();

            // Log.d(TAG, "Unicode is " + ((char)unicodeChar));

            if (Character.isLetterOrDigit(unicodeChar) || msPunctuation.contains(unicodeChar)) {
                // Log.d(TAG, "handling " + ((char)unicodeChar));
                if (mBoundService != null)
                    mBoundService.remoteKeypress(unicodeChar);
                return true;
            } else {
                return super.onKeyDown(keyCode, event);
            }
        }
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        switch (keyCode) {
        case KeyEvent.KEYCODE_BACK:
            if (RemoteApplication.getConfig().getHandleBack()) {
                return true;
            } else {
                return super.onKeyUp(keyCode, event);
            }
        case KeyEvent.KEYCODE_VOLUME_UP:
        case KeyEvent.KEYCODE_VOLUME_DOWN:
            // I catch these two to handle the annoying sound feedback given
            // in some hand-held (Samsung Tab, e.g.)
            return true;

        default:
            return super.onKeyDown(keyCode, event);
        }
    }

    private Runnable mClearUserMessageRunnable = new Runnable() {
        @Override
        public void run() {
            mUserMessage.startAnimation(mOutAnimation);
        }
    };

    private Runnable mSetUserMessageRunnable = new Runnable() {
        @Override
        public void run() {
            mUserMessage.setText(mUserMessageString);
            mUserMessage.setVisibility(View.VISIBLE);
            mUserMessage.startAnimation(mInAnimation);
        }
    };

    @Override
    public void showMessage(final String userMessage, final int messageTime) {
        mUserMessageString = userMessage;
        mHandler.removeCallbacks(mClearUserMessageRunnable);
        mHandler.removeCallbacks(mSetUserMessageRunnable);
        mHandler.postAtFrontOfQueue(mSetUserMessageRunnable);
        mHandler.postDelayed(mClearUserMessageRunnable, messageTime);
    }

    private int mSwipeStepSize = -1;

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        int x = (int) event.getX();
        int y = (int) event.getY();
        if (mSwipeStepSize < 1)
            mSwipeStepSize = RemoteApplication.getConfig().getSwipeStepSize(getApplicationContext());
        switch (event.getAction()) {
        case MotionEvent.ACTION_UP:
            if (!mDragged) {
                if (mBoundService != null)
                    mBoundService.remoteSelect();
                return true;
            }
            break;

        case MotionEvent.ACTION_DOWN:
            mTouchPoint.x = x;
            mTouchPoint.y = y;
            mDragged = false;
            return true;

        case MotionEvent.ACTION_MOVE:
            if (x - mTouchPoint.x > mSwipeStepSize) {
                if (mBoundService != null)
                    mBoundService.remoteRight();
                mTouchPoint.x += mSwipeStepSize;
                mTouchPoint.y = y;
                mDragged = true;
                return true;
            } else if (mTouchPoint.x - x > mSwipeStepSize) {
                if (mBoundService != null)
                    mBoundService.remoteLeft();
                mTouchPoint.x -= mSwipeStepSize;
                mTouchPoint.y = y;
                mDragged = true;
                return true;
            } else if (y - mTouchPoint.y > mSwipeStepSize) {
                if (mBoundService != null)
                    mBoundService.remoteDown();
                mTouchPoint.y += mSwipeStepSize;
                mTouchPoint.x = x;
                mDragged = true;
                return true;
            } else if (mTouchPoint.y - y > mSwipeStepSize) {
                if (mBoundService != null)
                    mBoundService.remoteUp();
                mTouchPoint.y -= mSwipeStepSize;
                mTouchPoint.x = x;
                mDragged = true;
                return true;
            }
            break;
        }

        return false;
    }

    /**
     * Set up a navigation button in the UI. Sets the focus to false so that we
     * can capture KEYCODE_DPAD_CENTER.
     * 
     * @param id id of the button in the resource file
     * @param keycode keyCode we should send to Boxee when this button is
     *            pressed
     */
    private void setButtonAction(int id, final int keyCode) {
        View button = findViewById(id);
        if (button == null)
            return;
        button.setFocusable(false);
        button.setTag(new Integer(keyCode));
        button.setOnClickListener(this);
    }

    /**
     * Set the state of the application based on prefs. This should be called
     * after every preference change or when starting up.
     * 
     * @param prefs
     */
    private void loadPreferences() {
        mSwipeStepSize = RemoteApplication.getConfig().getSwipeStepSize(getApplicationContext());
        // Setup the proper pageflipper page:
        flipTo(PAGE_NOTPLAYING);

        // Setup the HTTP timeout.
        int timeout_ms = RemoteApplication.getConfig().getTimeout();
        HttpRequest.setTimeout(timeout_ms);

        // Parse the credentials, if needed.
        String user = RemoteApplication.getConfig().getUser();
        String password = RemoteApplication.getConfig().getPassword();
        if (!TextUtils.isEmpty(password))
            HttpRequest.setUserPassword(user, password);
        else
            HttpRequest.setUserPassword(null, null);
    }

    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        if (fromUser) {
            mSeekToPercentRequest = 100f * ((float) progress / (float) seekBar.getMax());
            String totalTime = mDuration.getText().toString();
            int totalSeconds = ServerRemoteService.hmsToSeconds(totalTime);
            if (totalSeconds > 0) {
                int newSeconds = (int) (mSeekToPercentRequest * ((float) totalSeconds)) / 100;
                mMediaSeekDialogTime.setText(secondsToHms(newSeconds));
            } else {
                mMediaSeekDialogTime.setText("---");
            }
        }
    }

    private static String secondsToHms(int totalSeconds) {
        int hours = totalSeconds / (60 * 60);
        int minutes = (totalSeconds / 60) % 60;
        int seconds = totalSeconds % 60;

        if (hours > 0)
            return String.format("%d:%02d:%02d", hours, minutes, seconds);
        else
            return String.format("%02d:%02d", minutes, seconds);
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
        mSeekToPercentRequest = -1f;
        mMediaSeekBarInUserChange = true;
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
        mMediaSeekBarInUserChange = false;
        if (mSeekToPercentRequest >= 0) {
            if (mBoundService != null)
                mBoundService.remoteSeekTo(mSeekToPercentRequest);
        }
    }

    /**
     * Callback when user alters preferences.
     */
    public void onSharedPreferenceChanged(SharedPreferences prefs, String pref) {
        loadPreferences();
    }

    @Override
    public void onServerConnectionStateChanged(final State state) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                removeAnyDialogs();

                switch (state) {
                case DEAD:
                    /* OK */
                    break;
                case DISCOVERYING:
                    showFragmentDialog(FragmentAlertDialogSupport.DIALOG_DISCOVERYING);
                    break;
                case ERROR_NO_PASSWORD:
                    showFragmentDialog(FragmentAlertDialogSupport.DIALOG_NO_PASSWORD);
                    break;
                case ERROR_NO_SERVER:
                    showFragmentDialog(FragmentAlertDialogSupport.DIALOG_NO_SERVER);
                    break;
                case IDLE:
                    break;
                }

                String serverName = mBoundService != null ? mBoundService.getServerName() : null;
                if (TextUtils.isEmpty(serverName)) {
                    mActionBarHelper.onTitleChanged(getText(R.string.not_connected));
                } else {
                    mActionBarHelper.onTitleChanged(getString(R.string.connected_to, serverName));
                }
            }
        });
    }

    @Override
    public void onShake() {
        Log.d(TAG, "Shake detect!");
    }

    private void showOnScreenKeyboard() {
        mKeyboardText.setVisibility(View.VISIBLE);
        mKeyboardText.requestFocus();
        mImeManager.showSoftInput(mKeyboardText, InputMethodManager.SHOW_FORCED);
    }

    public void rescanForServers() {
        if (mBoundService != null)
            mBoundService.remoteRescanForServers();
    }

    public void scanForServersMenu() {
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                if (mServerDiscoverer != null)
                    mServerDiscoverer.setReceiver(null);
                mServerDiscoverer = new BoxeeDiscovererThread(RemoteUiActivity.this, RemoteUiActivity.this);
                mServerDiscoverer.start();
                return null;
            }
        }.execute();
    }

    @Override
    public void addAnnouncedServers(ArrayList<ServerAddress> servers) {
        mServersMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                if (item.getSubMenu().size() < 1) {
                    scanForServersMenu();
                }
                return false;
            }
        });

        for (ServerAddress server : servers) {
            final String name = server.name() != null ? server.name() : server.address().getHostName();
            final String serverNameKey = server.type() + "@" + name;
            mServersMenu.getSubMenu().add(serverNameKey);
            mServersMenu.getSubMenu().getItem(mServersMenu.getSubMenu().size() - 1)
                    .setOnMenuItemClickListener(new OnMenuItemClickListener() {
                        @Override
                        public boolean onMenuItemClick(MenuItem item) {
                            ServerAddress server = mServers.get(item.getTitle());
                            if (server == null) {
                                return true;
                            }
                            RemoteApplication.getConfig().putServer(server, false);
                            rescanForServers();
                            return false;
                        }
                    });
            mServers.put(serverNameKey, server);
        }
    }
}