org.mythdroid.remote.TVRemote.java Source code

Java tutorial

Introduction

Here is the source code for org.mythdroid.remote.TVRemote.java

Source

/*
MythDroid: Android MythTV Remote
Copyright (C) 2009-2010 foobum@gmail.com
    
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.mythdroid.remote;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

import org.mythdroid.Enums.ArtworkType;
import org.mythdroid.Enums.Extras;
import org.mythdroid.Globals;
import org.mythdroid.R;
import org.mythdroid.Enums.Key;
import org.mythdroid.data.Program;
import org.mythdroid.data.Video;
import org.mythdroid.data.Program.Commercial;
import org.mythdroid.frontend.FrontendLocation;
import org.mythdroid.frontend.MovePlaybackHelper;
import org.mythdroid.frontend.OnFrontendReady;
import org.mythdroid.frontend.OnPlaybackMoved;
import org.mythdroid.mdd.MDDChannelListener;
import org.mythdroid.mdd.MDDManager;
import org.mythdroid.resource.Messages;
import org.mythdroid.util.ErrUtil;
import org.mythdroid.util.LogUtil;
import org.mythdroid.views.CutListDrawable;
import org.mythdroid.activities.Guide;
import org.mythdroid.activities.VideoPlayer;

import android.R.drawable;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.DialogInterface.OnClickListener;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.view.MenuItemCompat;
import android.util.DisplayMetrics;
import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnLongClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;

/** Remote for controlling tv/recording/video playback */
public class TVRemote extends Remote {

    /** Menu entry identifiers */
    final private static int MENU_OSDMENU = 0, MENU_GESTURE = 1, MENU_BUTTON = 2, MENU_MOVE = 4;

    final private static int DIALOG_NUMPAD = 1, DIALOG_GUIDE = 2, DIALOG_QUIT = 3, DIALOG_MOVE = 4;

    private static SparseArray<Key> ctrls = new SparseArray<Key>(20), nums = new SparseArray<Key>(12);

    static {
        nums.put(R.id.one, Key.ONE);
        nums.put(R.id.two, Key.TWO);
        nums.put(R.id.three, Key.THREE);
        nums.put(R.id.four, Key.FOUR);
        nums.put(R.id.five, Key.FIVE);
        nums.put(R.id.six, Key.SIX);
        nums.put(R.id.seven, Key.SEVEN);
        nums.put(R.id.eight, Key.EIGHT);
        nums.put(R.id.nine, Key.NINE);
        nums.put(R.id.zero, Key.ZERO);
        nums.put(R.id.enter, Key.ENTER);
        ctrls.put(R.id.back, Key.ESCAPE);
        ctrls.put(R.id.num, Key.NUMPAD);
        ctrls.put(R.id.volUp, Key.VOL_UP);
        ctrls.put(R.id.volDown, Key.VOL_DOWN);
        ctrls.put(R.id.volMute, Key.VOL_MUTE);
        ctrls.put(R.id.up, Key.UP);
        ctrls.put(R.id.down, Key.DOWN);
        ctrls.put(R.id.left, Key.LEFT);
        ctrls.put(R.id.right, Key.RIGHT);
        ctrls.put(R.id.enter, Key.ENTER);
        ctrls.put(R.id.pause, Key.PAUSE);
        ctrls.put(R.id.seekBack, Key.SEEK_BACK);
        ctrls.put(R.id.skipBack, Key.SKIP_BACK);
        ctrls.put(R.id.skipForward, Key.SKIP_FORWARD);
        ctrls.put(R.id.seekForward, Key.SEEK_FORWARD);
        ctrls.put(R.id.info, Key.INFO);
        ctrls.put(R.id.skip, Key.SKIP_COMMERCIAL);
        ctrls.put(R.id.guide, Key.GUIDE);
    }

    final private Handler handler = new Handler();
    final private Object feLock = new Object();
    final private Context ctx = this;
    private TextView titleView = null;
    private SeekBar pBar = null;
    private Timer timer = null;
    private int jumpChan = -1, lastProgress = 0, endTime = -1, videoId = -1, seekTo = 0;
    private UpdateStatusTask updateStatus = null;
    private MDDManager mddMgr = null;
    private String lastFilename = null, filename = null, videoTitle = null;

    private MovePlaybackHelper moveHelper = null;

    private boolean paused = false, livetv = false, jump = false, gesture = false, wasPaused = false;

    /** Run periodically to update title and progress bar */
    final private class UpdateStatusTask extends TimerTask {
        @Override
        public void run() {

            FrontendLocation loc = null;

            synchronized (feLock) {
                if (feMgr == null || !feMgr.isConnected())
                    return;
                try {
                    loc = feMgr.getLoc();
                } catch (IOException e) {
                    ErrUtil.postErr(ctx, e);
                    done();
                    return;
                } catch (IllegalArgumentException e) {
                    ErrUtil.postErr(ctx, e);
                    done();
                    return;
                }
            }

            if (!loc.video) {
                done();
                return;
            }

            final String name = loc.filename;

            if (livetv && !name.equals(lastFilename))
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Program prog = Globals.getBackend().getRecording(name);
                            titleView.setText(prog.Title);
                        } catch (IOException e) {
                            ErrUtil.postErr(ctx, e);
                            return;
                        }
                    }
                });
            else if (!livetv)
                pBar.setProgress(loc.position);

        }
    };

    final private Runnable ready = new Runnable() {
        @Override
        public void run() {

            synchronized (feLock) {
                if (feMgr == null)
                    return;
            }

            if (!setupStatus())
                return;

            if (mddMgr == null) {
                updateStatus = new UpdateStatusTask();
                if (timer == null)
                    timer = new Timer();
                timer.scheduleAtFixedRate(updateStatus, 8000, 8000);
            } else
                mddMgr.setChannelListener(mddListener);

            if (seekTo != 0) {
                synchronized (feLock) {
                    try {
                        feMgr.seekTo(seekTo);
                    } catch (IOException e) {
                        ErrUtil.err(ctx, e);
                    }
                }
                seekTo = 0;
            }

            if (jump)
                dismissLoadingDialog();

        }
    };

    final private Runnable jumpRun = new Runnable() {
        @Override
        public void run() {

            synchronized (feLock) {
                if (feMgr == null)
                    return;
            }

            try {

                if (livetv) {

                    FrontendLocation loc = null;
                    synchronized (feLock) {
                        if (!(loc = feMgr.getLoc()).livetv)
                            feMgr.jumpTo("livetv"); //$NON-NLS-1$
                    }

                    synchronized (feLock) {
                        loc = feMgr.getLoc();
                    }
                    if (!loc.livetv) {
                        initError(Messages.getString("TVRemote.1")); //$NON-NLS-1$
                        return;
                    }

                    if (jumpChan >= 0)
                        synchronized (feLock) {
                            while (feMgr.getLoc().position < 1)
                                Thread.sleep(500);
                            feMgr.playChan(jumpChan);
                        }

                }

                else if (filename != null)
                    synchronized (feLock) {
                        feMgr.playFile(filename);
                    }
                else
                    synchronized (feLock) {
                        feMgr.playRec(Globals.curProg);
                    }

            } catch (IOException e) {
                initError(e.getMessage());
                return;
            } catch (IllegalArgumentException e) {
                ErrUtil.report(e);
                initError(e.getMessage());
                return;
            } catch (InterruptedException e) {
            }

            handler.post(ready);

        }

    };

    final private OnFrontendReady onReady = new OnFrontendReady() {
        @Override
        public void onFrontendReady(String name) {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    showDialog(DIALOG_MOVE);
                }
            });
        }
    };

    final private OnPlaybackMoved onMoved = new OnPlaybackMoved() {
        @Override
        public void onPlaybackMoved() {
            jump = true;
            done();
        }
    };

    final private MDDChannelListener mddListener = new MDDChannelListener() {

        @Override
        public void onChannel(final String channel, final String title, final String subtitle) {
            if (videoTitle != null)
                return;
            handler.post(new Runnable() {
                @Override
                public void run() {
                    titleView.setText(title);
                }
            });
        }

        @Override
        public void onProgress(final int pos) {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    if (livetv)
                        return;
                    pBar.setProgress(pos);
                    lastProgress = pos;
                }
            });
        }

        @Override
        public void onExit() {
            done();
        }

    };

    @Override
    public void onCreate(Bundle icicle) {

        super.onCreate(icicle);
        Intent intent = getIntent();

        livetv = intent.hasExtra(Extras.LIVETV.toString());
        jump = !intent.hasExtra(Extras.DONTJUMP.toString());
        if (intent.hasExtra(Extras.FILENAME.toString()))
            filename = intent.getStringExtra(Extras.FILENAME.toString());
        if (intent.hasExtra(Extras.TITLE.toString()))
            videoTitle = intent.getStringExtra(Extras.TITLE.toString());
        if (intent.hasExtra(Extras.VIDEOID.toString()))
            videoId = intent.getIntExtra(Extras.VIDEOID.toString(), -1);
        jumpChan = intent.getIntExtra(Extras.JUMPCHAN.toString(), -1);
        seekTo = intent.getIntExtra(Extras.SEEKTO.toString(), 0);

        if (seekTo != 0 || jumpChan != -1)
            jump = true;

        moveHelper = new MovePlaybackHelper(this, onReady, onMoved);

        if (!livetv
                && !PreferenceManager.getDefaultSharedPreferences(this).getBoolean("streamExternalPlayer", false) //$NON-NLS-1$
        )
            addHereToFrontendChooser(VideoPlayer.class);

        nextActivity = getClass();
        setResult(RESULT_OK);

        ctrls.put(R.id.rec, livetv ? Key.RECORD : Key.EDIT);

        gesture = PreferenceManager.getDefaultSharedPreferences(this).getString("tvDefaultStyle", "") //$NON-NLS-1$ //$NON-NLS-2$
                .equals(Messages.getString("TVRemote.0")); // Gesture //$NON-NLS-1$

        setupViews(gesture);
        listenToGestures(gesture);

    }

    @Override
    public void onResume() {
        super.onResume();
        try {
            synchronized (feLock) {
                feMgr = Globals.getFrontend(this);
            }
        } catch (IOException e) {
            ErrUtil.err(this, e);
            finish();
            return;
        }

        if (mddMgr == null)
            try {
                mddMgr = new MDDManager(feMgr.addr);
            } catch (IOException e) {
                mddMgr = null;
                timer = new Timer();
            }

        if (jump && !wasPaused) {
            showLoadingDialog();
            Globals.runOnThreadPool(jumpRun);
        } else
            handler.post(ready);
    }

    private void cleanup() {

        synchronized (feLock) {
            if (feMgr != null)
                feMgr.disconnect();
            feMgr = null;
        }

        if (timer != null) {
            timer.cancel();
            timer.purge();
            timer = null;
        }

        findViewById(R.id.tv_remote).setBackgroundDrawable(null);

    }

    @Override
    public void onPause() {
        super.onPause();
        cleanup();
        wasPaused = true;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        cleanup();
        if (mddMgr != null)
            mddMgr.shutdown();
        mddMgr = null;
    }

    @Override
    public void onConfigurationChanged(Configuration config) {
        super.onConfigurationChanged(config);
        setupViews(gesture);
        if (feMgr == null)
            onResume();
        else
            setupStatus();
    }

    @Override
    public void onAction() {
        if (mddMgr != null || updateStatus == null)
            return;
        updateStatus.run();
    }

    @Override
    public void onClick(View v) {

        final Key key = (Key) v.getTag();

        if (key == Key.PAUSE) {
            paused = !paused;
            ((ImageButton) v).setImageResource(paused ? R.drawable.play : R.drawable.pause);
        }

        else if (key == Key.NUMPAD) {
            showDialog(DIALOG_NUMPAD);
            return;
        }

        synchronized (feLock) {
            if (feMgr != null)
                try {
                    feMgr.sendKey(key);
                } catch (IOException e) {
                    ErrUtil.err(this, e);
                } catch (IllegalArgumentException e) {
                    ErrUtil.reportErr(this, e);
                }
        }

        if (key == Key.GUIDE)
            startActivityForResult(new Intent().setClass(ctx, NavRemote.class), 0);

        super.onClick(v);

    }

    @Override
    public Dialog onCreateDialog(int id) {

        switch (id) {

        case DIALOG_NUMPAD:
            final Dialog pad = new Dialog(this);
            pad.setContentView(R.layout.numpad);
            pad.findViewById(android.R.id.title).setVisibility(View.GONE);
            View button;
            int size = nums.size();
            for (int i = 0; i < size; i++) {
                int viewId = nums.keyAt(i);
                button = pad.findViewById(viewId);
                button.setTag(nums.get(viewId));
                button.setOnClickListener(this);
            }
            return pad;

        case DIALOG_GUIDE:
            return new AlertDialog.Builder(ctx).setIcon(drawable.ic_menu_upload_you_tube)
                    .setTitle(R.string.dispGuide)
                    .setAdapter(new ArrayAdapter<String>(ctx, R.layout.simple_list_item_1, new String[] {}), null)
                    .create();

        case DIALOG_QUIT:
            OnClickListener cl = new OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                    switch (which) {
                    case Dialog.BUTTON_POSITIVE:
                        jump = true;
                        break;
                    case Dialog.BUTTON_NEUTRAL:
                        jump = false;
                        setResult(REMOTE_RESULT_FINISH);
                        break;
                    default:
                        return;

                    }
                    done();
                }
            };

            return new AlertDialog.Builder(ctx).setTitle(R.string.leaveRemote).setMessage(R.string.haltPlayback)
                    .setPositiveButton(R.string.yes, cl).setNeutralButton(R.string.no, cl)
                    .setNegativeButton(R.string.cancel, cl).create();

        case FRONTEND_CHOOSER:
            return moveHelper.frontendChooserDialog(feMgr, feLock);

        case DIALOG_MOVE:
            return moveHelper.movePromptDialog(feMgr, feLock);

        }

        return super.onCreateDialog(id);

    }

    @Override
    public void onPrepareDialog(int id, final Dialog dialog) {

        if (id == DIALOG_MOVE) {
            moveHelper.prepareMoveDialog(dialog);
            return;
        }
        if (id != DIALOG_GUIDE) {
            super.onPrepareDialog(id, dialog);
            return;
        }

        final ListView lv = ((AlertDialog) dialog).getListView();

        lv.setAdapter(new ArrayAdapter<String>(ctx, R.layout.simple_list_item_1,
                new String[] { Messages.getString("TVRemote.2"), //$NON-NLS-1$
                        Messages.getString("TVRemote.3") + feMgr.name //$NON-NLS-1$
                }));

        lv.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> av, View v, int pos, long id) {
                dialog.dismiss();

                if (pos == 0)
                    startActivity(new Intent().setClass(ctx, Guide.class));
                else {
                    synchronized (feLock) {
                        try {
                            if (feMgr != null)
                                feMgr.sendKey(Key.GUIDE);
                        } catch (IOException e) {
                            ErrUtil.err(ctx, e);
                            return;
                        }
                    }
                    startActivityForResult(new Intent().setClass(ctx, NavRemote.class), 0);
                }
            }
        });
    }

    /** Compose the menu */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        menu.add(Menu.NONE, MENU_BUTTON, Menu.NONE, R.string.btnIface).setIcon(drawable.ic_menu_add);
        menu.add(Menu.NONE, MENU_GESTURE, Menu.NONE, R.string.gestIface).setIcon(R.drawable.ic_menu_finger);
        MenuItemCompat.setShowAsAction(
                menu.add(Menu.NONE, MENU_OSDMENU, Menu.NONE, R.string.osdMenu).setIcon(drawable.ic_menu_more),
                MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
        MenuItemCompat.setShowAsAction(
                menu.add(Menu.NONE, MENU_MOVE, Menu.NONE, R.string.moveTo).setIcon(drawable.ic_menu_set_as),
                MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        if (gesture) {
            menu.findItem(MENU_BUTTON).setVisible(true);
            menu.findItem(MENU_GESTURE).setVisible(false);
        } else {
            menu.findItem(MENU_BUTTON).setVisible(false);
            menu.findItem(MENU_GESTURE).setVisible(true);
        }
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        switch (item.getItemId()) {
        case MENU_GESTURE:
            gesture = true;
            break;
        case MENU_BUTTON:
            gesture = false;
            break;
        case MENU_OSDMENU:
            try {
                synchronized (feLock) {
                    if (feMgr != null)
                        feMgr.sendKey(Key.MENU);
                }
            } catch (IOException e) {
                ErrUtil.err(this, e);
            }
            return true;
        case MENU_MOVE:
            showDialog(FRONTEND_CHOOSER);
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }

        setupViews(gesture);
        setupStatus();
        listenToGestures(gesture);
        return true;

    }

    @Override
    public boolean onKeyDown(int code, KeyEvent event) {
        if (code == KeyEvent.KEYCODE_BACK) {
            showDialog(DIALOG_QUIT);
            return true;
        }
        return super.onKeyDown(code, event);
    }

    /**
     * Setup the interactive views
     * @param gesture true for 'gesture' layout, false for 'button'
     */
    private void setupViews(boolean gesture) {

        if (gesture) {
            setContentView(R.layout.tv_gesture_remote);
            for (int id : new int[] { R.id.back, R.id.num, R.id.volMute, R.id.guide, R.id.enter, R.id.info,
                    R.id.skip }) {
                View v = findViewById(id);
                v.setTag(ctrls.get(id));
                v.setOnClickListener(this);
                v.setFocusable(false);

            }
        } else {
            setContentView(R.layout.tv_remote);

            int size = ctrls.size();

            for (int i = 0; i < size; i++) {

                int id = ctrls.keyAt(i);
                final View v = findViewById(id);
                Key key = ctrls.get(id);
                v.setOnClickListener(this);
                v.setFocusable(false);
                v.setTag(key);

            }
        }

        View v = findViewById(R.id.guide);

        if (!livetv)
            v.setVisibility(View.INVISIBLE);
        else
            v.setOnLongClickListener(new OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    showDialog(DIALOG_GUIDE);
                    return true;
                }
            });

        v = findViewById(R.id.skip);
        v.setOnLongClickListener(new OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                try {
                    synchronized (feLock) {
                        if (feMgr != null)
                            feMgr.sendKey(Key.SKIP_PREV_COMMERCIAL);
                    }
                } catch (IOException e) {
                    ErrUtil.err(ctx, e);
                }
                return true;
            }
        });

        removeDialog(DIALOG_NUMPAD);

    }

    /** Setup the status widgets (progress bar, program title) */
    private boolean setupStatus() {

        titleView = (TextView) findViewById(R.id.title);
        pBar = (SeekBar) findViewById(R.id.progress);
        titleView.setFocusable(false);
        pBar.setFocusable(false);

        if (livetv || videoTitle != null)
            pBar.setVisibility(View.GONE);

        if (videoTitle != null) {
            titleView.setText(videoTitle);
            setBackground(videoId, null, findViewById(R.id.tv_remote));
            return true;
        }

        FrontendLocation loc = null;

        try {
            synchronized (feLock) {
                loc = feMgr.getLoc();
            }
            if (!loc.video) {
                initError(null);
                return false;
            }
            if (loc.filename.equals("Video")) { //$NON-NLS-1$ 
                pBar.setVisibility(View.GONE);
                return true;
            }
            Globals.curProg = Globals.getBackend().getRecording(loc.filename);
            Globals.curProg.Path = loc.filename;
        } catch (IOException e) {
            initError(e.getMessage());
            return false;
        } catch (IllegalArgumentException e) {
            initError(e.getMessage());
            return false;
        }

        endTime = loc.end;
        titleView.setText(Globals.curProg.Title);

        if (livetv) {
            lastFilename = loc.filename;
            setBackground(-1, Globals.curProg, findViewById(R.id.tv_remote));
            return true;
        }

        if (mddMgr != null)
            setupProgressBar(1000, lastProgress, Globals.curProg, loc.fps);
        else
            setupProgressBar(loc.end, loc.position, null, loc.fps);

        setBackground(videoId, Globals.curProg, findViewById(R.id.tv_remote));

        return true;

    }

    private void setupProgressBar(int max, int progress, Program prog, float fps) {

        pBar.setMax(max);
        pBar.setProgress(progress);

        pBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                if (!fromUser || seekBar.getMax() <= 0)
                    return;
                if (mddMgr != null) {
                    FrontendLocation loc = null;
                    try {
                        synchronized (feLock) {
                            loc = feMgr.getLoc();
                        }
                    } catch (IOException e) {
                        ErrUtil.err(ctx, e);
                        return;
                    } catch (IllegalArgumentException e) {
                        return;
                    }
                    if (loc.end <= 0)
                        return;
                    progress = (loc.end * progress) / 1000;
                }
                try {
                    synchronized (feLock) {
                        feMgr.seekTo(progress);
                    }
                } catch (IOException e) {
                    ErrUtil.err(ctx, e);
                }
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
            }

        });

        if (prog == null || endTime <= 0 || fps == 0)
            return;

        ArrayList<Commercial> cuts = prog.getCutList(feMgr.addr);
        if (cuts.isEmpty())
            return;

        Drawable[] layers = new Drawable[2];
        layers[0] = pBar.getProgressDrawable();
        Rect bounds = layers[0].getBounds();
        layers[1] = new CutListDrawable(cuts, fps, endTime, bounds);
        LayerDrawable lg = new LayerDrawable(layers);
        lg.setBounds(bounds);
        pBar.setProgressDrawable(lg);

    }

    private void setBackground(final int id, final Program prog, final View v) {

        final Drawable metal = getResources().getDrawable(R.drawable.metal);

        if (!Globals.haveServices()) {
            v.setBackgroundDrawable(metal);
            return;
        }

        final DisplayMetrics dm = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(dm);
        final int height = (int) (dm.heightPixels / 1.5);
        final int width = (int) (dm.widthPixels / 1.5);
        final ArtworkType type = (width > height) ? ArtworkType.fanart : ArtworkType.coverart;

        Globals.runOnThreadPool(new Runnable() {
            @Override
            public void run() {
                Bitmap bm = null;
                if (videoId != -1)
                    bm = Video.getArtwork(id, type, width, 0, null);
                else if (prog != null)
                    bm = prog.getArtwork(type, width, 0);
                final Drawable d = (bm == null) ? metal : new BitmapDrawable(getResources(), bm);
                if (bm != null)
                    d.setAlpha(65);
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        if (v == null)
                            return;
                        v.setBackgroundDrawable(d);
                    }
                });
            }
        });

    }

    private void initError(String e) {

        if (e != null)
            ErrUtil.postErr(ctx, e);

        if (seekTo != 0) {
            moveHelper.abortMove();
            return;
        }

        seekTo = 0;
        done();

    }

    /** Call finish() but jump to lastLocation first, if possible */
    private synchronized void done() {
        synchronized (feLock) {
            if (feMgr != null && feMgr.isConnected() && jump) {
                LogUtil.debug("TVRemote is finishing, jumping back to " + //$NON-NLS-1$
                        Globals.getLastLocation().location);
                try {
                    feMgr.jumpTo(Globals.getLastLocation());
                } catch (IOException e) {
                    ErrUtil.postErr(this, e);
                } catch (IllegalArgumentException e) {
                    ErrUtil.postErr(this, e);
                }
            }
        }
        jump = false;
        if (!isFinishing())
            finish();
    }

    @Override
    protected void onScrollDown() {
        synchronized (feLock) {
            if (feMgr == null)
                return;
            try {
                feMgr.sendKey(Key.VOL_DOWN);
                feMgr.sendKey(Key.VOL_DOWN);
            } catch (IOException e) {
                ErrUtil.err(this, e);
            }
        }
        onAction();
    }

    @Override
    protected void onScrollLeft() {
        synchronized (feLock) {
            if (feMgr == null)
                return;
            try {
                feMgr.sendKey(Key.SEEK_BACK);
            } catch (IOException e) {
                ErrUtil.err(this, e);
            }
        }
        onAction();
    }

    @Override
    protected void onScrollRight() {
        synchronized (feLock) {
            if (feMgr == null)
                return;
            try {
                feMgr.sendKey(Key.SEEK_FORWARD);
            } catch (IOException e) {
                ErrUtil.err(this, e);
            }
        }
        onAction();
    }

    @Override
    protected void onScrollUp() {
        synchronized (feLock) {
            if (feMgr == null)
                return;
            try {
                feMgr.sendKey(Key.VOL_UP);
                feMgr.sendKey(Key.VOL_UP);
            } catch (IOException e) {
                ErrUtil.err(this, e);
            }
        }
        onAction();
    }

    @Override
    protected void onTap() {
        synchronized (feLock) {
            if (feMgr == null)
                return;
            try {
                feMgr.sendKey(Key.PAUSE);
            } catch (IOException e) {
                ErrUtil.err(this, e);
            }
        }
        onAction();
    }

}