V49 Classes
V49 Classes for the Java SDK
Use these classes in place of the same-named SDK and Bananas classes to work with V49 TiVo features. (originally presented on forums)
V49IHmeProtocol.java - just the additional constants for the keyboard and video status, etc.
V49BApplicationFactory.java - report your app as V49 so you can use new features, fix spaces in AppTitle for bonjour.
V49BApplication.java - helpers for several V49 features, and fixes for some problems (some are pre 1.4.1e problems that don't need fixing if you use the newer SDK, so I made them forward compatible fixes).
V49IHmeProtocol.java
package com.blackledge.david.tivo; import com.tivo.hme.interfaces.IHmeConstants; import com.tivo.hme.sdk.IHmeProtocol; /** * IHmeProtocol extension that supports some features beyond SDK 1.4.1 * especially HTTP Header strings and Slide Remote key codes. Typically just use * {@link #KEY_OPT_ASCII_START}, {@link #KEY_OPT_ASCII_END}, and * {@link #KEY_OPT_ASCII_OFFSET} to get all the characters. * */ public interface V49IHmeProtocol extends IHmeProtocol { /** * 0.49 - Slide Remote key events supported (no "supporting" sdk version) * (0.41 ({@link IHmeProtocol#VERSION_0_41}) supported by sdk 1.4, 0.43-0.45 * ({@link IHmeProtocol#VERSION_0_43}-{@link IHmeProtocol#VERSION_0_45}) by * sdk 1.4.1e) */ public static final int VERSION_0_49 = (0 << 8) | 49; /** * according to an error logged: * "rendered text exceeded max allowed dimensions of 1920x1080" */ public static final int LIMIT_TEXT_RENDER_WIDTH = 1920; /** * according to an error logged: * "rendered text exceeded max allowed dimensions of 1920x1080" */ public static final int LIMIT_TEXT_RENDER_HEIGHT = 1080; /** Media status code, resource at end of stream. */ public static final int RSRC_STATUS_END = 11; /** Media status code, resource at end of available buffer. */ public static final int RSRC_STATUS_BUFFER_OVERFLOW = 12; /** * Zoom key, optional, relabel of {@link #KEY_OPT_ASPECT} key, which is same * code as {@link #KEY_OPT_WINDOW} and {@link #KEY_OPT_PIP} (22). */ public static final int KEY_OPT_ZOOM = KEY_OPT_WINDOW; // undefined keys in SDK: 0-21, 23, 25, 28-50 /** * On Demand key, reserved for internal use. E.g. long blue ON DEMAND button * on Charter remote. This is sent in a key press event just before * attempting to "teleport" to the service. In anything below Series 4, no * teleport and not transmitted over HME, apparently. */ public static final int KEY_OPT_ON_DEMAND = 30; // last SDK key code defined: KEY_OPT_DVD = 55; /** * "A" yellow action remote button, optional (first on Premiere), may only transmit on if identified as V49. */ public static final int KEY_OPT_ACTION_A = 56; /** * "B" blue action remote button, optional (first on Premiere), may only transmit on if identified as V49. */ public static final int KEY_OPT_ACTION_B = 57; /** * "C" red action remote button, optional (first on Premiere), may only transmit on if identified as V49. */ public static final int KEY_OPT_ACTION_C = 58; /** * "D" green action remote button, optional (first on Premiere), may only transmit on if identified as V49. */ public static final int KEY_OPT_ACTION_D = 59; /** * Backspace (picture of a left arrow on slide remote) button code.<BR> * Name matches known network interface KEYBOARD command. */ public static final int KEY_OPT_BACKSPACE = 65; /** * (TBD (backspace?) requires a higher version than 49) Back key, optional (first on Roamio remote). Other "Back" keys in most apps: {@link #KEY_OPT_ACTION_B}, {@link #KEY_OPT_ZOOM}, and sometimes KEY_REPLAY. */ public static final int KEY_OPT_BACK = KEY_OPT_BACKSPACE; // codes 60-64 and 66-69 are unknown, currently /** * Code returned by the RETURN key on the keyboard if it is modified by Alt * (or Windows or Ctrl or Shift (or possibly "sym"(bol))). Normally that key * sends KEY_SELECT. */ public static final int KEY_OPT_META_SELECT = 70; /** * Left Shift button code. You do not need to process this to handle * capitalization, use KEY_OPT_ASCII_* to get the properly capitalized * character. * Name matches known network interface KEYBOARD command */ public static final int KEY_OPT_LSHIFT = 71; /** * Right Shift button code. You do not need to process this to handle * capitalization, use KEY_OPT_ASCII_* to get the properly capitalized * character. * Name matches known network interface KEYBOARD command */ public static final int KEY_OPT_RSHIFT = 72; /** * Left Alt (and Windows) key button code for keyboards - POSSIBLY the * "sym"(bol) key for the Slide Remote (although there is only one, on the * right side). You do not need to process this to handle symbols, use * KEY_OPT_ASCII_* to get the properly capitalized character. * Name matches known network interface KEYBOARD command */ public static final int KEY_OPT_LMETA = 73; /** * Right Alt (and Windows) key button code for keyboards - POSSIBLY the * "sym"(bol) key for the Slide Remote. You do not need to process this to * handle symbols, use KEY_OPT_ASCII_* to get the properly capitalized * character. * Name matches known network interface KEYBOARD command */ public static final int KEY_OPT_RMETA = 74; /** * Capslock button code. You do not need to process this to handle * capitalization, use KEY_OPT_ASCII_* to get the properly capitalized * character. * Name matches known network interface KEYBOARD command */ public static final int KEY_OPT_CAPS = 75; /** known from network interface KEYBOARD command, just sends non-KBD code on a USB keyboard */ public static final int KEY_OPT_KBDUP = KEY_UP; /** known from network interface KEYBOARD command, just sends non-KBD code on a USB keyboard */ public static final int KEY_OPT_KBDDOWN = KEY_DOWN; /** known from network interface KEYBOARD command, just sends non-KBD code on a USB keyboard */ public static final int KEY_OPT_KBDLEFT = KEY_LEFT; /** known from network interface KEYBOARD command, just sends non-KBD code on a USB keyboard */ public static final int KEY_OPT_KBDRIGHT = KEY_RIGHT; /** known from network interface KEYBOARD command, just sends SELECT on a USB keyboard */ public static final int KEY_OPT_KBDENTER = KEY_SELECT; /** known from network interface KEYBOARD command, just sends CLEAR on a USB keyboard */ public static final int KEY_OPT_ESCAPE = KEY_CLEAR; /** NO keycode sent in V49 HME, known from network interface KEYBOARD command */ public static final int KEY_OPT_LCONTROL = KEY_UNKNOWN; /** NO keycode sent in V49 HME, known from network interface KEYBOARD command */ public static final int KEY_OPT_RCONTROL = KEY_UNKNOWN; /** NO keycode sent in V49 HME, known from network interface KEYBOARD command */ public static final int KEY_OPT_PAGEUP = KEY_UNKNOWN; /** NO keycode sent in V49 HME, known from network interface KEYBOARD command */ public static final int KEY_OPT_PAGEDOWN = KEY_UNKNOWN; /** NO keycode sent in V49 HME, known from network interface KEYBOARD command */ public static final int KEY_OPT_HOME = KEY_UNKNOWN; /** NO keycode sent in V49 HME, known from network interface KEYBOARD command */ public static final int KEY_OPT_INSERT = KEY_UNKNOWN; /** NO keycode sent in V49 HME, known from network interface KEYBOARD command */ public static final int KEY_OPT_DELETE = KEY_UNKNOWN; /** UNKNOWN keycode (same as ADVANCE?) - may not be sent through HME, known from network interface KEYBOARD command */ public static final int KEY_OPT_DELIMITER = KEY_ADVANCE; /** UNKNOWN keycode - may not be sent through HME, known from network interface KEYBOARD command */ public static final int KEY_OPT_CC_ON = KEY_UNKNOWN; /** UNKNOWN keycode - may not be sent through HME, known from network interface KEYBOARD command */ public static final int KEY_OPT_CC_OFF = KEY_UNKNOWN; /** UNKNOWN keycode - may not be sent through HME, known from network interface KEYBOARD command */ public static final int KEY_OPT_ASPECT_CORRECTION_FULL = KEY_UNKNOWN; /** UNKNOWN keycode - may not be sent through HME, known from network interface KEYBOARD command */ public static final int KEY_OPT_ASPECT_CORRECTION_PANEL = KEY_UNKNOWN; /** UNKNOWN keycode - may not be sent through HME, known from network interface KEYBOARD command */ public static final int KEY_OPT_ASPECT_CORRECTION_ZOOM = KEY_UNKNOWN; /** UNKNOWN keycode - may not be sent through HME, known from network interface KEYBOARD command */ public static final int KEY_OPT_ASPECT_CORRECTION_WIDE_ZOOM = KEY_UNKNOWN; /** UNKNOWN keycode - may not be sent through HME, known from network interface KEYBOARD command */ public static final int KEY_OPT_VIDEO_MODE_FIXED_480i = KEY_UNKNOWN; /** (teleport key) UNKNOWN keycode - may not be sent through HME, known from network interface KEYBOARD command */ public static final int KEY_OPT_STANDBY = KEY_UNKNOWN; /** (teleport key) reported to match TIVO key... unless it matches {@link #KEY_OPT_ON_DEMAND} - may not be sent through HME, known from network interface KEYBOARD command */ public static final int KEY_OPT_DIRECTV = KEY_TIVO; /** (teleport key) UNKNOWN keycode - may not be sent through HME, known from network interface KEYBOARD command */ public static final int KEY_OPT_NOWSHOWING = KEY_UNKNOWN; /** (teleport key) UNKNOWN keycode (same as NOWSHOWING?) - may not be sent through HME, known from network interface KEYBOARD command */ public static final int KEY_OPT_LIST = KEY_OPT_NOWSHOWING; /** * For all characters between KEY_OPT_ASCII_START and KEY_OPT_ASCII_END * (inclusive) subtract this value from the key code to get the ASCII * character they represent. */ public static final int KEY_OPT_ASCII_OFFSET = 0x10000; // 65536, 2^16 /** name matches known network interface KEYBOARD command */ public static final int KEY_OPT_ASCII_SPACE = KEY_OPT_ASCII_OFFSET+' '; // Sym Q public static final int KEY_OPT_ASCII_EXCLAMATION = KEY_OPT_ASCII_OFFSET+'!'; // Sym L /** name matches known network interface KEYBOARD command */ public static final int KEY_OPT_ASCII_DOUBLEQUOTE = KEY_OPT_ASCII_OFFSET+'\"'; // Sym E public static final int KEY_OPT_ASCII_HASH = KEY_OPT_ASCII_OFFSET+'#'; // Sym R public static final int KEY_OPT_ASCII_DOLLAR = KEY_OPT_ASCII_OFFSET+'$'; // Sym T public static final int KEY_OPT_ASCII_PERCENT = KEY_OPT_ASCII_OFFSET+'%'; // Sym U public static final int KEY_OPT_ASCII_AMPERSAND = KEY_OPT_ASCII_OFFSET+'&'; // Sym K /** (Apostrophe) name matches known network interface KEYBOARD command */ public static final int KEY_OPT_ASCII_QUOTE = KEY_OPT_ASCII_OFFSET+'\''; // Sym O public static final int KEY_OPT_ASCII_LPAREN = KEY_OPT_ASCII_OFFSET+'('; // Sym P public static final int KEY_OPT_ASCII_RPAREN = KEY_OPT_ASCII_OFFSET+')'; // Sym I public static final int KEY_OPT_ASCII_ASTERISK = KEY_OPT_ASCII_OFFSET+'*'; // Sym 1 public static final int KEY_OPT_ASCII_PLUS = KEY_OPT_ASCII_OFFSET+'+'; // Sym B /** name matches known network interface KEYBOARD command */ public static final int KEY_OPT_ASCII_COMMA = KEY_OPT_ASCII_OFFSET+','; // Sym 2 /** name matches known network interface KEYBOARD command */ public static final int KEY_OPT_ASCII_MINUS = KEY_OPT_ASCII_OFFSET+'-'; // Sym N /** name matches known network interface KEYBOARD command */ public static final int KEY_OPT_ASCII_PERIOD = KEY_OPT_ASCII_OFFSET+'.'; // Sym 5 /** name matches known network interface KEYBOARD command */ public static final int KEY_OPT_ASCII_SLASH = KEY_OPT_ASCII_OFFSET+'/'; // 0-9 use KEY_NUM0-KEY_NUM9 // Sym J public static final int KEY_OPT_ASCII_COLON = KEY_OPT_ASCII_OFFSET+':'; // Sym H /** name matches known network interface KEYBOARD command */ public static final int KEY_OPT_ASCII_SEMICOLON = KEY_OPT_ASCII_OFFSET+';'; // Sym Z public static final int KEY_OPT_ASCII_LESS = KEY_OPT_ASCII_OFFSET+'<'; // Sym 3 /** name matches known network interface KEYBOARD command */ public static final int KEY_OPT_ASCII_EQUALS = KEY_OPT_ASCII_OFFSET+'='; // Sym X public static final int KEY_OPT_ASCII_GREATER = KEY_OPT_ASCII_OFFSET+'>'; // Sym M public static final int KEY_OPT_ASCII_QUESTION = KEY_OPT_ASCII_OFFSET+'?'; // Sym W public static final int KEY_OPT_ASCII_AT = KEY_OPT_ASCII_OFFSET+'@'; public static final int KEY_OPT_ASCII_A_UPPER = KEY_OPT_ASCII_OFFSET+'A'; public static final int KEY_OPT_ASCII_Z_UPPER = KEY_OPT_ASCII_OFFSET+'Z'; // Sym F /** name matches known network interface KEYBOARD command */ public static final int KEY_OPT_ASCII_LBRACKET = KEY_OPT_ASCII_OFFSET+'['; // Sym V /** name matches known network interface KEYBOARD command */ public static final int KEY_OPT_ASCII_BACKSLASH = KEY_OPT_ASCII_OFFSET+'\\'; // Sym G /** name matches known network interface KEYBOARD command */ public static final int KEY_OPT_ASCII_RBRACKET = KEY_OPT_ASCII_OFFSET+']'; // Sym Y public static final int KEY_OPT_ASCII_CIRCUMFLEX = KEY_OPT_ASCII_OFFSET+'^'; // Sym 7 public static final int KEY_OPT_ASCII_UNDERSCORE = KEY_OPT_ASCII_OFFSET+'_'; // Sym 9 /** (Grave accent mark) name matches known network interface KEYBOARD command */ public static final int KEY_OPT_ASCII_BACKQUOTE = KEY_OPT_ASCII_OFFSET+'`'; public static final int KEY_OPT_ASCII_A_LOWER = KEY_OPT_ASCII_OFFSET+'a'; public static final int KEY_OPT_ASCII_Z_LOWER = KEY_OPT_ASCII_OFFSET+'z'; // Sym S public static final int KEY_OPT_ASCII_LBRACE = KEY_OPT_ASCII_OFFSET+'{'; // Sym C public static final int KEY_OPT_ASCII_BAR = KEY_OPT_ASCII_OFFSET+'|'; // Sym D public static final int KEY_OPT_ASCII_RBRACE = KEY_OPT_ASCII_OFFSET+'}'; // Sym A public static final int KEY_OPT_ASCII_TILDE = KEY_OPT_ASCII_OFFSET+'~'; /** * space character - beginning of range of values that are ASCII characters * plus KEY_OPT_ASCII_OFFSET. Note that 0-9 do not show up this way, * instead sending the KEY_NUM* codes. */ public static final int KEY_OPT_ASCII_START = KEY_OPT_ASCII_SPACE; /** * tilde character - end of range of values that are ASCII characters plus * KEY_OPT_ASCII_OFFSET. Note that 0-9 do not show up this way, * instead sending the KEY_NUM* codes. */ public static final int KEY_OPT_ASCII_END = KEY_OPT_ASCII_TILDE; // as discovered by davidblackledge - Premiere only. public static final int ID_FONT3_TTF = 13; public static final int ID_FONT3ITALIC_TTF = 14; public static final int ID_FONT3BOLD_TTF = 15; // as discovered by wmcbrine - Premiere only public static final int ID_SPEEDUP4_SOUND = 37; /** * HTTP Header sent in resource requests by the TiVo with the tsn (context * connection attribute "tsn") as value. */ public static final String HTTP_HEADER_TIVO_ID = "TiVo_TCD_ID"; /** * HTTP Header sent in resource requests by the TiVo with the TiVo Software * Version (Device Info event's "version") as value. */ public static final String HTTP_HEADER_TIVO_VERSION = "TiVo_SW_VER"; public static final String HTTP_HEADER_TIVO_DURATION = IHmeConstants.TIVO_DURATION; }
V49BApplicationFactory.java
package com.blackledge.david.tivo; import java.io.IOException; import com.tivo.core.ds.ThreadUtils; import com.tivo.hme.interfaces.IApplication; import com.tivo.hme.interfaces.IContext; import com.tivo.hme.interfaces.ILogger; import com.tivo.hme.sdk.Factory; import com.tivo.hme.sdk.IHmeProtocol; import com.tivo.hme.sdk.io.HmeInputStream; import com.tivo.hme.sdk.io.HmeOutputStream; /** * Factory that creates the {@link V49BApplication} to report its version to the * TiVo as VERSION_0_49 and fixes HD UI problem with spaces in bonjour announced * App titles. (V49BApplication required for createApplication duplicate to have * correct method access). */ public class V49BApplicationFactory extends Factory { /** * Override to report a different HME version by calling * {@link #supercreateApplication(IContext, int)}. */ @Override public IApplication createApplication(IContext context) throws IOException { int sentVersion = // IHmeProtocol.VERSION; V49IHmeProtocol.VERSION_0_49; // IHmeProtocol.VERSION_0_40; IApplication application = supercreateApplication(context, sentVersion); return application; } /** * Copy of super factory implementation with parameterized version number to * send to TiVo. * Also fixes Factory's monitor getting changed to this instantiating application instance. */ public IApplication supercreateApplication(IContext context, int sentVersion) throws IOException { int version = -1; HmeOutputStream out = null; HmeInputStream in = null; synchronized (lock) { // lock this factory if (context.getOutputStream() instanceof HmeOutputStream) { out = (HmeOutputStream)context.getOutputStream(); } else { out = new HmeOutputStream(context.getOutputStream()); } if (context.getInputStream() instanceof HmeInputStream) { in = (HmeInputStream)context.getInputStream(); } else { in = new HmeInputStream(context.getInputStream()); } // HME protocol starts here // Synchronize and flush the writes, so that no other writer // interleaves within our data synchronized (out.getMonitor()) { out.writeInt(IHmeProtocol.MAGIC); out.writeInt( // IHmeProtocol.VERSION sentVersion ); out.flush(); } // read magic and version int magic = in.readInt(); if (magic != IHmeProtocol.MAGIC) { throw new IOException( "bad magic: 0x" + Integer.toHexString(magic) // +getIdForException()); ); } version = in.readInt(); if (version >> 8 < // IHmeProtocol.VERSION sentVersion >> 8 ) { throw new IOException( "version mismatch: " + ( version >> 8 ) + "." + ( version & 0xff ) + " < " + ( // IHmeProtocol.VERSION sentVersion >> 8 ) + "." + ( // IHmeProtocol.VERSION sentVersion & 0xff ) // + getIdForException()); ); } } // synchronized // maintain proper order of locks -- we have released // the factory monitor before entering the app monitor IApplication retApp = null; try { // Application app = (Application)clazz.newInstance(); // have to have local class to access setContext V49BApplication app = (V49BApplication)clazz.newInstance(); synchronized (app) { app.setFactory(this); app.setContext(context, version); app.setMonitor(app); //david.blackledge.com: undo thread set monitor for factory as app! stupid app.setMonitor! ThreadUtils.obj.setMonitorFor(this, this); retApp = app; } } catch (InstantiationException ex) { log(ILogger.LOG_NOTICE, ex); } catch (IllegalAccessException ex) { log(ILogger.LOG_NOTICE, ex); } out.flush(); return retApp; } // createApplication() /** * Ensures any spaces (ASCII 32, 0x20) are replaced with non-breaking spaces (0xA0 or ASCII 160) */ protected String fixTitle(String origTitle) { return origTitle.replace((char)0x20, (char)0xA0); } /** * {@inheritDoc} * Ensures any spaces (ASCII 32, 0x20) are replaced with non-breaking spaces (0xA0 or ASCII 160) */ @Override public void setAppTitle(String title) { super.setAppTitle(fixTitle(title)); } /** * {@inheritDoc} * Ensures any spaces (ASCII 32, 0x20) are replaced with non-breaking spaces (0xA0 or ASCII 160) */ @Override public String getAppTitle() { return fixTitle(super.getAppTitle()); } }
V49BApplication.java
package com.blackledge.david.tivo; import java.awt.Color; import java.awt.Rectangle; import java.io.UnsupportedEncodingException; import java.lang.ref.WeakReference; import java.net.URLDecoder; import java.util.Map; import java.util.LinkedHashMap; import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.tivo.hme.bananas.BApplication; import com.tivo.hme.interfaces.IContext; import com.tivo.hme.sdk.Application; import com.tivo.hme.sdk.HmeEvent; import com.tivo.hme.sdk.HmeEvent.DeviceInfo; import com.tivo.hme.sdk.IHmeProtocol; import com.tivo.hme.sdk.Resource; import com.tivo.hme.sdk.Version; /** * BApplication that fixes SDK bugs, supports some features beyond SDK 1.4.1 and * makes some things easier. See {@link V49BApplicationFactory} and * {@link V49IHmeProtocol} for other features. * <UL> * <LI>Use {@link V49BApplicationFactory} as superclass for your factory to tell the TiVo you're using V49 (or another version). * <LI>Constants for SDK resource strings *.ttf, *.snd * <LI>Constants for discovered Stream resources "livetv:" "loopset:" and * "recording:" * <LI>Getters for spacing when a "loopset:" is the background * <LI>Member variables for protocol version, tivo version, and tivo ID. * <LI>Resolution help: automatically set preferred resolution, template methods * for init and showing the first BScreen during resolution setting * <LI>Template method for cleaning up during {@link #destroy()} to prevent an * SDK 1.4 bug (not an issue in 1.4.1e) * <LI>Post Resource errors to the resource and with new version of * {@link #handleApplicationError(int, String, Integer)} * <LI>Fix SDK bug that broke Application-as-stream feature. * <LI>Support more TTF built-in fonts with new constant names for series-4-only * fonts, and with id numbers-as-font-names for arbitrary attempts. * <LI>SDK capability/version methods. * <LI>Backward compatibility of new SDK methods to old SDK. You should be able * to run this app in either SDK. * </UL> * * @author David.Blackledge.com * */ public abstract class V49BApplication extends BApplication implements V49IHmeProtocol { public static final String TTF_SERIES4FONT3BOLD = "series4font3bold.ttf"; //$NON-NLS-1$ public static final String TTF_SERIES4FONTITALIC = "series4fontitalic.ttf"; //$NON-NLS-1$ public static final String TTF_SERIES4FONT = "series4font.ttf"; //$NON-NLS-1$ public static final String TTF_DEFAULT = "default.ttf"; //$NON-NLS-1$ public static final String TTF_SYSTEM = "system.ttf"; //$NON-NLS-1$ public static final String SOUND_BONK = "bonk.snd"; //$NON-NLS-1$ public static final String SOUND_UPDOWN = "updown.snd"; //$NON-NLS-1$ public static final String SOUND_THUMBSUP = "thumbsup.snd"; //$NON-NLS-1$ public static final String SOUND_THUMBSDOWN = "thumbsdown.snd"; //$NON-NLS-1$ public static final String SOUND_SELECT = "select.snd"; //$NON-NLS-1$ public static final String SOUND_TIVO = "tivo.snd"; //$NON-NLS-1$ public static final String SOUND_LEFT = "left.snd"; //$NON-NLS-1$ public static final String SOUND_RIGHT = "right.snd"; //$NON-NLS-1$ public static final String SOUND_PAGEUP = "pageup.snd"; //$NON-NLS-1$ public static final String SOUND_PAGEDOWN = "pagedown.snd"; //$NON-NLS-1$ /** Note, this sounds identical to {@link #SOUND_THUMBSUP} */ public static final String SOUND_ALERT = "alert.snd"; //$NON-NLS-1$ public static final String SOUND_DESELECT = "deselect.snd"; //$NON-NLS-1$ public static final String SOUND_ERROR = "error.snd"; //$NON-NLS-1$ public static final String SOUND_SLOWDOWN1 = "slowdown1.snd"; //$NON-NLS-1$ public static final String SOUND_SPEEDUP1 = "speedup1.snd"; //$NON-NLS-1$ public static final String SOUND_SPEEDUP2 = "speedup2.snd"; //$NON-NLS-1$ public static final String SOUND_SPEEDUP3 = "speedup3.snd"; //$NON-NLS-1$ // not yet supported by SDK or these V49 classes // public static final String SOUND_SERIES4SPEEDUP4 = "speedup4.snd"; //$NON-NLS-1$ /** * TiVo Built-in video stream resource which displays the "current" live tv * tuner, defaulting to the end of the 30 minute buffer. */ public static final String STREAM_LIVETV = "livetv:"; //$NON-NLS-1$ /** * TiVo Built-in background resource used on "TiVo Central" page. Red * animated video in Series 3 and earlier, Blue image in Series and later. */ public static final String STREAM_LOOP_RED_CENTRAL = "loopset:Central"; //$NON-NLS-1$ /** * TiVo Built-in background resource used on "Showcases" and * "TiVo Suggestions" pages. Purple animated video in Series 3 and earlier, * Blue image in Series and later. */ public static final String STREAM_LOOP_PURPLE_SHOWCASES = "loopset:Showcases"; //$NON-NLS-1$ /** * TiVo Built-in background resource used on "Setup" and "Apps/Photos/Music" * pages. Blue animated video in Series 3 and earlier, Blue image in Series * and later. */ public static final String STREAM_LOOP_BLUE_SETUP = "loopset:Setup"; //$NON-NLS-1$ /** * TiVo Built-in background resource used on "Now Playing" and all pages * that don't use the other LOOP resources. Green animated video in Series 3 * and earlier, Blue image in Series and later. */ public static final String STREAM_LOOP_GREEN_NOWPLAYING = "loopset:NowPlaying"; //$NON-NLS-1$ public static final String STREAM_RECORDING_PREFIX = "recording:"; //$NON-NLS-1$ /** * Protocol in use between the TiVo and the server. Used in methods like * {@link #isSlideRemoteSupported()}. Accurate/dynamic version of * {@link IHmeProtocol#VERSION}. Saved during * {@link #setContext(IContext, int)} call. Use V49BapplicationFactory as * your factory's supertype to register V49 as your preferred protocol * version. */ public int protocolVersion; /** * Device Info event's "version" - the TiVo's Software Version. E.g. * 11.0k-01-2-652 . Available after DEVICE_INFO_EVENT comes in (equivalent * to information in {@link V49IHmeProtocol#HTTP_HEADER_TIVO_VERSION}). */ public String tivoVersion; /** * The TiVo Service Number (tsn) uniquely identifying the connected TiVo DVR * device. Available after {@link #setContext(IContext, int)} is called * (extracted from {@link V49IHmeProtocol#HTTP_HEADER_TIVO_ID}). */ public String tivoID; /** * Map from the DeviceInfo event. * Examples: * <LI>host=Bedroom (user-defined or system default with last 4 of TSN) * <LI>brand=TiVo (or e.g. Pace) * <LI>platform=Gen06 (Gen07,Gen08,Gen10,Pac01) * <LI>version=11.0k-01-2-652 * <LI>zoneinfo=America/Chicago * <LI>country=US (or e.g. CA) * <LI>language=enUS * <LI>active-ui-mode=classic (or flash) * <LI>com.tivo.examine.idType.stationId=tivo * <LI>com.tivo.examine.showing=true * <LI>com.tivo.examine.idType.contentId=tivo * <LI>credential-manager-id-ccid=(Hex number up to 8 digits) * <LI>credential-manager-id-mac=(12dig hex number) * <LI>has-recording-capability=true * <LI>has-recording-storage=true (false seen) * <LI>has-season-pass-capability=true * <LI>vod-session-capable=false */ public Map deviceInfo; /** * TimeZone based on DeviceInfo's "zoneinfo" id, or null (if not found, or * if result is not-found default id of "GMT") - (you could use * TimeZone.getDefault()). Null until DeviceInfo event arrives. */ public TimeZone deviceZone; /** * Does nothing. Override this to output debug messages * @param message */ public void debug(String message) { } /** * Template method to do {@link #init(IContext)} tasks between super's init * and preferred resolution setting. Better to override this than init * unless you want to handle setting resolution yourself. */ protected void initBeforeResolutionSet(IContext context) { } /** * Template method to do {@link #rootBoundsChanged(Rectangle)} tasks between * super's rootBoundsChanged and a possible call to {@link #showFirstPage()} * if this is the first root bounds change during application * initialization. Override this, or it might be OK to override * rootBoundsChanged and just call its super method and ignore this. * * @param rootBounds */ protected void rootBoundsChangedBeforeFirstPage(Rectangle rootBounds) { } /** * Override this to push the first BScreen of the application after * resolution issues are resolved. */ protected abstract void showFirstPage(); /** * Override this instead of {@link #destroy()} for SDK 1.4 to do safe cleanup during the * destroy operation. Prevents the loop that occurs if a fatal error happens * during destroy. Not an issue in 1.4.1e */ protected void destroyCleanup() { } /* BEGIN fix JDK */ // TODO future: handle unsupported chunks (e.g. Event 10) // public boolean handleChunk(InputStream in) // { // synchronized (lock) { // //debug("handleChunk()"); // boolean doMore = true; // // // flush any data that was generated from previous handling of // // event // flush(); // // ChunkedInputStream chunkInStr = null; // if (in instanceof ChunkedInputStream) // { // chunkInStr = (ChunkedInputStream)in; // } // else // { // chunkInStr = new ChunkedInputStream(in); //, // IHmeConstants.TCP_BUFFER_SIZE); // } // // // if ( protocolVersion < VERSION_0_40 ) // // { // // chunkInStr.setUseVString( false ); // // } // // else { // chunkInStr.setUseVString( true ); // // } // // int opcode = -1; // try { // opcode = (int)chunkInStr.readVInt(); // } catch (IOException e) { // // receiver closed - ignore // // if (debug()) { // // log(ILogger.LOG_DEBUG, // // "Connection terminated by receiver"); // // } // } // if (opcode == -1) { // doMore = false; // return doMore; // } // // HmeEvent evt = null; // String eventString = null; // try // { // switch (opcode) { // case EVT_DEVICE_INFO: // evt = new HmeEvent.DeviceInfo(chunkInStr); // break; // case EVT_APP_INFO: // evt = new HmeEvent.ApplicationInfo(chunkInStr); // break; // case EVT_RSRC_INFO: // evt = new HmeEvent.ResourceInfo(chunkInStr, this); // break; // case EVT_RESOLUTION_INFO: // evt = new HmeEvent.ResolutionInfo(chunkInStr); // break; // case EVT_KEY: // evt = new HmeEvent.Key(chunkInStr); // break; // case EVT_IDLE: // evt = new HmeEvent.Idle(chunkInStr); // break; // case EVT_FONT_INFO: // evt = new HmeEvent.FontInfo(chunkInStr); // break; // case EVT_INIT_INFO: // evt = new HmeEvent.InitInfo(chunkInStr); // break; // default: // eventString = // "HmeEvent[opcode=" + opcode // + ", id = " + chunkInStr.readVInt() // + "]"; // // short clen = chunkInStr.readShort(); // // while (clen != 0) { // // System.out.println(clen); // // chunkInStr.skip(clen); // // clen = chunkInStr.readShort(); // // } // // // System.out.println(eventString); // // try { // // System.out.println(chunkInStr.readInt()); // // System.out.println(chunkInStr.readDict()); // // } catch(IOException e1) { // // chunkInStr.readTerminator(); // // throw e1; // // } // break; // } // if (evt != null) { // evt.setMonitor(lock); // } // chunkInStr.readTerminator(); // } // catch (IOException e) { // evt = null; // getLogger().log(ILogger.LOG_INFO, e); // } // // // // // Save the event, in case we need to dump the list // // // // if (eventsReceived != null) { // // if (eventsReceived.size() >= MAX_RECEIVED_EVENTS_SIZE) { // // // Startup is complete -- stop checking for protocol errs // // startupComplete = true; // // eventsReceived.clear(); // // eventsReceived = null; // // } else { // // if (evt != null) { // // eventString = evt.toString(); // // } // // if (eventString == null) { // // eventString = "Bad HmeEvent[opcode="+opcode+", no ID]"; // // } // // eventsReceived.add(eventString); // // } // // } // // if (evt == null) { // getLogger().log(ILogger.LOG_DEBUG, // "unknown event opcode : " + opcode); // } // else // { // // if any debugging is turned on, verify the event is kosher // // if (debug()) { // // evt.verify(logger); // // } // // // debugEvents means to print each and every event received // // if (debugEvents()) { // getLogger().log(ILogger.LOG_DEBUG, "event " + evt); // // } // // // if (!preprocessEvent(evt)) { // // // // // preprocessEvent() says this event should be discarded // // if (eventsReceived != null) { // // int i = eventsReceived.size() - 1; // // eventsReceived.set(i, // // eventsReceived.get(i).toString() + " [IGNORED]"); // // } // // // } else { // // // The app should receive this event. // // // // Only dispatch events after app.init() is called. // // Save events before init(), to be dispatched later. // // // // if (appInitialized) { // // dispatchEvent(evt); // // } else if (queuedEvents != null) { // // // Save the events to send after initialization // // queuedEvents.add(evt); // // } // // } // } // // // flush any data that was generated from handling of event // flush(); // // return doMore; // } // sync // // } // handleChunk() /** * unfortunately sdk's Resource.ResourceStatus isn't public, so we recreate * it here - all it does is use a different Event ID */ protected static class MockResourceStatus extends HmeEvent.ResourceInfo { MockResourceStatus(int id, Resource rsrc, int status, Map map) { super(EVT_RSRC_STATUS, id, rsrc, status, map); } public String toString() { return getID() + ".RSRC_STATUS(" + statusToString(getStatus()) //$NON-NLS-1$ + ")"; //$NON-NLS-1$ } } private boolean destroyedAlready = false; public void destroy() { // protect against SDK 1.4 bug: if there is a fatal error during below // operations (e.g. remove operations) close gets called which calls // destroy again. // what happens: after the 2nd call to here, we'll get HMEException from // the remove call that caused it which contains the actual cause // (unless it was originally an HMEException) // setId(-1) has not been called on the object yet // SO: want to catch HMEExceptions if we want to finish our destroy // operation if (destroyedAlready) return; destroyedAlready = true; // template method destroyCleanup(); // ... operations that could cause a fatal error, e.g. "remove" calls super.destroy(); // in case e.g. BApplication ever starts doing // something there. } /** * Improved version of * {@link Application#handleApplicationError(int, String)} that includes the * resourceId related to the error - critical for knowing when you have a * problem with a resource you created. Default implementation simply calls * {@link #handleApplicationError(int, String)}. ApplicationError codes 1 * (e.g. "can't create rsrc. unsupported stream type * /avatar/cf1e61a4330e75d5d1d7a744c5ef38c4") and 3 (e.g. "resource 2091 not * found (type type[-1])") about a resource never provide a resourceinfo * event! * * @param errorCode * the error code of the error (one of the {@link IHmeProtocol} * APP_ERROR_ constants) * @param errorText * details of the error * @param resourceId * the id associated with the error - could be null. Suitable for * use with {@link #getResource(Object)} (or ((WeakReference) * {@link #getResources()}.get(Object)).get() to avoid automatic * SDK warning message when it's not found) * @return true if the event was handled. */ public boolean handleApplicationError(int errorCode, String errorText, Integer resourceId) { if (handleApplicationError(errorCode, errorText)) { return true; } else { // FIXME add processing needed by e.g. image exceeding max width or // height (errorCode 2) with no resource id return false; } } /** * Perform some common handling - be sure to call this from subclasses that * override it. Sets {@link #tivoVersion} from DEVICE_INFO. Improves error * handling by posting resource errors to the resource itself, and calling * {@link #handleApplicationError(int, String, Integer)}, passing in * resource ID. * */ public boolean handleEvent(HmeEvent event) { // get tivo version from device info if (event.getOpCode() == EVT_DEVICE_INFO) { HmeEvent.DeviceInfo info = (DeviceInfo) event; this.tivoVersion = (String) info.getMap().get("version"); //$NON-NLS-1$ try { this.deviceZone = TimeZone.getTimeZone((String) info.getMap().get("zoneinfo")); //TODO assuming GMT is not right. (it's the default if the supplied zone is not found) if(this.deviceZone == null || this.deviceZone.getID().equals("GMT")) { debug("TimeZone object: ignore GMT"); this.deviceZone = null; } } catch(Exception e) { this.deviceZone = null; } debug("TimeZone object: "+(this.deviceZone==null?null:this.deviceZone.getID())); this.deviceInfo = info.getMap(); } // ... if (event.getOpCode() == EVT_APP_INFO) { HmeEvent.ApplicationInfo info = (HmeEvent.ApplicationInfo) event; if (info.getMap().get("error.code") != null) { //$NON-NLS-1$ int errorCode = Integer.parseInt((String) info.getMap().get( "error.code")); //$NON-NLS-1$ Integer resourceId = null; if (info.getMap().get("error.rsrc") != null) { //$NON-NLS-1$ try { resourceId = new Integer((String) info.getMap().get( "error.rsrc")); //$NON-NLS-1$ } catch (NumberFormatException e) { resourceId = null; } } String errorText = (String) info.getMap().get("error.text"); //$NON-NLS-1$ if (resourceId != null) { WeakReference res_wr = (WeakReference) getResources().get( resourceId); if (res_wr != null && res_wr.get() != null) { Resource res = (Resource) res_wr.get(); Map resInfoMap = info.getMap(); // TODO we're doing this regardless of whether it's // actually a change in status if (res.status == Resource.RSRC_STATUS_ERROR) { debug("NOTE: had to send ResourceStatus event for error even though resource's status was already error."); //$NON-NLS-1$ } debug("Posting APP_INFO resource error to resource " //$NON-NLS-1$ + resourceId + ": " + resInfoMap); //$NON-NLS-1$ res.postEvent(new MockResourceStatus(resourceId .intValue(), res, Resource.RSRC_STATUS_ERROR, resInfoMap)); } } else debug("APP_INFO error " + errorCode + ": " + errorText); //$NON-NLS-1$ //$NON-NLS-2$ return handleApplicationError(errorCode, errorText, resourceId); } } boolean result = super.handleEvent(event); // ... return result; } /** * SDK Bug Fix - OMG! sick bug in HMEObject that has never been noticed or * exercised since nobody has apparently attempted to do App-in-app with * {@link #createStream(String, Map)}. They forgot a "new" keyword and * auto-"fixed" the problem creating a method named LinkedHashMap that has * default method content returning null! Also, calling code won't handle * the nulls that are returned in certain circumstances, so now returning an * empty map here. Lucky for us it is public and non-final. {@inheritDoc} * * @see com.tivo.hme.sdk.HmeObject#parseQuery(java.lang.String) */ public Map parseQuery(String query) { Map map = new LinkedHashMap(); if (query == null || query.indexOf('=') == -1) { return map; } int at = 0; int len = query.length(); if (query.startsWith("?")) { //$NON-NLS-1$ ++at; } do { // find = and & int equal = query.indexOf('=', at); if (equal < 0) { throw new IllegalArgumentException( "invalid query (trailing key, = not found)"); //$NON-NLS-1$ } int amp = query.indexOf('&', equal); if (amp == -1) { amp = query.length(); } // add the key/value try { String key = URLDecoder.decode(query.substring(at, equal), "UTF-8"); //$NON-NLS-1$ String value = URLDecoder.decode( query.substring(equal + 1, amp), "UTF-8"); //$NON-NLS-1$ map.put(key.toLowerCase(), value); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } // advance at = amp + 1; } while (at < len); return map; } /** * Override to additionally support the STREAM_* constants to call * createStream, and to support a new text creation shortcut (like animation * and color) when a String key starts with an apostrophe. */ @Override public Resource getResource(Object key) { if (key instanceof String) { if (key.toString().equals(STREAM_LOOP_BLUE_SETUP) || key.toString().equals(STREAM_LOOP_GREEN_NOWPLAYING) || key.toString().equals(STREAM_LOOP_PURPLE_SHOWCASES) || key.toString().equals(STREAM_LOOP_RED_CENTRAL) || key.toString().equals(STREAM_LIVETV) || key.toString().startsWith(STREAM_RECORDING_PREFIX)) { return createStream(key.toString()); } if (createTextPattern.matcher(key.toString()).matches()) { return createText(key.toString()); } else if(key.toString().indexOf(".font:")>0){ System.out.println("NOPE:"+key); } } return super.getResource(key); } /* END fix JDK */ private Pattern createTextPattern = Pattern.compile("(?:java.awt.Color)?\\[([^\\]]*)(?:\\]([^:]*)(?::(.*))?)?", Pattern.DOTALL); /** * Creates a text resource using a specially formatted string, or using * default color (white) and font (default-15.font).<BR/> * <B>Supported createText shortcut format:</B><BR/> * A string that begins with [ or a Color.toString() is a createText * shortcut. Color is specified between the brackets, font is specified * after the close bracket and before a colon, text is after the colon. * Color defaults to white (0xFFFFFF), and font defaults to default-15.font. * Shortcut using just defaults is text after a [ (as long as the text * doesn't have a close bracket in it). Equivalent examples:<BR/> * "[0xFFFFFF]default-15.font:Hello World" or "[]:Hello World" or * "[]Hello World" or "[Hello World" or Color.white+"Hello World" or * Color.white+":Hello World" or Color.white+"default-15.font:Hello World" <BR/> * * @param stringSpec * [color]font:text, [text, []text, []:text, [color]:text, * java.awt.Color[color]:text, []font:text, or * java.awt.Color[color]font:text where color is 0xHHHHHH or * r=DDD,g=DDD,b=DDD */ public Resource createText(String stringSpec) { Object color = Color.white; String font = "default-15.font"; Matcher matcher = createTextPattern.matcher(stringSpec); if (matcher.matches() && matcher.groupCount() == 3) { String sfont = null; Object scolor = null; if (matcher.group(2) == null) { stringSpec = matcher.group(1); } else if (matcher.group(3) == null) { scolor = parseColor(matcher.group(1).trim()); stringSpec = matcher.group(2); } else { scolor = parseColor(matcher.group(1).trim()); sfont = matcher.group(2).trim(); stringSpec = matcher.group(3); } if (scolor != null) { color = scolor; } if (sfont != null && sfont.length() > 0) { if (sfont.endsWith(".font")) { font = sfont; } } } return createText(font, color, stringSpec); } private Pattern colorToStringPattern = Pattern.compile("r=(\\d+),g=(\\d+),b=(\\d+)"); private Object parseColor(String color) { if (color.startsWith("0x")) { return color; } Matcher matcher = colorToStringPattern.matcher(color); if (matcher.matches()) { try { if (matcher.groupCount() == 3) { return new Color(Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)), Integer.parseInt(matcher.group(3))); } } catch(Exception e) { return color; } } if(color.trim().length() == 0) { return null; } return color; } /** * Override to handle additional system font ids as [id].ttf and additional * names. */ public Resource createTrueType(String family) { // automatically handle 10.ttf - 19.ttf as ID refs if (family != null && family.matches("\\d+\\.ttf")) { try { int id = Integer.parseInt(family.substring(0, family.length()-4)); if (id < 20 && id >= 10) { return new TrueTypeFalseResource(this, family, id); } if(id >= 20) { return getResource(new Integer(id)); } } catch (Exception e) { } } // handle specific new names if (family.equals(TTF_SERIES4FONT)) { return new TrueTypeFalseResource(this, family, 13); } else if (family.equals(TTF_SERIES4FONTITALIC)) { return new TrueTypeFalseResource(this, family, 14); } else if (family.equals(TTF_SERIES4FONT3BOLD)) { return new TrueTypeFalseResource(this, family, 15); // } else if... { } else { // default behavior return super.createTrueType(family); } } /** * Equivalent of TrueTypeResource except accessible to this class so we can * instantiate additional names with a supplied id */ private static class TrueTypeFalseResource extends Resource { String name; TrueTypeFalseResource(Application app, String name, int id) { super(app, id); this.name = name; ((V49BApplication)app).debug("Created font " + name + " with id " + id); } /** * This is a thread-safe public entry point. */ protected void toString(StringBuffer buf) { synchronized (lock) { buf.append(",ttf=" + name); } } } private boolean waitingToInit; @Override protected void setContext(IContext context, int version) { super.setContext(context, version); // make accessible version of private Application class variable. this.protocolVersion = version; debug("Protocol Version per TiVo DVR: " + protocolVersion); //$NON-NLS-1$ debug("attributes: " + context.getConnectionAttributes()); //$NON-NLS-1$ // User-Agent: TvHttpClient -my tivo (and tsn=64...3E ) // User-Agent: TmkHttpRequest/1.0 - from somebody on internet - old // version of tivo? // User-Agent=tivo.http/03/30/2003 - simulator // attributes: {Cookie=id=895df24b5038b953631128d3aa994863d51da2b1, // Host=192.168.0.20:7288, User-Agent=TvHttpClient, Connection=close, // tsn=64...3E} this.tivoID = context.getConnectionAttribute("tsn"); //$NON-NLS-1$ // context.getConnectionAttribute(HTTP_HEADER_TIVO_ID); // tivoVersion is in DEVICE_INFO event under "version" // this.tivoVersion = // context.getConnectionAttribute(HTTP_HEADER_TIVO_VERSION); } /** * You should override {@link #initBeforeResolutionSet(IContext)} to do your * init work while this override handles resolution details after that is * called. */ @Override public void init(IContext context) throws Exception { super.init(context); setActive(true); // template method for subclasses to perform init activities initBeforeResolutionSet(context); waitingToInit = true; boolean changed = setPreferredResolution(); // will continue init on rootBoundsChanged() this.setActive(true); if (!changed || !isResolutionSupported()) { waitingToInit = false; showFirstPage(); } } /** * This override coordinates with the {@link #init(IContext)} override to * call {@link #showFirstPage()} at the right time. Should probably override * {@link #rootBoundsChangedBeforeFirstPage(Rectangle)} instead of this to * maintain order of calls. */ @Override public void rootBoundsChanged(Rectangle rootBounds) { super.rootBoundsChanged(rootBounds); // template method for subclasses to perform rootbounds activities? maybe just subclass calling super is good enough here. rootBoundsChangedBeforeFirstPage(rootBounds); if (isResolutionSupported()) { if (waitingToInit) { waitingToInit = false; showFirstPage(); } } } /** * Resolution control is supported in protocol version 0.43 and SDK 1.4.1 */ public boolean isResolutionSupported() { return this.protocolVersion >= VERSION_0_43 && isSDK141(); } /** * Stream Map argument is supported in protocol version 0.45 and SDK 1.4.1 */ public boolean isStreamMapSupported() { return this.protocolVersion >= VERSION_0_45 && isSDK141(); } /** * Slide Remote is supported in protocol version 0.49 */ public boolean isSlideRemoteSupported() { return this.protocolVersion >= VERSION_0_49; } /** * Tries to get the version string from the com.tivo.hme.sdk.Version class * which doesn't exist in all SDK versions. Returns null if it can't find * the class. * * @return */ public static String getSDKVersion() { try { if (Class.forName("com.tivo.hme.sdk.Version") != null) //$NON-NLS-1$ return Version.getVersion(); else return null; } catch (ClassNotFoundException e) { return null; } } public static boolean isSDK141() { if (getSDKVersion() != null) return getSDKVersion().startsWith("1.4.1"); //$NON-NLS-1$ else return false; } /** a multi-SDK version of the SDK 1.4.1 method */ public int getSafeActionHorizontal() { if (!isSDK141()) { return SAFE_ACTION_H; } else { return super.getSafeActionHorizontal(); } } /** a multi-SDK version of the SDK 1.4.1 method */ public int getSafeTitleVertical() { if (!isSDK141()) { return SAFE_TITLE_V; } else { return super.getSafeTitleVertical(); } } /** a multi-SDK version of the SDK 1.4.1 method */ public int getSafeActionVertical() { if (!isSDK141()) { return SAFE_ACTION_V; } else { return super.getSafeActionVertical(); } } /** a multi-SDK version of the SDK 1.4.1 method */ public int getSafeTitleHorizontal() { if (!isSDK141()) { return SAFE_TITLE_H; } else { return super.getSafeTitleHorizontal(); } } /** Standard title area for e.g. a STREAM_LOOP_* background. */ public Rectangle getTitleRegion() { int topPad = getSafeTitleVertical(); int height = getSafeTitleToScrollTop() - 15; int leftPad = getSafeTitleHorizontal() + getSafeLogoToScrollTitle(); int rightPad = getSafeTitleHorizontal(); return new Rectangle(leftPad, topPad, getWidth()-leftPad-rightPad, height); } /** Standard logo area for e.g. a STREAM_LOOP_* background. */ public Rectangle getLogoRegion() { int topPad = getSafeTitleVertical(); int height = getSafeTitleToScrollTop() - 15; int leftPad = getSafeTitleHorizontal() - 20; int width = getSafeLogoToScrollTitle() + 10; // seems to fit, but shouldn't it be +20 to offset leftpad adjustment? return new Rectangle(leftPad, topPad, width, height); } /** Standard scroll area for e.g. a STREAM_LOOP_* background. */ public Rectangle getScrollRegion() { int bottomPad = getSafeTitleVertical()+getSafeTitleToScrollBottom(); int topPad = getSafeTitleVertical()+getSafeTitleToScrollTop(); int leftPad = getSafeTitleHorizontal()+17; // pad 17(for hd at least) to get "inside" the scroll region like the top is int rightPad = getSafeTitleHorizontal(); return new Rectangle(leftPad, topPad, getWidth()-leftPad-rightPad, getHeight()-topPad-bottomPad); } /** Region based {@link #getSafeTitleHorizontal()} and {@link #getSafeTitleVertical()} */ public Rectangle getSafeTitleRegion() { return new Rectangle(getSafeTitleHorizontal(), getSafeTitleVertical(), getWidth() - 2 * getSafeTitleHorizontal(), getHeight() - 2 * getSafeTitleVertical()); } /** Region based {@link #getSafeActionHorizontal()} and {@link #getSafeActionVertical()} */ public Rectangle getSafeActionRegion() { return new Rectangle(getSafeActionHorizontal(), getSafeActionVertical(), getWidth() - 2 * getSafeActionHorizontal(), getHeight() - 2 * getSafeActionVertical()); } /** * Vertical Space from safeTitle bottom to bottom of scrolling area of of a * STREAM_LOOP_* background. */ public int getSafeTitleToScrollBottom() { if (isResolutionSupported() && getCurrentResolution().getHeight() > 480) { return 20; } else { return 15; // SD number } } /** * Vertical Space from safeTitle top to top of scrolling area of a * STREAM_LOOP_* background. */ public int getSafeTitleToScrollTop() { if (isResolutionSupported() && getCurrentResolution().getHeight() > 480) { return 85 + 60; } else { return 95; // SD number } } /** * Horizontal space from safeTitle left to right edge of TiVo logo in the * title area of a STREAM_LOOP_* background. Note, logo extends a little to the left of the safeTitle left. */ public int getSafeLogoToScrollTitle() { if (isResolutionSupported() && getCurrentResolution().getHeight() > 480) { return 100; // could need a diff number for 720, but this will work. } else { return 45;// SD number - goes past "glow" } } protected void setMaxStackDepth(int max) { if (isSDK141()) { super.setMaxStackDepth(max); } } /** * If {@link #isResolutionSupported()}, makes sure the resolution is the * preferred one, and if it's not, calls * {@link #setReceiverResolution(com.tivo.hme.sdk.Resolution)} with the * ResolutionInfo's preferred resolution. * * @return true if {@link #setReceiverResolution(com.tivo.hme.sdk.Resolution)} was actually called. */ protected boolean setPreferredResolution() { /* BEGIN HME 1.4.1e only */ if (isResolutionSupported()) { boolean noChangeNeeded = this .getReceiverResolutionInfo() .getPreferredRenderingResolution() .equals(this.getReceiverResolutionInfo() .getRenderingResolution()); if (noChangeNeeded) { // debugOut(this.getCurrentResolution()+" is already preferred resolution of "+this.getReceiverResolutionInfo().getAvailableRenderingResolutions()); return false; } else { // debugOut(this.getCurrentResolution() // +" => "+this.getReceiverResolutionInfo().getPreferredRenderingResolution()+" of "+this.getReceiverResolutionInfo().getAvailableRenderingResolutions()); this.setReceiverResolution(this.getReceiverResolutionInfo() .getPreferredRenderingResolution()); return true; } } /* END HME 1.4.1e only */ return false; } /** Multiplier relative to SD height of 480 */ public double getHeightMultiplier() { if (isResolutionSupported()) { return getCurrentResolution().getHeight() / 480.0; } else { return 1.0; } } /** Multiplier relative to SD width of 640 */ public double getWidthMultiplier() { if (isResolutionSupported()) { return getCurrentResolution().getWidth() / 640.0; } else { return 1.0; } } }
page revision: 2, last edited: 28 Dec 2014 21:24