Pages

Saturday, July 13, 2013

Add missing Android Activity tools

One major issue with Android, is that Google have never implemented a proper way of detecting which Activity is currently in the foreground and which are behind. If you open multiple Activities in an application, and then flip the screen, all the callbacks (onCreate(), onResume() etc) will be invoked in all your Activities. The problem is that you might have some code in some of these Activities that you do not wish to have executed, unless the Activity holding that code, is currently placed in the foreground. Problem is that you can't check this state of an Activity. At least not in a proper way.

public Boolean isForeground() {
    ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
    ComponentName cn = am.getRunningTasks(1).get(0).topActivity;
}

There is the possibility to use something like the above. There is just two issues regarding this. For one, google states that getSystemService(Context.ACTIVITY_SERVICE) is only intended for debugging and presenting task management user interfaces, and that it should never be used for core logic in an application. Second, you need to have android.permission.GET_TASKS added to the application in order to use this, which is not ideal for a simple task like this.

So what we can do instead, is extending the Activity class with a custom one which includes this missing feature and then extend our application Activities with our custom one instead.

public class ExtendedActivity extends Activity {
 
 private final static ArrayList mActivities = new ArrayList();
 
 private final static Object oLock = new Object();
 
 private Boolean mReCreate = false;
 
 @Override
 public void onSaveInstanceState(Bundle savedInstanceState) {
  savedInstanceState.putBoolean("mReCreate", true);

  super.onSaveInstanceState(savedInstanceState);
 }
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  
  if (savedInstanceState != null) {
   mReCreate = savedInstanceState.getBoolean("mReCreate");
  }
  
  synchronized (oLock) {
   mActivities.add( (mReCreate ? mActivities.size() : 0), this.getClass().getName());
  }
 }
 
 @Override
 protected void onDestroy() {
  super.onDestroy();
  
  synchronized (oLock) {
   mActivities.remove(this.getClass().getName());
  }
 }
 
 public final Boolean isForeground() {
  synchronized (oLock) {
   return mActivities.size() == 0 || mActivities.get(0).equals(this.getClass().getName());
  }
 }
}

What this does, it keep track of all active Activities and in which order they where opened. We only need to extend all of our Activities using this one, and then we can use isForeground() to check the state of an Activity Object.

public class MyActivity extends ExtendedActivity {

 @Override
 protected void onResume() {
  super.onResume();
  
  if ( isForeground() ) {
            // Do something
        }
 }
}

Sunday, June 30, 2013

Android Activity in Dialog Style

I was working on an Application where I had created a custom settings Activity (Not using the Android built-in settings tools), in order to adopt the same layout style as in the rest of the Application. It worked fine, and now I wanted to add some additional tablet styles as well. When using the Application on a tablet, I wanted a more dialog like view, something without a panel and did not take up the whole screen, but instead placed itself on top of the main activity. When searching the web for a solution to this, the only thing that I could find over and over again, was to add the Dialog Theme to the Activity via AndroidManifest.xml. I had two issues with this. For one, I did not want a Dialog like Activity for all devices, but only for tablets. And second, I have my own custom themes that I apply to all Activities. So I went another way which I was sure would work.

public class ActivityAppSettings extends Activity {
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  if (getApplication() != null && ((ApplicationBase) getApplication()).mTheme > 0) {
   setTheme( ((ApplicationBase) getApplication()).mTheme );
  }
  
  super.onCreate(savedInstanceState);
  
  if (getResources().getString(R.string.config_screen_type).equals("xlarge")) {
   requestWindowFeature(Window.FEATURE_NO_TITLE);
  
   Rect display = new Rect();
   getWindow().getDecorView().getWindowVisibleDisplayFrame(display);
   getWindow().setBackgroundDrawable(new ColorDrawable(0));

   getWindow().setLayout(
     (int) ((display.width() > display.height() ? display.height() : display.width()) * 0.7), 
     (int) (display.height() * 0.7)
   );
  }
  
  setContentView(R.layout.activity_app_settings);
 }
}

Sure enough it worked, but with one little issue. The space around the Activity was not transparent. It seamed that you could not change the window background from within the Activity. The panel however was removed, the size was set at 70% of the screen size and my custom theme had been applied to the content within, but with a black background surrounding it all.

I needed another work-around. I went to AndroidManifest.xml and applied Androids's Translucent theme to the Activity.

<activity android:theme="@android:style/Theme.Translucent"

The Activity theme would still be replaced by my Custom theme from within the Activity, but since you cannot change the window background from within there, the transparency would still stick. And then on Phones, you just apply a background to the main View, which you then configure as match_parent for both height and width, which will overlay the transparent window background, making it seam like a regular full-screen Activity.

Friday, June 28, 2013

Check Android Uptime

So you are programming an application for Android. You have made a custom caching system which should clear the cache after each boot. One way to do this, could be to create an onBoot Receiver and have that clear out your cache. However, why have your application run on each boot if the user might not even use it all that much? And what if the user installed some Privacy Guard Application and disabled your app's receiver?

The best way to handle this, is to have the cache cleared on the first launch of the application. But in order to do this, you will have to know whether or not this actually is the first launch or not. To help you with this, Android has two useful methods. One to provide the total amount of milliseconds that have past since 1970 (or something like that) and one to provide the total amount of milliseconds that have pasted since the device was booted. Extract the boot time from the total time, and you will have the exact timestamp from when the device was started. Now just save this to your shared preferences and compare it with a fresh timestamp on each application launch.

public class myclass extends something {

    /*
     * We can use this to avoid to much checking.
     * As long as this static property exists, we know that device has not been rebooted and there is no reason to do a check.
     */
    private static Boolean oCacheCheck = false;
    
    public SharedPreferences getCache() {
        SharedPreferences preferences = .getSharedPreferences("cache", 0x00000000);

        if (!oCacheCheck) {
            Long freshTime = System.currentTimeMillis() - SystemClock.elapsedRealtime();
            Long cachedTime = preferences.getLong("timestamp", 0);

            if (freshTime == cachedTime) {
                Editor edit = preferences.edit();
    
                edit.clear();
                edit.putLong("timestamp", freshTime);
                edit.commit();
            }

            oCacheCheck = true;
        }

        return preferences;
    }
}

if (freshTime == cachedTime) {

There is however one tiny issue with this way of checking last boot time, and that is that both methods might take a few milliseconds to execute (depending on the speed of the device) and you can only execute one at a time. This means that your calculation could differ each time you run it, making the comparison with the cached timestamp useless.

The solution to this problem is to check the difference between the two timestamps instead of comparing them to see if they are equal. The methods might take a few milliseconds to execute and any type of device will take several seconds, some even minutes, to boot. So we will just check to see if the two timestamps has a difference less than 3 seconds.

public class myclass extends something {

    /*
     * We can use this to avoid to much checking.
     * As long as this static property exists, we know that device has not been rebooted and there is no reason to do a check.
     */
    private static Boolean oCacheCheck = false;
    
    public SharedPreferences getCache() {
        SharedPreferences preferences = .getSharedPreferences("cache", 0x00000000);

        if (!oCacheCheck) {
            Long freshTime = System.currentTimeMillis() - SystemClock.elapsedRealtime();
            Long cachedTime = preferences.getLong("timestamp", 0);

            /*
             * If this is grater than 3 second, then we will most likely have a fresh application launch. 
             * No device, no mater how slow, takes 3 seconds or more to execute the time methods.
             * And no device, no mater how fast, can boot and launch the application in less than 3 seconds. 
             */
            if ((freshTime - cachedTime) > 3000) {
                Editor edit = preferences.edit();
    
                edit.clear();
                edit.putLong("timestamp", freshTime);
                edit.commit();
            }

            oCacheCheck = true;
        }

        return preferences;
    }
}

if ((freshTime - cachedTime) > 3000) {

If, when you extract the cached timestamp from the fresh one, get a difference on more than +3000 milliseconds, you can be sure that the device has been rebooted since your last application launch.