Monday, December 26, 2011

SAMSUNG Galaxy S ICS Petition!

Some angry Samsung Galaxy S users have done petition to get Samsung to give the GS Android4.0 Ice Cream Sandwich, its already 8000+ signatures strong and growing:

http://change.org/petitions/wwwsamsungcom-distribution-of-ice-cream-sandwich-version-for-users-of-the-samsung-i9000

 Please sign it if you believe SAMSUNG made the wrong decision when leaving the Galaxy i900x devices out of their ICS update roadmap.

Monday, December 19, 2011

How to use the raw resource files in the native code


Russian translation. Pending...


As you know the Android application is packed to the ".apk" container and all resources of the application are there. But what if you want to use a resource file inside the native code? Sure, you may move it to the external card and do with it all what you want. But it seems to be a slightly clumsy way. Really, I want to treat a raw resource file as it was an ordinary file stream. What we have at start: java code able to open and work with raw data files. Here is the sample code:
    
    AssetFileDescriptor descriptor = context.getResources().
        openRawResourceFd(R.raw.rawFile);
    FileDescriptor fd = desc.getFileDescriptor();
Let's look at the documentation:

Instances of the file descriptor class serve as an opaque handle to the underlying machine-specific structure representing an open file, an open socket, or another source or sink of bytes.
So it is what we are looking for. Moreover from the "AssetFileDescriptor" we can get the length of the file and its offset in the "apk" file.
    long offset = descriptor.getStartOffset();
    long lenth = descriptor.getLength();
And, at last, how to get the real native file descriptor:
    try {
        Field field = fd.getClass().getDeclaredField("descriptor");
        field.setAccessible(true);
        nativeDescriptor = (Integer) field.get(fd);
    } catch (NoSuchFieldException e1) {
    } catch (IllegalArgumentException e) {
    } catch (IllegalAccessException e) {
    }
Again, let's look at the documentation about "raw" files:

Arbitrary files to save in their raw form
So, they are saved as is - without compression and we can read them. Now we may go to the native part with following entry data:

1. The native file descriptor of the "apk" file.
2. The Offset of the "raw" file.
3. The length of the "raw" file.

The base idea is to map this part of the "apk" file into a memory and use the "fmemopen" function to get the "FILE" structure. Android doesn't support the "fmemopen" function, but you can find its implementation here:

fmemopen.c

And at last:
    int desc = dup(fd);
    long pa_offset = off & ~(sysconf(_SC_PAGE_SIZE) - 1);

    void *mapped = mmap(NULL, len + off - pa_offset, PROT_READ,
   MAP_PRIVATE, desc, pa_offset);
    if (mapped == MAP_FAILED) {
        ... error handling...
    }

    FILE *memoFile = fmemopen((void*) (mapped + off - pa_offset), len, "r");
    if (memoFile == NULL){
        ... error handling...
    }
And don't forget to free resources...

Tuesday, December 13, 2011

Source code

The source code for all articles was moved to the Google Code. There are links for articles: 3D Carousel Demo:
3D Carousel


ImageView with SVG support:
SVG ImageView


All other articles are combined into one project:
Android.Tips


Links in articles are updated too.

Monday, December 5, 2011

Using a mask with EditText



Source Code

Русский перевод.


To continue the theme about formatting a text with regular expressions we will
implement a functionality for using a mask with the EditText control.

Sure we can use InputType, but it would be nice to have more flexible functionality.


To begin let's look at the MaskFormatter class of the Swing framework.
It has all what we need. So, let's make some changes in this class.
We leave inner classes as is. But the MaskFormatter class works with
the JFormattedTextField control. We must remove this appendix.
As a result we have something like this:
public class MaskedFormatter {
 
    // Potential values in mask.
    private static final char DIGIT_KEY = '#';
    private static final char LITERAL_KEY = '\'';
    private static final char UPPERCASE_KEY = 'U';
    private static final char LOWERCASE_KEY = 'L';
    private static final char ALPHA_NUMERIC_KEY = 'A';
    private static final char CHARACTER_KEY = '?';
    private static final char ANYTHING_KEY = '*';
    private static final char HEX_KEY = 'H';
    
    /** The user specified mask. */
    private String mask;    
    
    /** Indicates if the value contains the literal characters. */
    private boolean containsLiteralChars;
    
    private static final MaskCharacter[] EmptyMaskChars = 
           new MaskCharacter[0];

    /** List of valid characters. */
    private String validCharacters;
    
    /** List of invalid characters. */
    private String invalidCharacters;

    /** String used to represent characters not present. */
    private char placeholder;  
    
    /** String used for the passed in value if it does not completely
     * fill the mask. */
    private String placeholderString;    
    
    private transient MaskCharacter[] maskChars;
    
    
    /** Indicates if the value being edited must match the mask. */
    @SuppressWarnings("unused")
 private boolean allowsInvalid;
    
    
    /**
     * Creates a MaskFormatter with no mask.
     */
    public MaskedFormatter() {
        setAllowsInvalid(false);
        containsLiteralChars = true;
        maskChars = EmptyMaskChars;
        placeholder = ' ';
    }

    /**
     * Creates a MaskFormatter with the specified mask.
     * A ParseException
     * will be thrown if mask is an invalid mask.
     *
     * @throws ParseException if mask does not contain valid mask characters
     */
    public MaskedFormatter(String mask) throws ParseException {
        this();
        setMask(mask);
    }    
    
    /**
     * Sets the mask dictating the legal characters.
     * This will throw a ParseException if mask is
     * not valid.
     *
     * @throws ParseException if mask does not contain valid mask characters
     */
    public void setMask(String mask) throws ParseException {
        this.mask = mask;
        updateInternalMask();
    }
    
    /**
     * Returns the formatting mask.
     *
     * @return Mask dictating legal character values.
     */
    public String getMask() {
        return mask;
    }    
    
    /**
     * Updates the internal representation of the mask.
     */
    private void updateInternalMask() throws ParseException {
        String mask = getMask();
        ArrayList<MaskCharacter> fixed = new ArrayList<MaskCharacter>();
        ArrayList<MaskCharacter> temp = fixed;

        if (mask != null) {
            for (int counter = 0, maxCounter = mask.length();
                 counter < maxCounter; counter++) {
                char maskChar = mask.charAt(counter);

                switch (maskChar) {
                case DIGIT_KEY:
                    temp.add(new DigitMaskCharacter());
                    break;
                case LITERAL_KEY:
                    if (++counter < maxCounter) {
                        maskChar = mask.charAt(counter);
                        temp.add(new LiteralCharacter(maskChar));
                    }
                    // else: Could actually throw if else
                    break;
                case UPPERCASE_KEY:
                    temp.add(new UpperCaseCharacter());
                    break;
                case LOWERCASE_KEY:
                    temp.add(new LowerCaseCharacter());
                    break;
                case ALPHA_NUMERIC_KEY:
                    temp.add(new AlphaNumericCharacter());
                    break;
                case CHARACTER_KEY:
                    temp.add(new CharCharacter());
                    break;
                case ANYTHING_KEY:
                    temp.add(new MaskCharacter());
                    break;
                case HEX_KEY:
                    temp.add(new HexCharacter());
                    break;
                default:
                    temp.add(new LiteralCharacter(maskChar));
                    break;
                }
            }
        }
        if (fixed.size() == 0) {
            maskChars = EmptyMaskChars;
        }
        else {
            maskChars = new MaskCharacter[fixed.size()];
            fixed.toArray(maskChars);
        }
    }    
    
    
    /**
     * Sets whether or not the value being edited is allowed to be invalid
     * for a length of time (that is, stringToValue throws
     * a ParseException).
     * It is often convenient to allow the user to temporarily input an
     * invalid value.
     *
     * @param allowsInvalid Used to indicate if the edited value must always
     *        be valid
     */
    public void setAllowsInvalid(boolean allowsInvalid) {
        this.allowsInvalid = allowsInvalid;
    }    


    /**
     * Allows for further restricting of the characters that can be input.
     * Only characters specified in the mask, not in the
     * invalidCharacters, and in
     * validCharacters will be allowed to be input. Passing
     * in null (the default) implies the valid characters are only bound
     * by the mask and the invalid characters.
     *
     * @param validCharacters If non-null, specifies legal characters.
     */
    public void setValidCharacters(String validCharacters) {
        this.validCharacters = validCharacters;
    }

    /**
     * Returns the valid characters that can be input.
     *
     * @return Legal characters
     */
    public String getValidCharacters() {
        return validCharacters;
    }
 
    /**
     * Allows for further restricting of the characters that can be input.
     * Only characters specified in the mask, not in the
     * invalidCharacters, and in
     * validCharacters will be allowed to be input. Passing
     * in null (the default) implies the valid characters are only bound
     * by the mask and the valid characters.
     *
     * @param invalidCharacters If non-null, specifies illegal characters.
     */
    public void setInvalidCharacters(String invalidCharacters) {
        this.invalidCharacters = invalidCharacters;
    }

    /**
     * Returns the characters that are not valid for input.
     *
     * @return illegal characters.
     */
    public String getInvalidCharacters() {
        return invalidCharacters;
    }    
    
    /**
     * If true, the returned value and set value will also contain the literal
     * characters in mask.
     * 
     * For example, if the mask is '(###) ###-####', the
     * current value is '(415) 555-1212', and
     * valueContainsLiteralCharacters is
     * true stringToValue will return
     * '(415) 555-1212'. On the other hand, if
     * valueContainsLiteralCharacters is false,
     * stringToValue will return '4155551212'.
     *
     * @param containsLiteralChars Used to indicate if literal characters in
     *        mask should be returned in stringToValue
     */
    public void setValueContainsLiteralCharacters(
                        boolean containsLiteralChars) {
        this.containsLiteralChars = containsLiteralChars;
    }

    /**
     * Returns true if stringToValue should return literal
     * characters in the mask.
     *
     * @return True if literal characters in mask should be returned in
     *         stringToValue
     */
    public boolean getValueContainsLiteralCharacters() {
        return containsLiteralChars;
    }    
    
    /**
     * Sets the character to use in place of characters that are not present
     * in the value, ie the user must fill them in. The default value is
     * a space.
     * 
     * This is only applicable if the placeholder string has not been
     * specified, or does not completely fill in the mask.
     *
     * @param placeholder Character used when formatting if the value does not
     *        completely fill the mask
     */
    public void setPlaceholderCharacter(char placeholder) {
        this.placeholder = placeholder;
    }

    /**
     * Returns the character to use in place of characters that are not present
     * in the value, ie the user must fill them in.
     *
     * @return Character used when formatting if the value does not
     *        completely fill the mask
     */
    public char getPlaceholderCharacter() {
        return placeholder;
    }
    
    /**
     * Sets the string to use if the value does not completely fill in
     * the mask. A null value implies the placeholder char should be used.
     *
     * @param placeholder String used when formatting if the value does not
     *        completely fill the mask
     */
    public void setPlaceholder(String placeholder) {
        this.placeholderString = placeholder;
    }

    /**
     * Returns the String to use if the value does not completely fill
     * in the mask.
     *
     * @return String used when formatting if the value does not
     *        completely fill the mask
     */
    public String getPlaceholder() {
        return placeholderString;
    }    
    /**
     * Returns a String representation of the Object value
     * based on the mask.  Refer to
     * {@link #setValueContainsLiteralCharacters} for details
     * on how literals are treated.
     *
     * @throws ParseException if there is an error in the conversion
     * @param value Value to convert
     * @see #setValueContainsLiteralCharacters
     * @return String representation of value
     */
    public String valueToString(Object value) throws ParseException {
        String sValue = (value == null) ? "" : value.toString();
        StringBuilder result = new StringBuilder();
        String placeholder = getPlaceholder();
        int[] valueCounter = { 0 };

        append(result, sValue, valueCounter, placeholder, maskChars);
        return result.toString();
    }    
    
    /**
     * Invokes append on the mask characters in
     * mask.
     */
    private void append(StringBuilder result, String value, int[] index,
                        String placeholder, MaskCharacter[] mask)
                          throws ParseException {
        for (int counter = 0, maxCounter = mask.length;
             counter < maxCounter; counter++) {
            mask[counter].append(result, value, index, placeholder);
        }
    }    



And to simplify the life we will create a TextWatcher class to work with the formatter:
 public class MaskedWatcher implements TextWatcher {
 
 private String mMask;
 String mResult = ""; 
 
 public MaskedWatcher(String mask){
  mMask = mask;
 }

 @Override
 public void afterTextChanged(Editable s) {
  
  String mask = mMask;
  String value = s.toString();
  
  if(value.equals(mResult))
   return;

  try {
   
   // prepare the formatter
   MaskedFormatter formatter = new MaskedFormatter(mask);
   formatter.setValueContainsLiteralCharacters(false);
   formatter.setPlaceholderCharacter((char)1);
   
   // get a string with applied mask and placeholder chars
   value = formatter.valueToString(value);
   
   try{
    
    // find first placeholder
    value = value.substring(0, value.indexOf((char)1));

    //process a mask char
    if(value.charAt(value.length()-1) == 
                                      mask.charAt(value.length()-1)){
     value = value.substring(0, value.length() - 1);
    }
    
   }
   catch(Exception e){}
   
   mResult = value;
   
   s.replace(0, s.length(), value);
   
   
  } catch (ParseException e) {
   
   //the entered value does not match a mask
   int offset = e.getErrorOffset();
   value = removeCharAt(value, offset);
   s.replace(0, s.length(), value);
   
  }
  
  
 }

 @Override
 public void beforeTextChanged(CharSequence s, int start, int count,
   int after) {
 }

 @Override
 public void onTextChanged(CharSequence s, int start, 
             int before, int count) {
 }

 public static String removeCharAt(String s, int pos) {

  StringBuffer buffer = new StringBuffer(s.length() - 1);
  buffer.append(s.substring(0, pos)).append(s.substring(pos + 1));
  return buffer.toString();

 } 
 
}


And now we can work with masks:
        EditText phone = (EditText)findViewById(R.id.phone);
        phone.addTextChangedListener(
          new MaskedWatcher("(###) ###-##-##")
        )