Monthly Archives: May 2011

Advanced topics for Android developers

Google I/O talk notes.

Dealing with various APIs and Android versions.
Coding patterns.

How to deal with various device capabilities.

private static boolean isNewAPI =
   android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Intent intent;
    if (isNewAPI) {
        intent = new Intent(this, ModernActivity.class);
    } else {
        intent = new Intent(this, LegacyActivity.class);
    }
    startActivity(intent);
    finish();
}

Sensor registration example (Orientation Sensor).

private static boolean isNewSensorAPI =
   android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.CUPCAKE;

boolean gyroExists = getPackageManager().hasSystemFeature(
  PackageManager.FEATURE_SENSOR_GYROSCOPE);

IOrientationSensorListener myListener;
if (gyroExists) {
    myListener = new GyroOrientetationSensorListener();
} else if (isNewSensorAPI) {
    myListener = new AccOrientationSensorListener();
} else {
    myListener = newAccOldOrientationSensorListener();
}

myListener.setOrientationChangeListener(myOCListener);

Getting users feedback

try {

} catch (Exception ex) {
   String exText = "something went wrong";
   MyApplication.getInstance().tracker().trackPageView(exText);
}

A – B user testing on Beta deployment. Getting real users feedback on different possible scenarios/visuals/etc.

private static final boolean isA =
UUID.randomUUID().getLeastSiginifcantBits() % 2 == 0;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if(isA) {
       setContentView(R.layout.mainA);
       MyApp.getInstance().tracker().trackPageView("/AUser");
    } else {
       setContentView(R.layout.mainB);
       MyApp.getInstance().tracker().trackPageView("/BUser");
    }
}

Using Android Market for beta testing:
- private betas that require login or passcode
- public betas with disguised or obscured listing
- update listing or create new on launch
- point beta users to official app at launch
- be clear that this is beta. venues for feedback
- make sure you translated your app name and description to local languages

Other hints:
- upload your package before distributing it to anyone
- backup your keystore
- use generic gmail account for app releases (sharing the responsibilities)

Sensor orientation tip

int x = AXIS_X;
int y = AXIS_Y;

case (Display.getRotation()):
    Surface.ROTATION_0: break;
    Surface.ROTATION_90: x = AXIS_Y; y = AXIS_MINUS_X; break;
    Surface.ROTATION_180: y = AXIS_MINUS_Y; break;
    Surface.ROTATION_270: x = AXIS_MINUS_Y; y = AXIS_X; break;
    default: break;
}
SensorManager.remapCoordinateSystem(inR, x_axis, y_axis, outR);

Generating unique identifier (per user/device). Use the following method of generating the unique id and store it in users’s preferences.

UUID. randomUUID().toString().

How to get the up-to-date location without draining the battery?
Use passive location provider, or, alternatively, use Location change intent.

final int resultCode = 0;
final String locAction = "com.ioApp.LOCATION_UPDATE_RECEIVED";
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
Intent intent = new Intent(locAction);
PendingIntent pi = PendingIntent.getBroadcast(this, resultCode, intent, flags);
locationManager.requestLocationUpdates(provider, minTime, minDistance, pi);

....

BroadcastReceiver locReceiver = new BroadcastReceiver() {
  @Override
  public void onReceive(Context ctx, Intent intent) {
    String key = LocationManager.KEY_LOCATION_CHANGED;
    Location location = (Location)intent.getExtras().get(key);
    // process new location
  }
};
IntentFilter locIntentFilter = new IntentFilter(locAction);
registerReceiver(locReceiver, locIntentFilter);

To use intents to passively detect location changes start a service to refresh the data without updating a UI.

 receiver android:name=".locReceiver" android:enabled="true"
 intent-filter>
  action android:name="com.ioApp.LOCATION_UPDATE_RECEIVED"/>
  /intent-filter>
 /receiver

Wake alarms and non-waking alarms example:

int wake = AlarmManager.ELAPSED_REALTIME_WAKEUP;
int sleep = AlarmManager.ELAPSED_REALTIME;
long minInt = AlarmManager.INTERVAL_HALF_DAY;
long bestInt = AlarmManager.INTERVAL_HALF_HOUR;
long trigger = SystemClock.elapsedRealtime() + bestInt;

alarms.setInexactRepeating(wake, trigger, minInt, alarmIntent);
alarms.setInexactRepeating(sleep, trigger, bestInt, alarmIntent);

Make your data updates smart by monitoring number of elements. For example:

monitor connectivity

Connectivity Manager cm = (ConnectivityManager)ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNet = cm.getActiveNetworkiInfo();
boolean isConnected = activeNet.isConnectedOrConnecting();
boolean isMobile = activeNet.getType() ==
   ConnectivityManager.TYPE_MOBILE;

monitor power and battery

IntentFilter bf = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent bat = ctx.registerReceiver(null, bF);
int bstat = bat.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
boolean power = bstat == BatteryManager.BATTERY_STATUS_CHARGING ||
    bstat == BatteryManager.BATTERY_STATUS_FULL;

monitor docking status and other possible state-change broadcasts to monitor:

ACTION_DOCK_EVENT
ACTION_BATTERY_LOW
ACTION_POWER_CONNECTED
ACTION_POWER_DISCONNECTED
CONNECTIVITY_CHANGED

Cloud-based Android Backup service.

Backing up shared preferences

public class MyPrefsBackupAgent extends BackupAgentHelper {
  static final String PREFS = "user_prefs";
  static final String PREFS_BACKUP_KEY = "prefs";

  @Override
  public void onCreate() {
    SharedPreferencesBackupHelper helper =
       new SharedPreferencesBackupHelper(this, PREFS);
   addHelper(PREFS_BACKUP_KEY, helper);
  }
} 

in Manifest
application android:label="MyApp" android:backupAgent="MyPrefsBackupAgent">
  meta-data android:name="com.google.android.backup.api_key"
          android:value="MyAPIKey" />
/application>

Making your apps more adaptive. Use appropriate keyboard for EditText entry UI.

eg, in EditText  ....
   android:inputType="phone"
   android:imeOptions="actionSend | flagNoEnterAction"
/>

Listen for those special action keys in your OnEditorActionListener:

EditText.OnEditorActionListener myActionListener =
 new () {
   @Override
   public boolean onEditorAction(EditText v,
     int actionId,
     KeyEvent event) {
       if (actionId == EditorInfor.IME_ACTION_SEND) {
         //  handle the SEND action
         return true;
       }
       return false;
    }
};

editText.setOnEditorActionListener(myActionListener);

Handle Audio nicely (interoperate nicely with audio focus).

Make everything asynchronous and make use of background threads (Handler, AsyncTask, IntentService, AsyncQueryHandler, Loader and CursorLoader).

CursorLoader example

// within onCreate...
getLoaderManager(),initLoader(0, null, null);

// Callbacks
public Loader onCreateLoader(int id, Bundle args) {
    Uri baseUri = MyContentProvider.CONTENT_URI;
    return new CursorLoader(getActivity(), baseUri, null, null, null, null);
}

public void onLoadFinished(Loader loader, Cursor data) {
   mAdapter.swapCursor(data);
}

public void onLoaderReset(Loader loader) {
    mAdapter.swapCursor(null);
}

Testing/Debugging. Use Strict Mode:

public void onCreate() {
 if (DEVELOPER_MODE) {
   StrictMode.setThreadPolicy(
     new StrictMode.ThreadPolicy.Builder()
              .detectDiskReads()
              .detectDiskWrites()
              .detectNetwork()
              .penaltyFlashScreen()
              .build());
 }
 super.onCreate();
}

Cleaning up MacPorts

First, let’s see how much macports use up on my system:

$ du -sh /opt
8.1G

Next, how many packages do I have installed in total:

sudo port installed | wc
     538

And how many packages are active:

sudo port installed | grep active | wc
     253

Now let’s trim the entire collection, by uninstalling all inactive packages:

sudo port uninstall inactive

If you have some dependency issues, add ‘-f’ flag to the port command that will ignore dependencies.

This reduced the disk usage in /opt to 4.8G, and got the number of all installed ports to 254.

More cleanup:

sudo port clean --all installed

This removes all temporary files generated while building and installing packages. After that, I got down to 3.6G in /opt. Not bad down from 8.1G.