Friday, June 29, 2012

Automated Testing on Android

For my most recent project, a very large tablet app, I've been experimenting with the Android Testing Framework.  My piece of the app is the backend integration consisting of a number of different API calls that send and receive JSON, handle login and more. The app will utilize content providers to enable other apps to access the database.  I'm specifically using the test framework to exercise these content providers as well as the services that integrate with the backend.

While there is some overhead with learning the test framework. it's very useful. There's a few gotchas that slow down setting up your tests. A good one is why doesn't my test run?  Test methods need to start with the word test. So testMyInsertCode() will run while doMyInsertCodeTest() won't.  Content provider testing has some specific requirements to set up the mock statements as well.  Be sure when testing queries that you don't forget to put the =? on the end of the where clauses....

Monday, June 25, 2012

Android Service not found error

I was recently helping a developer debug some services when he ran into an error saying that the service was not found. You get this error if your manifest doesn't have the service registered. Be sure to check for typos in the service tag.


06-19 13:26:32.543: W/ActivityManager(306): Unable to start service Intent { act=com.yourapp.special.android.services.BigService }: not found


< service
            android:name=" com.yourapp.special.android.services.BigService "
            android:exported="true" >
           
               
           
       

Friday, June 22, 2012

Content Provider and Count queries


I'm creating a number of custom content providers for a client and on one of the screens we want to show the count of some of the tables.
  Now in sql world that would be easy, simply run a query that is

  select count(_id) from cartable

  But in content provider world, it's a bit harder. After stumbling around a bit playing with provider.call methods,
  Now just like you can tell the content provider to give you an instance of a car by id like this:

  content://find.me.a.car/car/123


  You can define a new URI format and use that to have the content provider's query method do something other than return a cursor that contains a
  list or a single row.  You can have it return a cursor that is a count.

  Here's our test method. Since it's a test, we're inserting a record first then getting the count of the table.  The URI would look something like

  content://find.me.a.car/car/COUNT


 
  public void testCountCall(){ 

         Log.d(TAG, "--------------testInsert()-------------"); 

     ContentProvider provider = getProvider(); 

     Log.d(TAG, "Running insert of new Car record"); 

     Car obj = createObject(); 

         mUriRInsert=provider.insert(ProviderHelper.determineURI(obj), obj.createContentValues()); 

        Log.d(TAG, "Content inserted at: " + mUriRInsert); 

        Log.d(TAG, "------- testinsert will complete successfully if the returned uri is not null"); 

     Uri uriCount = Uri.withAppendedPath(ProviderConstants.CAR_CONTENT_URI, "COUNT"); 

     Cursor c = provider.query( uriCount, null, null, null, null); 

     c.moveToFirst(); 

     int count = c.getInt(0); 

      Log.d(TAG, "new Count:" + count); 

      assertEquals(1, count);      

   }  

 
    --- NEXT STEP: Define a pattern for the COUNT in your provider
 
    The first two are standard, the third pattern will match the count.
 
 
 
  static 

        { 

             sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); 

             sUriMatcher.addURI(ProviderConstants.CAR_AUTHORITY, ProviderConstants.CAR_PATH, 

                 ProviderConstants.CP_TYPE_LIST); 

             sUriMatcher.addURI(ProviderConstants.CAR_AUTHORITY, ProviderConstants.CAR_PATH+"/#", 

                 ProviderConstants.CP_TYPE_ITEM); 

             sUriMatcher.addURI(ProviderConstants.CAR_AUTHORITY, ProviderConstants.CAR_PATH+"/COUNT", 

                 ProviderConstants.CP_TYPE_COUNT); 

      }  


----------- Third Step: Modify the query() method of the provider so it knows what to do with a URI that matches the COUNT


 @Override 

      public Cursor query(Uri uri, String[] projection, String selection, 

          String[] selectionArgs, String sortOrder) 

      { 

           Log.d(TAG, "uri:" +uri); 

           Log.d(TAG, "where:" +selection); 

           SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); 

           switch (sUriMatcher.match(uri)) 

           { 

           case ProviderConstants.CP_TYPE_LIST: 

                builder.setTables(TABLENAME); 

                builder.setProjectionMap(sDataProjectionMap); 

                break; 

           case ProviderConstants.CP_TYPE_ITEM: 

                builder.setTables(TABLENAME); 

                builder.setProjectionMap(sDataProjectionMap); 

                builder.appendWhere(DatabaseConstants.F__ID + " = " 

                    + uri.getPathSegments().get(1)); 

                break; 

           case ProviderConstants.CP_TYPE_COUNT: 

                builder.setTables(TABLENAME); 

                HashMap<String, String> countMap = new HashMap<String, String>(); 

                countMap.put("count", "count(*)"); 

                builder.setProjectionMap(countMap); 

                break; 

           default: 

                throw new IllegalArgumentException("Unknown URI: " + uri); 

           } 

           Cursor queryCursor = mDBHelper.runQueryBuilder(builder, projection, 

               selection, selectionArgs, null, null, null); 

           queryCursor.setNotificationUri(getContext().getContentResolver(), uri); 

           return queryCursor; 

      }  



Pretty straightforward and very handy.

Sunday, June 17, 2012

Android- Link to Google Store for Ranking


User reviews of Android Apps and rankings are the key metric by which Android apps are measured. I've added buttons to many of my apps to make it easy for users to create a review for my app from the app.  All you need is the url to the app and the code below.

// the package name as registered in the manifest.
public static final String URL_LINK_DEST = "http://market.android.com/search?q=pname:com.yourapp.appname.android";


 Button btnRate= (Button) findViewById(R.id.btnRateThisApp);
       btnRate.setVisibility(View.VISIBLE);
       btnRate.setClickable(false);
       btnRate.setOnClickListener(new View.OnClickListener(){

public void onClick(View v) {
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(GlobalConstants.URL_LINK_DEST));
    startActivity(i);  
}

});

Wednesday, June 13, 2012

Sending an SMS


Sending an SMS from your app is actually quite simple.  Note that once you add sms capabilities to your app, devices (like tablets) that don't have SMS can't install the app.



 String message= "Watson come here"; 

 String watsonsCell= "1234567890"; 

 SmsManager sms = SmsManager.getDefault();  

  if (message!=null) { 

   sms.sendTextMessage(watsonsCell, null, message, null, null); 

 }  



You need to add a permission to the AndroidManifest.



 <uses-permission android:name="android.permission.SEND_SMS"/>  



If you want to process an sms, you need the following permission, but it isn't necessary for sending.



 <uses-permission android:name="android.permission.RECEIVE_SMS"/>  


Sunday, June 10, 2012

Android Bug Report Database

A very useful resource is the official Android bug report database located at http://source.android.com/source/report-bugs.  This page allows developers to report bugs and search for known issues.  This is particularly useful for debugging problems reported for specific devices or for different API levels.  For example, I've used this to determine problems with JSON processing failure on a particular HTC device or problems debugging camera integration on Samsung tables and  differences between GMAIL support on api levels.  So check it out and book mark the page. Sooner or later it will come in handy!

Saturday, June 9, 2012

Starting Services on Boot


Most of the projects I work on have lots of backend integrations,some of which update data on a regular basis.  So I set up these integrations within a service that gets started when the device is turned on.  Pretty easy to do. I set up an array in the arrays.xml that has the full path to the service I want to start (packagename+servicename") so for example com.me.myproject.android.services.mygreatservice

The boot service gets the Broadcast message that Android sends out on startup, iterates through array of services and starts them one by one.

You'll need to add the permission below and register the receiver class in the manifest.



  <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
 <receiver android:name=".services.BootReceiver" >
       <intent-filter >
         <action android:name="android.intent.action.BOOT_COMPLETED" />
       </intent-filter>
     </receiver>      



 public class BootReceiver extends BroadcastReceiver { 
      private static final String TAG ="BootReceiver";
      /**
       * Service intent
       */
      public static Intent mIntent;
      @Override
      public void onReceive(Context context, Intent intent) {
           String[] servs= context.getResources().getStringArray(R.array.services);
            for (int i=0; i<servs.length; i++) {
                 Log.d(TAG, "Starting: "+servs[i]);
                     Intent mIntent = new Intent();                                
                     mIntent.setAction(servs[i]);
                     context.startService(mIntent);
            }
      }
 }  

Wednesday, June 6, 2012

Illegal Access Error and Referenced Projects

I've been writing a number of test classes for a very complex Android app. Recently I ran into a problem with a third party jar file misbehaving and throwing a java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation.  This is due to a class path problem. The solution is to reference like you normally would the jar in the source app and BE SURE TO EXPORT IT.(last tab in Eclipse properties-->Classpath


Then in the test app you MUST REFERENCE the project. Thanks to this blog for the excellent details on what to do!

http://blog.js-development.com/2010/06/android-instrumentation-test.html

Sunday, June 3, 2012

Deleting files from the SDCard

As part of a recent project, I enabled users to export data from the database to the file system by clicking a button.  Users then wanted an easy way to delete the files that they created.   This little bit of code below does just that. It looks in the standard Android download directory (where I'm saving the files) for all the files that end in testfile.txt (I'm naming files {_id}_{date}_testfile.txt) and then deletes them one by one..



 Button clearJsonFiles = (Button) findViewById(R.id.clearJsonFiles); 
           clearJsonFiles.setOnClickListener(new OnClickListener()
           {
                public void onClick(View v)
                {
                     File path = Environment
                         .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
                     File[] files = path.listFiles();
                     for (int i = 0; i < files.length; i++)
                     {
                          try {
                               Log.d(TAG, "Found File:" + files[i].getCanonicalFile());
                               if (files[i].getCanonicalPath().contains("testfile.txt")) {
                                    Log.d(TAG, "Deleting File:" + files[i].getCanonicalFile());
                                    //deleteExternalStoragePublicFile(files[i].getCanonicalPath());
                                    files[i].delete();
                               }
                          } catch (IOException e) {
                               // TODO Auto-generated catch block
                               e.printStackTrace();
                          }
                     }
                }
           });