Pages

Wednesday, October 21, 2015

Android regional indicator symbols, country flags emoji's

So, i had this problem, instead of country flags like on iPhone, my app showed symbols like "US", "GB", etc. I needed to show them in a TextView.

I found out that those regional indicator symbols are supported starting with android 5.0, but genymotion test on a 5.0 device showed that they are not fully working there.

The logic of the workaround is like this:

  1. get those symbols
  2. convert them to normal characters like "us", "gb".
  3. get an appropriate png image from raw folder
  4. show in textview using spannable string
Detailed description (huge thanks to the blogspot code formatting xD):

First i separated the text part and the symbols part of the string. There is this special character '\uD83C'  that comes before each regional indicator symbol, and you can split the string on it. 

Character sc = '\uD83C';
if (input.indexOf(sc) > -1){
    //input has special regional characters    String[] parts = input.split(sc.toString());    String text = parts[0];
    String[] flagchars = new String[parts.length - 1];
    for (int i = 1; i < parts.length; i++){
        flagchars[i - 1] = parts[i];    }
}

So now the flagchars array contains only the regional indicator symbols.

Next step is to parse those symbols to normal characters
String flagLetters = "";for (int i = 0; i < flagchars.length; i++){
    char ch = flagchars[i].charAt(0);    flagLetters = flagLetters + parseRegionalSymbolToLetter(ch);}

private static String parseRegionalSymbolToLetter(char c) {
    String result = "";
    switch (c){
        case '\uDDE6': result = "A"; break;        case '\uDDE7': result = "B"; break;        case '\uDDE8': result = "C"; break;        case '\uDDE9': result = "D"; break;        case '\uDDEA': result = "E"; break;        case '\uDDEB': result = "F"; break;        case '\uDDEC': result = "G"; break;        case '\uDDED': result = "H"; break;        case '\uDDEE': result = "I"; break;        case '\uDDEF': result = "J"; break;        case '\uDDF0': result = "K"; break;        case '\uDDF1': result = "L"; break;        case '\uDDF2': result = "M"; break;        case '\uDDF3': result = "N"; break;        case '\uDDF4': result = "O"; break;        case '\uDDF5': result = "P"; break;        case '\uDDF6': result = "Q"; break;        case '\uDDF7': result = "R"; break;        case '\uDDF8': result = "S"; break;        case '\uDDF9': result = "T"; break;        case '\uDDFA': result = "U"; break;        case '\uDDFB': result = "V"; break;        case '\uDDFC': result = "W"; break;        case '\uDDFD': result = "X"; break;        case '\uDDFE': result = "Y"; break;        case '\uDDFF': result = "Z"; break;    }

    return result;}

Then, i need to return a List of bitmaps to where im setting the text to TextView

List<Bitmap> bl = getFlagIcons(flagLetters, c);

private static List<Bitmap> getFlagIcons(String flagLetters, Context c) {
    List<Bitmap> bl = new ArrayList<>();
    String remainingLetters = flagLetters;
    int i = 1;
    while (!remainingLetters.isEmpty()){
        String letters = remainingLetters.substring(0, i);        String imageName = checkImageName(letters);        Bitmap flag = getBitmapFromRaw(imageName, c);
        if (flag == null){
            i++;        }else{
            remainingLetters = remainingLetters.substring(i, remainingLetters.length());            bl.add(flag);            i = 0;        }
    }

    return bl;}

This method works like this (sample input string "USGBCA"):
  1. takes first char, tries to find a bitmap in raw folder - in first iteration looks for u.png, 2nd - us.png
  2. if there is no such file, adds one more letter and repeats
  3. if a file is found - adds to List of bitmaps and starts over for next letters.
Returns list of bitmaps. Input "USGBCA" will result in a list of 3 bitmaps with flags of US, GB, CA. This method will also locate 1 char and 3 char countries.

The checkImageName method is there to modify file names for countries if needed

private static String checkImageName(String letters) {

    String result = letters.toLowerCase();
    switch (result){
        case "do": result = "_do"; break;    }

    return result;}

Because a do.png file cant exist because do is a keyword.

public static Bitmap getBitmapFromRaw(String filename, Context context) {
    int identifier = context.getResources().getIdentifier(filename, "raw", context.getPackageName());    Bitmap bmp = null;    if (identifier != 0) {
        InputStream is = context.getResources().openRawResource(identifier);        BufferedInputStream bis = new BufferedInputStream(is);        bmp = BitmapFactory.decodeStream(bis);    }
    return bmp;}



So now we have the List of bitmaps that we need to put inside a TextView.

Next method returns a SpannableString.

public static SpannableString getSpannableString(String text, List<Bitmap> images, Context context) {
    int iml = images.size();    String spaces = "";    for (int k = 0; k < iml; k++){
        spaces = spaces + "  ";    }

    SpannableString ss = new SpannableString(text + spaces);    int l = text.length();
    if (!images.isEmpty()) {
        for (int i = 0; i < iml; i++) {
            Bitmap b = images.get(i);
            Drawable d = new BitmapDrawable(context.getResources(), b);            float scale = context.getResources().getDisplayMetrics().density * 1.3f;            float w = d.getIntrinsicWidth() * scale;            float h = d.getIntrinsicHeight() * scale;            d.setBounds(0, 0, (int) w, (int) h);            ImageSpan is = new ImageSpan(d, ImageSpan.ALIGN_BASELINE);            ss.setSpan(is, l + i * 2, l + i * 2 + 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);        }
    }

    return ss;}

Which is then set to a textview

text.setText(ss);

And ofcourse you need to get flag pngs inside of your res/raw folder. I got mine from here http://www.famfamfam.com/lab/icons/flags/


Monday, September 14, 2015

JavaScript nudity detection script

http://stackoverflow.com/questions/713247/what-is-the-best-way-to-programatically-detect-porn-images

I found a python script in this SO answer, and adapted it to JavaScript. It counts skin colored pixels and compares to total pixel count.

Input - image data;

//nudity check script
function(data) {
var length = data.length;
var skinPixels = 0;
var totalPixels = 0;

for (var i = 0, n = length; i < n; i += 4) {
//i+3 is alpha (the fourth element)
    var r = data[i]; 
    var g = data[i+1]; 
    var b = data[i+2]; 

    totalPixels = totalPixels + 1;

    if ((r > 60) && (g < (r * 0.85)) && (b < (r * 0.7)) && (g > (r * 0.4)) && (b > (r * 0.2))) {
    skinPixels = skinPixels + 1;
    }
}

var isPorn = false;
var ratio = (skinPixels / totalPixels);

if (ratio > 0.0744) {
isPorn = true
}

// console.log("skin:" + skinPixels + ", total:" + totalPixels + ", ratio:" + ratio);

return isPorn;
}

Sample results are like this:


isPorn:true



isPorn:true



isPorn:false



isPorn:false



isPorn:false




Thursday, September 10, 2015

android M, sdk 23, proguard, apache library errors

Hi!

I've spent last one and a half days banging my head against the table because i could not build a signed apk file with proguard, because it had some errors, similar to this:


  • cant find superclass or interface org.apache...
  • cant find referenced class org.apache...

While debug builds and apk generation works without proguard, enabling proguard produces this error.

What i understood from this:

  • google removed apache library from android sdk 23. While the classes itself are still there, they were removed from some kind of index
  • thats why in debug modes everything works, but with enabled proguard proguard obfuscates apache classes making them unusable by some other libraries those are referencing apache classes
  • it also became clear that no additional jar files or dependencies are needed, because your debug modes works ok, just the correct settings for proguard are needed. (im talking about google's advice to add apache legacy library)
The problematic classes those i seen in error logs were:
  1. org.apache.http.*
  2. android.net.http.*
Also when i made a build my app crashed because of support library obfuscations (i could not open the preference activity, i checked stack trace from emulator and seen that support libraries tried to reference obfuscated classes/methods).

Summarising, everything was fixed and working on release apk with the next lines in my proguard-rules.pro file:


-keep class org.apache.http.** { *; }
-dontwarn org.apache.http.**

-keep class android.net.http.** { *; }
-dontwarn android.net.http.**

-keep class android.support.v7.** { *; }
-keep class android.support.v4.** { *; }

Friday, August 28, 2015

Passing data from notification click event to activity android

A lot of time spend until i made it work!

I had lots of notifications and all they should trigger different events in the activity.

The data passed through intent's putExtra methods was not reliable, because of way that intent works. Some extras did not change.

The solution i ended up with is like this:

1) In my custom receiver:
Intent intent = new Intent(null, Uri.parse("some data"), context, MainActivity.class);intent.putExtra("from_notification", true);intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

Create notification here. Btw logo should be set like this, because lollipop and higher logo must be only from white color:
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
    iconId = R.raw.logo_white;}
else{
    iconId = R.raw.logo;}

mBuilder.setContentIntent(contentIntent);




My MainActivity:
@Overrideprotected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    boolean fromNotification = intent.getBooleanExtra("from_notification", false);
    if (fromNotification){
        String someData = intent.getData().getSchemeSpecificPart();        //some data is the unique string for each notification
    }
}

Also you should call  the method from onCreate, for cases when activity is not yet running when you tap the notification:

onNewIntent(getIntent());

And dont forget to register the receiver in manifest file with appropriate filters.

Saturday, August 22, 2015

android SQLite best practices (how to SQLite, android sqlite guide tutorial)

Some info i feel obliged to persist.

Had lots of problems and knowledge gaps with android SQLite database, now all seems to fit in its places.


I will share the approach i came to in a few months time of debugging and error fixing.


1) To work with android SQLite you must create your own helper class extending SQLiteOpenHelper.


2) You should use a singleton pattern for this helper instance and get the instance where needed in Activity's onCreate method:

public static synchronized DBHelper getInstance(Context context) {
    // Use the application context, which will ensure that you
    // don't accidentally leak an Activity's context.       
    if (sInstance == null) {
        sInstance = new DBHelper(context.getApplicationContext());    
    }
    return sInstance;
}
3) Before each db interaction (insert, delete, etc) open the database: 
db = this.getWritableDatabase();

4) The helper class should have methods for fetching/persisting data. Methods using cursor must close the cursor after using it. Never close the database itself in this class.


5) Use transaction mode for looped interactions:

db = this.getWritableDatabase();
db.beginTransaction();
try {
    for (...) {
        db.insert(...);
    }

    db.yieldIfContendedSafely();    
    db.setTransactionSuccessful();
}catch(Exception exc){
    exc.printStackTrace();
}finally {
    db.endTransaction();
}
6) My approach to Cursors/queries:
db = this.getWritableDatabase();
Cursor c = db.rawQuery("Select * from TABLE where ID = ?",new String[] {id});
c.moveToPosition(-1);
while (c.moveToNext()) {
    String s = c.getString(c.getColumnIndex("col_name"));
}
c.close();
7) onUpgrade method - when you increment the database version, this method is  called. Mine looks like this:
public static final int DATABASE_VERSION = 238;
@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    dropAll(db); 
    makeDB(db);
}

private SQLiteDatabase dropAll(SQLiteDatabase db){
    db.execSQL("DROP TABLE IF EXISTS tasks");
    return db;
}
public static void makeDB(SQLiteDatabase db){
    db.execSQL(CREATE_TABLE_TASKS);
}
private static final String CREATE_TABLE_TASKS = "CREATE TABLE IF NOT EXISTS tasks(user VARCHAR, ID INTEGER PRIMARY KEY AUTOINCREMENT);";
8) onCreate method:
@Overridepublic void onCreate(SQLiteDatabase db) {
    makeDB(db);
}

9) Working with database is very fast, performance drops might be caused by something else usually, so put that code in AsyncTask.

Monday, August 3, 2015

Anonymous messenger Stealthy.ms

Hi!

Last months i'm developing android version of the already popular iphone app Anonymous messenger "Stealthy". (http://stealthy.ms or http://smsSekret.ru)

The initial idea was to enable people to open their hearts to loved ones, anonymously. Or maybe send secret insides to your business partners, or any other kind of 2 way conversation where you wish to remain anonymous.

The realisation came as ability of the app was to send anonymous SMS to anyone from your contact list, and if they download the app they can answer to your anonymous sms, not knowing who it is. You will know who you are talking to, but not they, they will see you as "Secret Friend".

This app acts just like a simple chat/irc, but you are anonymous! There are group chats with people from your locations (also lots of group chats on different topics - cars/games/ etc), where you can troll everybody or even find new friends.

Also a chat roulette functionality is available, where you will be connected to a random active user of the other gender.

Saturday, April 25, 2015

android sqlite made easy tutorial

Hi!

Start using SQLite in your android app is very easy! SQLite is supported from start for any android app, you dont need to configure anything. There are some frameworks, but i found easier to use without them. Also using frameworks have some litimations, for example currently frameworks do not support encryption of the database.

Also check my second post about SQLite: http://tocrva.blogspot.com/2015/08/android-sqlite-best-practices-how-to.html

1) private field in the activity and add the needed imports:

private SQLiteDatabase db;

2) In onCreate method:

db = openOrCreateDatabase("dbname.db", MODE_PRIVATE, null);

As you might understand, this creates or opens a database named dbname.db in private mode. Private mode means that only your app will have access to this database. Note that the database itself is persisted in text file form and can be accesses easily from a rooted device or a emulator.

3) Now you must create a table inside your database:

db.execSQL("CREATE TABLE IF NOT EXISTS mytable (column1 VARCHAR, column2 VARCHAR);");

This also is self explanatory. Standart SQL sintax. The execSQL method is used to execute sql queries that do not return anything.

4) Inserting values, the best practice using ContentValues (inside try/catch):

ContentValues cv = new ContentValues();
cv.put("column1", "some value");
db.insert("mytable", null, cv);

5) Fetching values. You can provide a static query in the first argument of rawQuery method, or use array of arguments new String[]{"arg1", "arg2", "arg3"}, those will be put in places of "?" in the query:

Cursor c = db.rawQuery("select * from mytable where column1 = ?", new String[]{"some value"});

c.moveToPosition(-1);
while (c.moveToNext()) {
    String column1value = c.getString(0);
}
c.close();

6) Performance optimisation for cycled operations:

db.beginTransaction();
try {
    for(){
        //some insert query
    }
    db.yieldIfContendedSafely();
    db.setTransactionSuccessful();
}catch(Exception exc){
    exc.printStackTrace();
}
finally {
    db.endTransaction();
}

7) A good practice would be to close the db when you will not use it anymore, for example in activity's onPause method:

db.close();