• Android EditText automatic MAC address formatting

    Android EditText automatic MAC address formatting

    I recently made a simple Android Wake-on-Lan app which includes an EditText MAC address input field. To make it easier for users to type in proper MAC’s (not having to type colons manually) I used TextWatcher to automatically format the input on text-changed event. This stackoverflow topic gave me a general idea on how to do it. What I wanted is for the application to insert a colon after every second character, remove the character preceding a colon if the user deletes the colon and to handle MAC address editing. For example if the user takes out a few characters from the middle of the string, the formatting should still be preserved and the cursor should stay in the right place and not go to the end or beginning of the string.

    The only callback I used was onTextChanged(). On each text-change event, users input gets stripped of all non-MAC characters (this means only numbers 0-9 and letters A-F are left), then the colons are added and after that (potential) character deletion and cursor positioning are handled. Here is the code I came up with in the end:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    private EditText mMacEdit = (EditText)findViewById(R.id.edit_mac);

    /**
     * Registers TextWatcher for MAC EditText field. Automatically adds colons,  
     * switches the MAC to upper case and handles the cursor position.
     */

    private void registerAfterMacTextChangedCallback() {
        mMacEdit.addTextChangedListener(new TextWatcher() {
            String mPreviousMac = null;

            /* (non-Javadoc)
             * Does nothing.
             * @see android.text.TextWatcher#afterTextChanged(android.text.Editable)
             */

            @Override
            public void afterTextChanged(Editable arg0) {
            }

            /* (non-Javadoc)
             * Does nothing.
             * @see android.text.TextWatcher#beforeTextChanged(java.lang.CharSequence, int, int, int)
             */

            @Override
            public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
            }

            /* (non-Javadoc)
             * Formats the MAC address and handles the cursor position.
             * @see android.text.TextWatcher#onTextChanged(java.lang.CharSequence, int, int, int)
             */

            @SuppressLint("DefaultLocale")
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                String enteredMac = mMacEdit.getText().toString().toUpperCase();
                String cleanMac = clearNonMacCharacters(enteredMac);
                String formattedMac = formatMacAddress(cleanMac);

                int selectionStart = mMacEdit.getSelectionStart();
                formattedMac = handleColonDeletion(enteredMac, formattedMac, selectionStart);
                int lengthDiff = formattedMac.length() - enteredMac.length();

                setMacEdit(cleanMac, formattedMac, selectionStart, lengthDiff);
            }

            /**
             * Strips all characters from a string except A-F and 0-9.
             * @param mac       User input string.
             * @return          String containing MAC-allowed characters.
             */

            private String clearNonMacCharacters(String mac) {
                return mac.toString().replaceAll("[^A-Fa-f0-9]", "");
            }

            /**
             * Adds a colon character to an unformatted MAC address after
             * every second character (strips full MAC trailing colon)
             * @param cleanMac      Unformatted MAC address.
             * @return              Properly formatted MAC address.
             */

            private String formatMacAddress(String cleanMac) {
                int grouppedCharacters = 0;
                String formattedMac = "";

                for (int i = 0; i < cleanMac.length(); ++i) {
                    formattedMac += cleanMac.charAt(i);
                    ++grouppedCharacters;

                    if (grouppedCharacters == 2) {
                        formattedMac += ":";
                        grouppedCharacters = 0;
                    }
                }

                // Removes trailing colon for complete MAC address
                if (cleanMac.length() == 12)
                    formattedMac = formattedMac.substring(0, formattedMac.length() - 1);

                return formattedMac;
            }

            /**
             * Upon users colon deletion, deletes MAC character preceding deleted colon as well.
             * @param enteredMac            User input MAC.
             * @param formattedMac          Formatted MAC address.
             * @param selectionStart        MAC EditText field cursor position.
             * @return                      Formatted MAC address.
             */

            private String handleColonDeletion(String enteredMac, String formattedMac, int selectionStart) {
                if (mPreviousMac != null && mPreviousMac.length() > 1) {
                    int previousColonCount = colonCount(mPreviousMac);
                    int currentColonCount = colonCount(enteredMac);

                    if (currentColonCount < previousColonCount) {
                        formattedMac = formattedMac.substring(0, selectionStart - 1) + formattedMac.substring(selectionStart);
                        String cleanMac = clearNonMacCharacters(formattedMac);
                        formattedMac = formatMacAddress(cleanMac);
                    }
                }
                return formattedMac;
            }

            /**
             * Gets MAC address current colon count.
             * @param formattedMac      Formatted MAC address.
             * @return                  Current number of colons in MAC address.
             */

            private int colonCount(String formattedMac) {
                return formattedMac.replaceAll("[^:]", "").length();
            }

            /**
             * Removes TextChange listener, sets MAC EditText field value,
             * sets new cursor position and re-initiates the listener.
             * @param cleanMac          Clean MAC address.
             * @param formattedMac      Formatted MAC address.
             * @param selectionStart    MAC EditText field cursor position.
             * @param lengthDiff        Formatted/Entered MAC number of characters difference.
             */

            private void setMacEdit(String cleanMac, String formattedMac, int selectionStart, int lengthDiff) {
                mMacEdit.removeTextChangedListener(this);
                if (cleanMac.length() <= 12) {
                    mMacEdit.setText(formattedMac);
                    mMacEdit.setSelection(selectionStart + lengthDiff);
                    mPreviousMac = formattedMac;
                } else {
                    mMacEdit.setText(mPreviousMac);
                    mMacEdit.setSelection(mPreviousMac.length());
                }
                mMacEdit.addTextChangedListener(this);
            }
        });
    }

    One important thing to note is the removal and re-adding of TextChangedListener in the setMacEdit() method. Without this part the code results in stack-overflow since every setText() triggers the TextChangedListener over and over again.

    Hope it helps, enjoy! :)

  • Automatic Wake-on-Lan (Android)

    Automatic Wake-on-Lan (Android)

    I just released this simple Android Wake-on-Lan app which lets you turn on your devices automatically over Wi-Fi upon obtaining network connectivity. The app features quiet-hours which let you suppress auto-wake during a period of time of your choosing. You can also set an “idle-time” value which can be used to suppress auto-wake for a period of time since your device has last been disconnected from Wi-Fi to prevent random wakes in case your Android device looses Wi-Fi connectivity.

    The app is released under GPLv3 over at github.

    Special thanks go to my good friend Marko Iličić for his help and guidance through the Android SDK. :)

    It was a fun ride, enjoy!

  • SaaS’s rottenpotatoes ruby-debug LoadError fix

    SaaS’s rottenpotatoes ruby-debug LoadError fix

    In two days Barkeley’s edx SaaS course is starting and I wanted to set up their rottenpotatoes RoR app on my machine without the VM they provided but I kept getting this error every time I tried to run “rails server” within the rottenpotatoes directory:

    “/usr/lib/ruby/gems/1.9.1/gems/ruby-debug-base19-0.11.25/lib/ruby-debug-base.rb:1:in `require’: /usr/lib/ruby/gems/1.9.1/gems/ruby-debug-base19-0.11.25/lib/ruby_debug.so: undefined symbol: ruby_current_thread - /usr/lib/ruby/gems/1.9.1/gems/ruby-debug-base19-0.11.25/lib/ruby_debug.so (LoadError)”

    Knowing almost nothing about working with RoR it took me a while to figure out why exactly this wouldn’t work and how to fix it. I tried installing a few different versions of ruby-debug and whatnot but nothing helped. Finally, I found this answer on StackOverflow which let me to the right rail. :D Apparently, ruby-debug19 and ruby-debug-base19 gems haven’t been updated in some time (and won’t any time soon) which already caused headaches for many people around the world.

    To fix this simply install debugger (“Debugger is a fork of ruby-debug called ‘debugger’ and it incorporates many of the fixes people had been deploying on top of ruby-debug19.”) gem using:

    1
    gem install debugger

    after that edit the following line from “Gemfile” file from your rottenpotatoes directory:

    1
    gem 'ruby-debug19', :require => 'ruby-debug'

    to:

    1
    gem 'debugger', :require => 'ruby-debug'

    save the changes and run “rails server”. You should get this as the output:

    1
    2
    3
    4
    5
    6
    7
    => Booting WEBrick
    => Rails 3.1.0 application starting in development on http://0.0.0.0:3000
    => Call with -d to detach
    => Ctrl-C to shutdown server
    [2013-01-05 17:30:01] INFO WEBrick 1.3.1
    [2013-01-05 17:30:01] INFO ruby 1.9.3 (2012-12-25) [i686-linux]
    [2013-01-05 17:30:01] INFO WEBrick::HTTPServer#start: pid=3041 port=3000

    and now you will be able to acces the rottenpotatoes app on http://localhost:3000/movies

  • Pyramid installation – “UnicodeDecodeError: ‘ascii’ codec can’t decode byte” error fix

    Pyramid installation – “UnicodeDecodeError: ‘ascii’ codec can’t decode byte” error fix

    Following pyramid framework installation notes I kept getting ‘ascii’ codec can’t decode byte error message. The whole error output looked like this:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    $ bin/easy_install pyramid
    Searching for pyramid
    Reading http://pypi.python.org/simple/pyramid/
    Reading http://docs.pylonshq.com
    Reading http://pylonsproject.org
    Reading http://docs.pylonsproject.org
    Best match: pyramid 1.4a4
    Downloading http://pypi.python.org/packages/source/p/pyramid/pyramid-1.4a4.tar.gz#md5=0a5da1a25791764cdfefb15b2d55656a
    Processing pyramid-1.4a4.tar.gz
    Writing /tmp/easy_install-hrcuuc/pyramid-1.4a4/setup.cfg
    Running pyramid-1.4a4/setup.py -q bdist_egg --dist-dir /tmp/easy_install-hrcuuc/pyramid-1.4a4/egg-dist-tmp-kbuxa9
    Traceback (most recent call last):
    File "bin/easy_install", line 9, in
    load_entry_point('distribute==0.6.28', 'console_scripts', 'easy_install')()
    File "/home/pootzko/env/lib/python3.3/site-packages/distribute-0.6.28-py3.3.egg/setuptools/command/easy_install.py", line 1929, in main
    with_ei_usage(lambda:
    File "/home/pootzko/env/lib/python3.3/site-packages/distribute-0.6.28-py3.3.egg/setuptools/command/easy_install.py", line 1910, in with_ei_usage
    return f()
    File "/home/pootzko/env/lib/python3.3/site-packages/distribute-0.6.28-py3.3.egg/setuptools/command/easy_install.py", line 1933, in
    distclass=DistributionWithoutHelpCommands, **kw
    File "/usr/lib/python3.3/distutils/core.py", line 148, in setup
    dist.run_commands()
    File "/usr/lib/python3.3/distutils/dist.py", line 917, in run_commands
    self.run_command(cmd)
    File "/usr/lib/python3.3/distutils/dist.py", line 936, in run_command
    cmd_obj.run()
    File "/home/pootzko/env/lib/python3.3/site-packages/distribute-0.6.28-py3.3.egg/setuptools/command/easy_install.py", line 358, in run
    self.easy_install(spec, not self.no_deps)
    File "/home/pootzko/env/lib/python3.3/site-packages/distribute-0.6.28-py3.3.egg/setuptools/command/easy_install.py", line 598, in easy_install
    return self.install_item(spec, dist.location, tmpdir, deps)
    File "/home/pootzko/env/lib/python3.3/site-packages/distribute-0.6.28-py3.3.egg/setuptools/command/easy_install.py", line 628, in install_item
    dists = self.install_eggs(spec, download, tmpdir)
    File "/home/pootzko/env/lib/python3.3/site-packages/distribute-0.6.28-py3.3.egg/setuptools/command/easy_install.py", line 823, in install_eggs
    return self.build_and_install(setup_script, setup_base)
    File "/home/pootzko/env/lib/python3.3/site-packages/distribute-0.6.28-py3.3.egg/setuptools/command/easy_install.py", line 1103, in build_and_install
    self.run_setup(setup_script, setup_base, args)
    File "/home/pootzko/env/lib/python3.3/site-packages/distribute-0.6.28-py3.3.egg/setuptools/command/easy_install.py", line 1089, in run_setup
    run_setup(setup_script, args)
    File "/home/pootzko/env/lib/python3.3/site-packages/distribute-0.6.28-py3.3.egg/setuptools/sandbox.py", line 31, in run_setup
    lambda: exec(compile(open(
    File "/home/pootzko/env/lib/python3.3/site-packages/distribute-0.6.28-py3.3.egg/setuptools/sandbox.py", line 79, in run
    return func()
    File "/home/pootzko/env/lib/python3.3/site-packages/distribute-0.6.28-py3.3.egg/setuptools/sandbox.py", line 34, in
    {'__file__':setup_script, '__name__':'__main__'})
    File "setup.py", line 122, in
    File "/usr/lib/python3.3/distutils/core.py", line 148, in setup
    dist.run_commands()
    File "/usr/lib/python3.3/distutils/dist.py", line 917, in run_commands
    self.run_command(cmd)
    File "/usr/lib/python3.3/distutils/dist.py", line 936, in run_command
    cmd_obj.run()
    File "/home/pootzko/env/lib/python3.3/site-packages/distribute-0.6.28-py3.3.egg/setuptools/command/bdist_egg.py", line 172, in run
    self.run_command("egg_info")
    File "/usr/lib/python3.3/distutils/cmd.py", line 313, in run_command
    self.distribution.run_command(command)
    File "/usr/lib/python3.3/distutils/dist.py", line 936, in run_command
    cmd_obj.run()
    File "/home/pootzko/env/lib/python3.3/site-packages/distribute-0.6.28-py3.3.egg/setuptools/command/egg_info.py", line 179, in run
    self.find_sources()
    File "/home/pootzko/env/lib/python3.3/site-packages/distribute-0.6.28-py3.3.egg/setuptools/command/egg_info.py", line 254, in find_sources
    mm.run()
    File "/home/pootzko/env/lib/python3.3/site-packages/distribute-0.6.28-py3.3.egg/setuptools/command/egg_info.py", line 308, in run
    self.add_defaults()
    File "/home/pootzko/env/lib/python3.3/site-packages/distribute-0.6.28-py3.3.egg/setuptools/command/egg_info.py", line 339, in add_defaults
    self.read_manifest()
    File "/usr/lib/python3.3/distutils/command/sdist.py", line 377, in read_manifest
    for line in manifest:
    File "/home/pootzko/env/lib/python3.3/encodings/ascii.py", line 26, in decode
    return codecs.ascii_decode(input, self.errors)[0]
    UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 7896: ordinal not in range(128)

    …aaaaand it kept driving me nuts for a couple of hours until it finally came to me that the problem lied my locale setup and not in python setuptools. Simply typing:

    1
    export LANG='en_US.UTF-8'

    in terminal to change the encoding fixed the problem (keep in mind that this changed the locale temporarily only for the current session).

    Hope it saved you some time. Cheers!

  • autokana – jQuery plugin

    autokana – jQuery plugin

    Autokana is a jQuery plugin that automatically converts latin (roumaji) to kana (hiragana / katakana) using the auto-correct principle (keep in mind that it’s not suitable for converting copy/pasted but only typed-in text). You can check out how it works at autokana example page.

    You can also download the plugin (including a simple usage example) from github and use it for free.

    To use it, you will have to add the following somewhere at the beginning of your body tag:

    1
    2
    3
    4
    5
    6
    7
    <script type="text/javascript">
       // bind events when document is ready
       $(document).ready(function() {
          // attach auto-kana plugin with default options
          $("#kana_text_box").autokana();
       });
    </script>

    After that add the input box itself wherever you need it (make sure to use the same ID in the above part and in the input field itself):

    1
    2
    3
    4
    5
    6
    7
    <input id="kana_text_box" type="text" />
    <script type="text/javascript">
       // this part will clear and set focus on the input field on refresh
       // feel free to remove that part if you don't need it
       document.getElementById("kana_text_box").value="";
       document.getElementById("kana_text_box").focus();
    </script>

    If you have any suggestions or bug reports, feel free to leave a comment.

  • Mapping Japanese kana characters (hiragana/katakana) for use in programming

    Mapping Japanese kana characters (hiragana/katakana) for use in programming

    When I started working on Zanzou, I had to find a way to somehow map the whole kana so I could later use it in my webapp. I simply used 2D array to accomplish this. It’s no nuclear science but to save you the trouble of copying and pasting all the symbols into a single file yourself (I think it actually took me around an hour to do it :D), here is my kana.js file (note that if if you open the file itself in the browser, it will most probably display some strange characters instead of proper UTF-8 encoded kana symbols but when you open the file in your text editor, it should be usable and displayed just fine).

    I divided it into 4 major groups (arrays): monographs, digraphs, monographs with diacritics and digraphs with diacritics. Each array element (representing sound) consists of 3 subelements (roumaji record, and hiragana and katakana symbols).

    With some minor modifications, this could easily be used in any other programming language. Just make sure if you edit the file to save it in UTF-8.

    Hope it helps, がんばって!

Back to top