• KatKiss >> Cyanogenmod (Asus TF300T)

    KatKiss >> Cyanogenmod (Asus TF300T)

    Ever since my little TF300T rooting/flashing adventure I have had nothing but trouble with Cyanogenmod on my TF300T. I flashed 3 new versions throughout 3 months, but none of them were stable or fast enough. I have been a long time Cyanogenmod user and I like it a lot so that was my obvious choice. But the tablet would often become very, very slow, couldn’t connect to Wi-Fi or wouldn’t even be able to run certain apps. There are also other issues people reported and it seemed that things wouldn’t change in future.. I guess the device is simply not popular enough to have enough people care about and it maintain it’s CM version. Lately I started thinking I would either have to flash an older version of Android (perhaps even Asus stock crap) or retire the device to a dark corner of a drawer..

    Hopefully, I remembered seeing everyone giving praise to the KatKiss ROM so I decided to give it one last try and try KatKiss out. Man was this a pleasant surprise. The device is now bursting with speed! It’s really responsive, I haven’t noticed any bugs so far, it looks nice and I have the latest Android 5.1 features. It’s even faster than what it was with the old 4.x stock Android, it can now run anything (including Hearthstone! :D). I’m really happy as this means I will now be able to use the device with joy again.

    If you’re looking for a fresh rom for your TF300T – avoid CM and give KatKiss a try, it will save you time.

    Mad props to timduru! :)

  • TF300T Easter brick resurrection

    TF300T Easter brick resurrection

    Since Asus stopped providing us with official updates for TF300T tablet from the old Android 4.2.1, I decided it was finally time to root it and put a brand new shiny Lollipop ROM onto it. I rooted all my previous phones and it all went well all the times so I figured it would go smooth this time as well. Except it didn’t. :D

    Somehow I managed to follow an outdated tutorial for unlocking and flashing the TF300T that recommended using the ClockWorkMod recovery. All went well up to that point, I unlocked the tablet and put the CWM without any trouble but when I tried to flash the CyanogenMod ROM, CWM complained about not being able to mount any partitions. That meant that I couldn’t select the ROM zip file from a internal location or sideload it with ADB. Funky.

    So I did a bit more research and it turned out that CM does not really support CWM for Lollipop (not sure if only for this device, or generally, but it doesn’t matter anyway). Solution – flash TWRP recovery instead. Ok, so I went back to fastboot and tried to flash TWRP over CWM but it failed every time. This is where it all started to go down..

    My first mistake was that I downloaded the *-JB version of TWRP instead of the *-4.2. I was quite lucky here as it was only later that I read that if you screwed that part – hardbricking was guaranteed. Instead of everything going to smoke already, flashing TWRP simply failed every time (RCK would stop blinking and rebooting using fastboot -i 0x0B05 reboot didn’t work) and each reboot into recovery would load CWM again and again. I could still boot into Android as well. I thought that was strange so I tried a couple of times but of course it didn’t help.

    My second (even bigger) mistake was – I selected the “wipe” option from the fastboot screen.. Fool of a Took. ‘#$&%*!… I thought this would somehow help but instead I got stuck inside an infinite CWM loop. I had ADB access, but rebooting to fastboot using adb reboot-bootloader simply didn’t work. After ~6 hours of trying, I almost gave up at this point but after a bit more research I found a slightly different version of the same command – adb reboot bootloader (without the dash) and BINGO! That was my way back. So happy! Now I could boot into fastboot again.

    Well now I only had to figure out how to get rid of the CWM.. This SE answer stated that I should restore the device to defaults by flashing Asus stock ROM (download firmware for your language, unzip it and flash the *.blob file). Running fastbot erase before flashing the Asus firmware looked reeeally scary, but it was the only option I had so I went for it.. It all went great and after that it was very easy to flash TWRP and then CM and Gapps from TWRP.

    Problem solved! After ~8 hours of not giving up, my tablet was resurrected and alive again! Easter day of 2015. True story.

    Well, lesson learned – do the research and RTFM in advance. And I hope the post helps some other impatient bricker! :)

    PS – the device works faster/smoother/better with Lollipop. *thumbsup* for the Android team.

    Cheers!

  • Returning value from fragment into parent activity on Android

    Returning value from fragment into parent activity on Android

    This post will show you how to return a value set in a dialog fragment back to the parent activity it was called from. Code sample provided here is from the Auto-WOL app I made not so long ago, so if you need the bigger picture, feel free to check TimePickerFragment and DeviceActivity classes over at github. This example uses TimePickerDialog. It also keeps track of the layoutId (where the layout it is bound to acts as a button in the UI) because in my app I have multiple buttons which can invoke the same TimePickerFragment so I needed to know which button invoked the dialog fragment because upon selecting the time the button caption gets updated.

    auto-wol-fragment-activity

    So, to make this whole thing work, we will need a fragment class which will extend DialogFragment and implement TimePickerDialog.OnTimeSetListener. This class will expose the OnTimePickedListener interface so it could later be consumed by the parent activity. This class will also be responsible for registering and calling the callback on time set event. The defined OnTimePickedListener interface has only one method signature – onTimePicked(), which will need to be implemented by the parent activity. Other than this, the class only has a few more overrides and that’s all.

    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
    /**
     * This class is used so values from TimePickerFragment could be
     * returned back to the activity from which it was called.
     */

    public class TimePickerFragment extends DialogFragment implements TimePickerDialog.OnTimeSetListener {
        OnTimePickedListener mCallback;
        Integer mLayoutId = null;

        /**
         * An interface containing onTimePicked() method signature.
         * Container Activity must implement this interface.
         */

        public interface OnTimePickedListener {
            public void onTimePicked(int textId, int hour, int minute);
        }

        /* (non-Javadoc)
         * @see android.app.DialogFragment#onAttach(android.app.Activity)
         */

        @Override
        public void onAttach(Activity activity) {
            super.onAttach(activity);

            try {
                mCallback = (OnTimePickedListener)activity;
            } catch (ClassCastException e) {
                throw new ClassCastException(activity.toString() + " must implement OnTimePickedListener.");
            }
        }

        /* (non-Javadoc)
         * @see android.app.DialogFragment#onCreateDialog(android.os.Bundle)
         */

        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            mCallback = (OnTimePickedListener)getActivity();

            Bundle bundle = this.getArguments();
            mLayoutId = bundle.getInt("layoutId");
            int hour = bundle.getInt("hour");
            int minute = bundle.getInt("minute");

            // Create a new instance of TimePickerDialog and return it
            return new TimePickerDialog(getActivity(), this, hour, minute, DateFormat.is24HourFormat(getActivity()));
        }

        /* (non-Javadoc)
         * @see android.app.TimePickerDialog.OnTimeSetListener#onTimeSet(android.widget.TimePicker, int, int)
         */

        public void onTimeSet(TimePicker view, int hour, int minute) {
            if(mCallback != null)
            {
                mCallback.onTimePicked(mLayoutId, hour, minute);
            }
        }
    }

    The parent activity class implements the OnTimePickedListener defined in TimePickerFragment. This means we are requried to override the onTimePicked() method. This method is what gets executed after time has been picked in the dialog pop-up and “Done” confirmation button has been pressed. From here you’re back in your activity class and can do whatever you want with the obtained data.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class DeviceActivity extends BaseActivity implements OnTimePickedListener {    
        ...
       
        /**
         * On time picked event, converts hour and minutes values to milliseconds
         * milliseconds and sets a new value for the layout in the activity.
         * @param layoutId      QuietHoursFrom or QuietHoursTo layout Id.
         * @param hour          Hour value.
         * @param minute        Minutes value.
         */

        @Override
        public void onTimePicked(int layoutId, int hour, int minute) {
            Long timeInMillis = TimeUtil.getTimeInMilliseconds(hour, minute);
           
            // Here you can do whatever needed with value obtained from the fragment
        }
       
        ...
    }
  • 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!

Back to top