Sunday, September 30, 2012

Intel Atom emulator crashes with DatePicker

I love the new Intel Atom (x86) emulator for Android development. It starts up quickly and runs at real hardware speeds. I did have one big issue, every time I brought up a view with a DatePicker control in it the emulator would crash to the desktop. Not acceptable.

Running the same code on my phone or in the emulator using ARM worked like a champ. So what should a developer do? This is a bug in the emulator code, not my code. Doing a little research shows that it could be the hardware acceleration at fault. You can shut that off with a setting the android:layerType="software" in your XML file or with a call to setLayerType(View.LAYER_TYPE_SOFTWARE, null); 

The problem is I am developing my app for 2.2 and beyond. That call does not show up until a much later version of the Android platform. Time for some reflection.

Add the following as a new file to your project


import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import android.graphics.Paint;
import android.view.View;

public class NewerMethods {
    private static Method LayerType;

    static {
        initMethods();
    }

    private static void initMethods() {
        try {
            LayerType = View.class.getMethod("setLayerType", new Class[] {int.class, Paint.class});
            /* success, this is a newer device */
        }
        catch (NoSuchMethodException nsme) {
            /* failure, must be older device */
        }
    }

    public static void setLayerType(View view, int layerType, Paint paint) throws IOException {
        try {
            if (LayerType != null) {
                LayerType.invoke(view, layerType, paint);
            }
        }
        catch (InvocationTargetException ite) {
            /* unpack original exception when possible */
            Throwable cause = ite.getCause();
            if (cause instanceof IOException) {
                throw (IOException) cause;
            }
            else if (cause instanceof RuntimeException) {
                throw (RuntimeException) cause;
            }
            else if (cause instanceof Error) {
                throw (Error) cause;
            }
            else {
                /* unexpected checked exception; wrap and re-throw */
                throw new RuntimeException(ite);
            }
        }
        catch (IllegalAccessException ie) {
            System.err.println("unexpected " + ie);
        }
    }
}

Add the following to your onCreate method of the Activity that uses a DatePicker in its view. I have it right after my call to setConventView(...)


        try {
            NewerMethods.setLayerType(findViewById(android.R.id.content), 1, null);
        }
        catch (IOException e) {
            // Just doing this on newer stuff to avoid emulator crash
        }
        
I know, I am using a magic number in the call. I can't use View.LAYER_TYPE_SOFTWARE because Eclipse has no idea about it when compiling 2.2 code.

This allows my code to run just fine in the emulator. I am running 2.2 code in a 4.0.3 emulator as that is the super fast Intel based emulator. I also run the code on my actual 2.2 phone from time to time to make sure everything is fine. Just easier to stick on the computer using the keyboard and mouse while doing initial development and only test on hardware as needed.

Thursday, September 20, 2012

Xcode 4.5 iOS 6 iPhone 5 simulator issues


Last night before I left I begin the Xcode 4.5 official release download. This morning it was ready to roll and then it needed to update a few other areas of Xcode but it was up and running pretty quickly. I then went about the work of having my app make use of the full height of the iPhone 5 screen.

Xcode asked if I wanted to add the Default-568h@2x.png which I allowed it to do. I knew that was one of the requirements so I was very happy it created the all black image for me. Our launch time is super fast so I don't need a real launch image.

Next I knew the rotation messages needed a massage. I add the following lines to all the ViewControllers allowing all orientations to work.

// OS 6 rotate message
- (BOOL)shouldAutorotate {
    return YES;
}

// OS 6 rotate message response
- (NSUInteger)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskAll;
}

I left the original orientation code in place for non-iOS 6 devices. 

Time to tweak the rootViewController

    // Changed for iOS 6
    self.window.rootViewController = self.navigationController;
//  [window addSubview:navigationController.view];


I had to switch to libxml2.2.dylib for libxlm.2.2.7.3.dylib for my frameworks. Not sure why the newer Xcode has what appears to be an older version of this lib but everything appears to work using it.

I download older simulators (4.3 and 5.1) so I can test as many as possible without swapping devices.

Adjusting the home screen paint for extra size by checking for iPhone 5 resolution and putting our logo below the grid of buttons on the portrait version makes that look nice. Putting extra space between each icon on landscape mode to space them evenly across the screen gives that area the proper look.

All the other screens filled the new height and went smoothly and things look great except for one big issue I have not yet been able to resolve. The back button in the navigation bar does not work in the simulator. Well it does work for everything but an iPhone (Retina 4-inch). I can choose iOS 4.3, 5.1 or 6.0 and any device - iPhone, iPhone (Retina 3.5-inch),  iPad, iPad (Retina) - and everything about my program works just fine. Once I set the hardware to be iPhone (Retina 4-inch) then the buttons will not work. Nothing happens when I click on them, no errors, no highlighting, no action at all.

Seems to me I am not having an iOS 6.0 issue but a simulator issue. Until I get my hands on some real hardware I am not sure what to do. I can install iOS 6.0 on my iPad 2 or my third gen iTouch but that does not tell me if things have gone haywire on a real iPhone 5.

The other area of fun is two of my hardware testing devices are no longer supported by Xcode 4.5. I can't use the second generation iTouch or the iPhone 3G. They are both stuck at iOS 4.2.1. We checked our logs and it appears we still have a user with that flavor of iOS. If I update the software I would set 4.3 as the minimum. Users would either stick with the version they have, upgrade their iOS if possible or buy new hardware. Apple is sort of forcing things along in this area. Even the original iPad has hit the end of its upgrade cycle.

At this point I get to keep digging to find out why the simulator is not letting me press the back button or the menu button I have at the other end of the navigation area on the 4" iPhone. Pretty obvious that I will not even think of releasing a new cut of the program until this is resolved. Sure, the simulator can be hosed as long as the code runs on a real device I will be fine. I really am hoping it is a code tweak to make it work across the board. Right now 4" iPhone information is pretty sparse.

** UPDATE ** Not fixed but I did try rotating the screen in the simulator. The buttons started to work. Then I rotated again and they stopped, then again and the back button would work but not the menu button. At times all buttons will work then will stop working. Rotation may or may not allow them to work again. I really does appear the simulator has an issue in this area.

** FIXED ** I had to open my MainWindow.XIB file, select the Window object and ticked "Full Screen at Launch". Now everything appears to be working on all simulator devices. I need to beat on the application for a bit more but now I at least feel safe checking in the code changes I have made. Hope this knowledge can help out someone else.

Monday, September 10, 2012

Mobile Native vs. HTML 5 - can't hide your JavaScript code

I have the scheduler viewer running in HTML 5. It scrolls around and paints rather nicely. Performance of the HTML 5 canvas control is acceptable on various devices. I had fun experimenting with the Canvas control but one thing really bothers me - anyone can grab my JavaScript code. Sure I could obfuscate it and make it a little more difficult to us but it is still there in your browser ready to steal. A pretty printer will reformat the code to be readable with silly looking variable and method names.

This is just a little sample app so I don't really care too much about this code but what if the code is logging in to your server and making a lot of API calls? Better not do all of that in JavaScript. That means you need to hide things on the server side. Basically you need to move a bulk of the work up there so others can't have free reign on your data. That puts more of a load on your server instead of on the client. We don't want slow clients but JavaScript has really stepped up its processing speed so putting what you can on the client makes sense.

I ran into this situation at a previous company. We wanted to do an HTML interface to our stock market data. Some one looked over the code as it was sent to their PC, figured out most of the API and started grabbing the 20 minute delayed stock market data off our feed. They were not using the API correctly and crashing our server. Going through all the fun of legal was really dragging out so we explain to the thief how to use the API to stop crashing the server. I was not involved in all the legal aspects so I don't know the full story or the final resolution. Given enough time we probably could have gotten the server to not crash and to obfuscate the API even more but that generally turns into a losing battle.

With a Native Mobile App this is much less likely to occur. Yes, you can sniff the wire and try to emulate the calls that way. People will do that. You can use HTTPS instead of HTTP which helps. It really is a lot harder to figure out an API from an App though, you just don't get to see the source code.

This does not make HTML 5 + JavaScript the wrong way to go. This does make you really think about JavaScript if you are accessing a lot of data off your server. If you are just doing an interactive web site, a game or something else where the loss of data is really very minimal and you don't mind others scanning your source code then go for it.

Before you decide HTML 5 is the way to go for a mobile project think about your level of source code exposure. Decide how much you need to handle on the server. Decide what code is harmless for others to see on the client side. Don't go in blind and try to solve these issues the week before your first release.

Thursday, September 6, 2012

Convert Numeric keypad [ENTER] to [TAB] in java

Our data entry staff does a lot of numeric entry. Java uses the [TAB] key to move between entry fields. I was asked if we could have the NumPad [ENTER] key act like the [TAB] key greatly speeding up data entry. I was able to pull it off with a little bit of code.


import java.awt.AWTEvent;
import java.awt.AWTException;
import java.awt.EventQueue;
import java.awt.Robot;
import java.awt.event.KeyEvent;

/**
 * Special event queue to convert NUMPAD + ENTER into TAB
 *
 * @author kevin.peck
 * @date Sep 4, 2012
 */
public class CustomEventQueue extends EventQueue {
    private Robot robot;
    private boolean swapKeys;

    /**
     * Constructor
     *
     * Get robot running so we can fake key events
     */
    public CustomEventQueue() {
        super();
        try {
            robot = new Robot();
        } catch (AWTException e) {
            System.out.println("Unable to get robot running " + e);
        }
    }

    /**
     * @param swapKeys  true to force key swap processing
     */
    public void setSwapKeys(boolean swapKeys) {
        this.swapKeys = swapKeys;
    }

    /**
     * @return  true if we are doing key swap processing
     */
    public boolean areKeysSwapped() {
        return swapKeys;
    }

    /**
     * Watch for keyevents and convert NUMPAD ENTER key into TAB key
     */
    @Override
    protected void dispatchEvent(AWTEvent event) {
        if (swapKeys && event instanceof KeyEvent) {
            KeyEvent keyEvent = (KeyEvent)event;
            if (keyEvent.getKeyLocation() == KeyEvent.KEY_LOCATION_NUMPAD && keyEvent.getKeyCode() == KeyEvent.VK_ENTER) {
                if (keyEvent.getID() == KeyEvent.KEY_PRESSED) {
                    robot.keyPress(KeyEvent.VK_TAB);
                    robot.keyRelease(KeyEvent.VK_TAB);
                }
                return;
            }
        }
        super.dispatchEvent(event);
    }
}

Add this to your main code (class derived from JFrame)
    private CustomEventQueue eventQueue = new CustomEventQueue();

Add this to the constructor or initialization routine of the class derived from JFrame
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                EventQueue ev = Toolkit.getDefaultToolkit().getSystemEventQueue();
                eventQueue.setSwapKeys(true);
                ev.push(eventQueue);
            }
        });

Why is there a setSwapKeys() method? So you can turn off this functionality. I set enabled the sample above but in my code it is actually reading a Preference setting. You can enable / disable the [ENTER] = [TAB] at any time in your code by invoking the swap keys method.

If swap keys is enabled I eat the VK_ENTER from the numeric keypad for both PRESSED and RELEASED events. I use the Robot object to send out a VK_TAB press and release on the PRESSED event. Nothing is sent on the VK_ENTER RELEASED event. 

Why am I installing the queue invokeLater? I was not doing that an it ran just fine on Java 1.7 but on Java 1.6 I would get an exception. Putting it on the EDT solved that issue.

So far it seems to be working like a champ. With minimal modifications you can watch for any event you need to perform special actions. Don't go crazy but have fun.