Sunday, December 29, 2013

java.lang.IllegalArgumentException: Can't pass bindargs for this sql :insert into....

I've optimized one of my Android apps to use compiled statement (SQLiteStatement) instead of direct SQL to improve performance.

After doing so, I started to get some unhandled exception reports, which had the following in common: Android 4.0.3, Turkish locale and the following stack trace:
java.lang.IllegalArgumentException: Can't pass bindargs for this sql :insert into android.database.sqlite.SQLiteProgram.compileAndbindAllArgs(SQLiteProgram.java)
android.database.sqlite.SQLiteStatement.acquireAndLock(SQLiteStatement.java)
android.database.sqlite.SQLiteStatement.executeUpdateDelete(SQLiteStatement.java)
android.database.sqlite.SQLiteStatement.execute(SQLiteStatement.java)
 
Solution: When using SQLiteStatement write SQL in uppercase letters.

Reason: After searching for some time I found this issue. The SQLiteStatement checks the type of the SQL inside. The checking process is simple string compare of the SQL prefix (INSERT/UPDATE/SELECT/etc..). The string compare in Android 4.0.3 seem to be culture dependent, and in Turkish locale uppercase 'insert' translate to '─░NSERT' (note the dot above the Turkish I') which gives an error in SQL.

Wednesday, December 4, 2013

Notes on using Google Play Game Services

Here are few tips & tricks that helped me [on going post]:

Google Drive: This spreadsheet contains too much content and can no longer be edited

I got this error ("This spreadsheet contains too much content and can no longer be edited") while using ACRA reporting to Google Drive (legacy). In general you should prevent your spreadsheet from reaching the maximum limits (see this for ACRA example). Once you get this error - you can't clean the spreadsheet anymore.

Solution: It's a bypass, not a real solution - click on "File" => "See revision history". Select a revision that should work and click "Restore this revision". You'll lose your latest information, but the spreadsheet will be writable. From now on make sure you're not getting anything close to the limits.

Wednesday, May 22, 2013

How to open calendar intent programmatically

I was looking around for a way to open the calendar application for viewing. The closest solution I found was running the stock calendar app explicitally using the package name.

Here's the best solution I came up with to open for viewing the Android calendar:
Intent i = new Intent(Intent.ACTION_VIEW);
// Android 2.2+
i.setData(Uri.parse("content://com.android.calendar/time"));  
// Before Android 2.2+
//i.setData(Uri.parse("content://calendar/time"));  

startActivity(i);

Wednesday, May 15, 2013

Error after updating Android's ADT 22 plug-in on Eclipse

After updating to the latest ADT version on Eclipse Juno, Windows 7, I got the following error:
 
Error Loading the SDK:

Error: Error parsing the sdk.
Failed to create C:\Program Files (x86)\Android\android-sdk\build-tools

Solution: To my best of understanding the ADT plugin is trying to change files in location the current user can't. I'm sure there are better solutions, but this is the quickest:
1. Go to folder 'C:\Program Files (x86)\Android\'
2. Right click on 'android-sdk' => 'Properties' => 'Security'
3. Click on 'Users' and then 'Edit', and add 'Full Control' and hit 'OK'

After Windows finish changing the permissions, restart Eclipse.

Saturday, March 9, 2013

BitmapFactory.decodeResource performance

While checking the performance of my app I've noticed BitmapFactory.decodeResource is relatively slow. So I've checked the alernative, instead of loading my PNG files as resources, putting them in /assets/ folder and loading using BitmapFactory.decodeStream.

Here are the test functions:

void decodeResource() {
 Bitmap originalBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.tile, null);
 originalBitmap.recycle();
}
void decodeStream() {
 InputStream ins = null;
 try {
  ins = getAssets().open("tile.png");

  Bitmap originalBitmap = BitmapFactory.decodeStream(ins);
  originalBitmap.recycle();
 } catch (final IOException e) {
  e.printStackTrace();
 } finally {
  if (ins != null)
   try {
    ins.close();
   } catch (IOException e) { }
 }     
}

Running both functions 50 times to load a small PNG file (230*230) on Nexus Galaxy running Android 4.2.2:
  • decodeResource: 1793ms 
  • decodeStream: 188ms 
Conclusion: decodeStream is almost x10 faster than decodeResource.

Tuesday, February 12, 2013

Chartboost SDK & Proguard

UPDATE: This post (both issues) was made obsolete by Chartboost SDK 3.1.5. Follow the integration instructions supplied by Chartboost.

When using Chartboost SDK 3.1.3, ProGuard fails to compile with few warnings. To bypass this issue add the following lines to proguard.cfg:
# Chartboost
-dontwarn com.mongodb.tools.ConnectionPoolStat
-dontwarn com.mongodb.util.management.jmx.JMXMBeanServer
-dontwarn org.bson.types.ObjectId

IMPORTANT: If you're using mediation you might need to add more ProGuard exceptions.

Also, as of Chartboost Android SDK 3.1.3 there's a bug which prevent clicking Chartboost ads on Android 3.0+ (it will hang during the "Loading..." stage after the user clicks an ad). In the logcat the following exception appears: android.os.NetworkOnMainThreadException

Until Chartboost will fix it, just add the following patch to the onCreate method:
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy); 
Note: this requires Android Platform SDK 9+

Sunday, February 10, 2013

Switching users when using Team Foundation Service & Visual Studio

I encountered this problem using Visual Studio 2010, but it might also apply to Visual Studio 2012, Eclipse & Team Foundation Everywhere. It's relevant only to the Team Foundation Service, not Server (for the server, the secret is to clear the "Credentials Manager").

I wanted to switch users, but I could not. Long story short, when I tried to log out I was automatically logged in, when I tried to open my other TFS site I got error TF31003 with the "Use different user" link missing.

The solution:
  1. Close all programs.
  2. Open "Internet Explorer".
  3. Click on "Settings" (the wheel on the right side in my version of IE).
  4. Under "Browsing History", click on "Delete", select "Passwords" and delete.
  5. Next time, NEVER EVER click on "Sign me in next time" in any of Microsoft's services.

Sunday, January 6, 2013

Advanced ACRA 3: Auto delete old records

One issue with using Google Spreadsheet for ACRA reports is the size limitation of Google Spreadsheet. Specifically, the maximum size of Google Spreadsheet is 400K cells (which translates to 11K reports as of ACRA 4.2.3). More over, if the Google Spreadsheet become too large it simply won't open (there are multiple post of people saying they just can't open large ACRA Spreadsheets).

To avoid this limitation - automatically delete old ACRA reports:
1. Add the following Google Apps Script to your ACRA Google Spreadsheet ("Tools" -> "Script Editor" -> "Blank Project").
2. Schedule the script to run once a day using Time-Driven trigger (in the Script Editor, "Resources"-> "Current script's triggers").

I've extended the script to:
*. remove old versions entries so I could focus on the latest version only, just adjust the version number parameter.
*. Remove invalid entries (I think that was solved in later version of ACRA).



function deleteObsoleteRows() {  
  // Application specific
  //  // https://developers.google.com/apps-script/class_spreadsheetapp#openById
  var SPREAD_SHEET_KEY = "ENTER YOUR KEY HERE";
  //  // Delete any recoprd below this version 
  var APP_VERSION_CODE_CURRENT = 0;

  // ACRA 4.2.3 version specific - report cell locations
  var APP_VERSION_CODE = 2;

  // Maximum records per spreadsheet - if there are more - delete the oldest records
  // Google Spreadhseet over too many records is not useable
  var MAX_RECORDS = 10000;
    
  // For details see 
  // https://developers.google.com/apps-script/class_spreadsheetapp#openById
  var sheet = SpreadsheetApp.openById(SPREAD_SHEET_KEY);
  //var sheet = SpreadsheetApp.getActiveSheet();
  
  var rows = sheet.getDataRange();
  var numRows = rows.getNumRows();
  var values = rows.getValues();

  // Delete invalid records
  for (var i = numRows - 1; i >= 1; i--) {
    var row = values[i];
    
    // Logger.log(row);
    
    // Delete empty APP_VERSION_CODE
    if (row[APP_VERSION_CODE] == "") 
      sheet.deleteRow(i+1);
    // Delete old version records
    else if (row[APP_VERSION_CODE] < APP_VERSION_CODE_CURRENT) 
      sheet.deleteRow(i+1);
  }
  
  // Delete the oldest records if there are too many records
  for (var i = numRows - MAX_RECORDS; i > 0; i--) {
    sheet.deleteRow(2);
  }
};