Saturday 27 November 2010

Top 20 nice market comments about MyPOD Podcast manager/player

It's always nice when people like your work, nice comments go a long way when you are trying to make something that is different, so I though I'd post the Top 20 nice comments about MyPOD, so here goes, thanks to everyone:-
  1. After long research, I eventually found a podcast client which can do everything what I wanted (and more). Awesome app, keep going!
  2. This app requires an IQ of at least 85 to use. If you like to smash big buttons like a monkey do not install it. Your fault if you can't use.
  3. I've tried a few podcast players, and this is by far my favourite. It is worth buying the full version for more than 6 feeds. Good job.
  4. Fantastic app. Evolving quickly and getting better every time. Most used app on my phone.
  5. Best most robust podcast app by far.
  6. The best thing is the independant volume controls. You can make it quieter than the phone would normally let you. Handy listening to podcasts at night
  7. Tried all podcats and this is by far the best. A lil confusing at first, but get used to it and youll see its AWESOME!
  8. Far and away the best android podplayer i'v ever used. Simple to use but extremely configurable. Unlocked, on samsung captivate.
  9. Best app I have bought! Easy to set up and no sync/account issues like listen. Could do with option to save favourite pods to media player
  10. Best podcatcher. It takes alittle to get used to the UI but once you do, it blows all others out of the water!!!!
  11. Easily the most used app on my phone. Avaulable features and responsive developer make MyPod the best podcast manager available. Well worth unlocking.
  12. Truly wonderful app. Great interface, easy to use, minimal learning curve and its very powerful. Well worth the price (I bought it twice)
  13. This is by far the best podcast app on the market.it takes about ten minutes to get the hang of how it works.
  14. An excellent podcast app. The first to make me begin shifting subscriptions from my ipod to my EVO. First app I excitedly bought an unlock key for.
  15. This app does it all! Huge thanks to the dev.
  16. Fantastic app. Manages all my podcasts perfectly in one place. Who needs an iPhone whilst this exists? ;-)
  17. The UI is intimidating the first time you use it, but it's easy to learn and the app is feature packed...worth taking a few minutes to get familiar w/
  18. One of the best podcast apps around. You can't do better for free. So good I wanted to pay for it.
  19. I've tried them all and was very frustrated with the confusing and shamefully poor podcast support till I found this gem!
  20. If you love to control how your phone works this is the perfect app; If you have trouble with complexity and want a predetermined path go for Listen.

Sunday 14 November 2010

Android NDK Primer & Drawable Customiser presentation

I presented a primer on getting started with Android NDK at OpenLab OpenDay on the 13th November. The presentation consisted of the following parts:
  • Basic introduction to the Android NDK (Building and development basics)
  • Use of DirectBuffers
  • Accessing java objects from the NDK (Getting and Setting data in Java Objects)
The talk covered most of the basics, and people were surprised to see just how easy using the NDK can be...

I also gave a brief overview of the upcomping Drawable Customiser release and have released a basic version of the API under GPL (Available here). I will be posting some usage examples in the next couple of weeks ..

The NDK presentation is available as : OpenOffice and PowerPoint XML

Monday 25 October 2010

Filtering onTouch for light pressure

For the drawing program I am writing, there is a sensitivity problem when the pressure is light on the touchpad. When the finger is pressed down (like when you are in the middle of a drag operation) the touch point is stable as the area of the finger pressed to the touchpad is large and so the centroid of the area is quite stable.

However at the end of the drag operation, as the finger pulls away, the area on the touch sensor decreases and the actual touch point seems to vary a lot. This is a problem for me in my drawing program, as you make fine changes to the drawing, you don't want the changes to be affected by this noise as you pull up from the touchpad.

This solution uses a mean filter to combat the problem - noting new there. But we combine the mean value with the pressure value so as the pressure gets lighter, we us more of the mean value. It works best if the finger pulls off the surface slowly.

The filter class I have used is shown below:
/*
* licensed under CC BY-SA : http://creativecommons.org/licenses/by-sa/3.0/legalcode
*/
class MeanFilter {
 public static final int DEFAULT_LEN = 11;
 float[][] points = new float[2][DEFAULT_LEN];
 int currentPos=0;
 int actualValues = 0;
 int length = DEFAULT_LEN;
 float pressureLimit = 0.2f;
  
 public MeanFilter(int length,float pressureLimit) {
  super();
  this.length = length;
  this.pressureLimit = pressureLimit;
 }
 public void add(float x,float y) {
  points[0][currentPos]=x;
  points[1][currentPos]=y;
  currentPos++;
  currentPos%=length;
  if (actualValues<length) {
   actualValues++;
   actualValues=Math.min(actualValues, length);
  }
 }
 public void clear() {currentPos=0;actualValues=0;}
 public void get(PointF defaultPoint,float pressure){
  if (actualValues==0) {return;}
  if (pressure<pressureLimit) {
   float xmean = getMean(points[0], actualValues);
   float ymean = getMean(points[1], actualValues);
   float pressureRatio = pressure/pressureLimit;
   defaultPoint.x=(pressureRatio)*defaultPoint.x+(1-pressureRatio)*xmean;
   defaultPoint.y=(pressureRatio)*defaultPoint.y+(1-pressureRatio)*ymean;
  }
 }
 public void setLength(int i) {
  length = i;
  clear();
 } 
 private float getMean(float[] arr,int ctr){
  float accum = 0;
  for (int i=0;i<ctr;i++) {
   accum+=arr[i];
  }
  return accum/ctr;
 }
}

To use it we create an instance - this is a mean filter length:7, pressure threshold:0.3
private MeanFilter touchFilter = new MeanFilter(7,0.3f);

Then in onTouch, we can filter the value to be used like so:
/*
* licensed under CC BY-SA : http://creativecommons.org/licenses/by-sa/3.0/legalcode
*/
public boolean onTouch(View v, MotionEvent event) {
 PointF touchPointOnScreen = new PointF(event.getX(),event.getY());
 switch (event.getAction() ) {
  case MotionEvent.ACTION_DOWN: touchFilter.clear();
   break;
  case MotionEvent.ACTION_MOVE: 
   touchFilter.add(event.getX(),event.getY());
   if (event.getPressure()<0.05) {return true;}
   if (event.getPressure()<0.3) {
    touchFilter.get(touchPointOnScreen,event.getPressure());
   } 
   break;
  case MotionEvent.ACTION_UP:
   touchFilter.get(touchPointOnScreen,event.getPressure());
   break;
 }
 // use touchPointOnScreen here...
 return true;
}
So touchPointOnScreen contains the filtered value to use.

We can note that the mean values arent calculated until the pressure drops below the threshold, thus being slightly more efficient than just mean filtering all the time, this also stops touchpoint lag as the mean filtered value will generally lay somewhere behind the current value.

This code is made open under the CC BY-SA licence.

Tuesday 28 September 2010

Silent Timer Released

I have just yesterday released a (very) small app and widget called "Silent Timer". This a simple app to disable the silent mode after a certain time. I made it because I regularly forget to turn off silent mode after movies and meetings. There are plans to add some more features in the next few weeks.

There is a free ad supported version and a paid version. The free version has had 300 downloads so far - which isn't too shabby for absolutely no announcements or marketing (well, except this post).

OTA Update 2.2.1

Just received an OTA update today to 2.2.1 - looks to be mostly security stuff, blocking one touch rooting and some WIFI issues. There are also some nice little improvements to Gmail, easier replying and forwarding buttons. Some people reported maps changes but they must be slight, as I can't see them.

The only issue being that Swype has the be re-installed (it works as keyboard but the Swype feature disables - I can only assume this is intentional as it happened on my 2.2 update as well). But who can can live without Swype?

Nexus one users have got the update first as usual, other are coming soon (Droid X tomorrow?).

Tuesday 21 September 2010

HTC launch event - Desire HD and Z

HTC got friendly last week having a launch party for their yet unreleased Desire HD and Dersire Z phones. The phones are nice and light (maybe slightly too light) models with WVGA screens (the desire HD has a larger screen at 4.3").

The event was a bit misguided - the phones were in a separate room, in which only a few people were allowed to view them at a time. I think a better marketing technique would've been to have more phones so everyone that went could get a better look at them. It was quite annoying going to see new phones and not really being able to get a sense of what they feel like to use for a while. If you're going to spend all that money on marketing, then more actual phones definitely couldn't hurt, but who would say no to free food and beer anyway ...

The Desire HD borders on a mini-tablet device with such a large screen, though it is still very much a (big) phone. It also has the very nice feature of a built in DNLA server, which I am looking very forward to. Hopefully it will play nicely with MyPOD so you can easily have videocasts on your main TV. It is certainly a nice feature to have for a media device though, videos can be viewed without cables. I'm guessing more recent TVs have this DNLA client functionality built in, but I use my PlayStation which works fine (I haven't had a chance to test it with the device unfortunately).

The desire Z is slightly lower power (800MHz), and has a nice looking slide out keyboard.

The phone specs are available here:
HTC Desire HD

HTC Desire Z

Thursday 16 September 2010

DroidCon London 28-29 Oct, 2010

The upcoming London DroidCon conference looks very promising, the people at Novoda (and others I'm sure ..) have put together an excellent programme of speakers and topics, especially in the user experience and business marketing side. I would highly recommend it for people who are looking to develop their ideas on Android.

These events are very important for the Android community to meet face-to-face and discuss ideas and strategies, and the Barcamp on the first day is a good way to incubate such ideas.

DroidCon events have been held in London and Berlin for the past two years and with the quickening pace of Android development and market penetration, it looks to continue as a premier event for the Android community.

http://www.droidcon.co.uk/

Saturday 11 September 2010

Android vs Oracle contradictions

There has been a lot of talk in the tech world about Oracle's patent infringement claims against the Android operating system. Oracle have not disclosed the details of the claims, and are highly unlikely to do so before the case goes to court. While I respect the right of Oracle to protect it's intellectual property, it seems this non-disclosure is more aimed at scaring people away from Android, even though ultimately they will be seeking some form of licensing arrangement for whatever patents/copyrights have been infringed in Android.

The best thing Oracle can do at the moment is to disclose the basis of their claims, not only are they hurting Android but they are also hurting the open-source Java community in its entirety, and in doing so the value of arguably the biggest asset they acquired in their takeover of Sun (i.e. Java). One wonders how they can't see this, possibly they think Java is too big to fail now (sound familiar?).

The whole thing rings of the IBM vs SCO case of a few years ago. They made claims Linux infringed their patents, would not disclose any information about it and then when the case came to court, they didn't really have much to stand on. SCO eventually filed for chapter 11 bankruptcy protection and looks like it will go out of business. The lesson that could have been learnt is that these frivolous patent claims cost everyone money and to proceed you would want to be quite sure of wining, even though the lack of disclosure indicates they aren't really that sure.

It all adds a lot of weight to the argument that the patents system needs drastic reformation for use with software, which is re-used as a general development practice. Until this happens patents will be used as a tools for revenue generation and most likely result in mutually assured destruction for businesses looking to (mis)use them for revenue generation purposes.

Wednesday 8 September 2010

MSc Project presentation

I have recently completed a MSc in Digital Music Processing at Queen Mary University in London. My final masters project was a implementation of beat detection and mixing on Android, which included a thorough analysis on onset detection, tempo analysis and beat recognition, as well as a custom Phase Vocoder implementation. The project went quite well and a good result was achieved, though in 2 months it is quite hard to complete a large implementation. The project had a healthy proportion of NDK programming in it, which was really interesting and my first use of JNI, definitely a necessity for apps needing a DSP component on Android.

The project was presented as part of the university evaluation process and is even being considered for inclusion as part of a new project to make mobile technology @ Queen Mary available to all.

For anyone interested in the gory details - I have included the final project report and presentation.

Thursday 12 August 2010

MyPOD hits 50K ...

MyPOD the podcast player by sentinel web technoloiges has recently passed the 50K downloads barrier over the last week. So congratulations to us, stay tuned for more updates in the future.

While there are many apps that have hit the 50K barrier by now, each rung on the download ladder gives more confidence to users (to try it out) and developers (to add features).

I've got a feeling the 250K will be hit a lot quicker ;)

Tuesday 22 June 2010

Anroid UI setInterval

In most apps I develop, I usually have a need to update the UI within the activity (as services change, or just live time displays). Making these timers is easy in Javascript, you just use setInterval(method,nnn).

On Android it is more complex - you need a Timer and TimerTask, and then you need another Runnable to give to Activity.runOnUiThread(..), and then your actual method. It all adds a lot of code bloat, especially if you have a few activities in your app. AsyncTask is good for updating the UI while a background process is running, but for just a simple update interval it's no use.

This is a reasonably simple solution to the problem - the MyTimer class has a setInterval method that behaves similarly to Javascript. The usage is:
  • Set the timer:
    int timer=MyTimer.setInterval(this, "updateUI", 2000,true);
    (where "updateUI" is a method with the signature: public void updateUI() {..})
  • Cancel the timer:
    timer=MyTimer.cancelTimer(timer);
Some Notes:
  • The cancel operation returns -1 if the cancel was performed successfully, so you can just test if the timer ==-1 to tell if you need to start it again.
  • The code above is called from the activity, the first argument of setInterval should be an Activity object.
  • There is a static debug variable if it doesnt work as expected (MyTimer.debug=true).
  • If there is an exception in the target method -> the timer is cancelled.
  • MAKE SURE YOU CANCEL THE TIMER (in onStop or onPause). If the timer is not cancelled, then it will prevent garbage collection, a possible way around is just to return the Timer object instead of holding it in a Vector.
  • Note that this type of usage can potentially use a lot of battery if its just left running, the updateUI method (or whatever the method name is), should do the minimum processing necessary. It should just update a couple of UI elements or something - not do masses of calculations (store results as variables is a simple solution).
package co.uk.sentinelweb.util;
/*
* licensed under CC BY-SA : http://creativecommons.org/licenses/by-sa/3.0/legalcode
*/
import java.lang.reflect.Method;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import android.app.Activity;
import android.util.Log;

public class MyTimer {
public static Vector timers = new Vector();
public static boolean debug = false;// turn on to check timer activates and cancels
public static int setInterval(Activity object,String method,int interval,boolean isUI) {
Timer timeoutTimer= new Timer();
TimeoutTask timeoutTimerTask;
try {
timeoutTimerTask = new TimeoutTask(object,method,isUI,timeoutTimer.hashCode());
if (debug) {Log.d(MyTimer.class.getSimpleName(), "timer created :");}
} catch (Exception e) {
Log.d(MyTimer.class.getSimpleName(), "error creating timer :"+method,e);
return -1;
} 
timeoutTimer.scheduleAtFixedRate(timeoutTimerTask, 0, interval); 
timers.add(timeoutTimer);
return timeoutTimer.hashCode();
}

public static int cancelTimer(int code) {
Timer timeoutTimer = null;
for (int i=0;i<timers.size();i++) {
if (timers.get(i).hashCode()==code) {
timeoutTimer=timers.get(i);
}
}
if (timeoutTimer!=null) {
timers.remove(timeoutTimer);
timeoutTimer.cancel();
timeoutTimer.purge();
if (debug) {Log.d(MyTimer.class.getSimpleName(), "timer deleted :");}
return -1;
} else {
if (debug) {Log.d(MyTimer.class.getSimpleName(), "timer not found :"+code);}
}
return code;
}

private static class TimeoutTask extends TimerTask {
Activity targetObject;
Method targetMethod;
Runnable invoker;
int code ;
public TimeoutTask(Activity targetObject, String targetMethod,boolean isUI,int code) throws SecurityException, NoSuchMethodException {
super();
this.targetObject = targetObject;
this.targetMethod = targetObject.getClass().getDeclaredMethod(targetMethod, new Class[]{});
this.code=code;
if (isUI) {
invoker = new ThreadRunner(this.targetObject,this.targetMethod);
}
}

@Override
public void run() {
try {
if (debug) {Log.d(MyTimer.class.getSimpleName(), "invoke timer :");}
if (invoker==null) {
targetMethod.invoke(targetObject, new Object[]{});
} else {
targetObject.runOnUiThread(invoker);
}
} catch(Exception e) {
Log.d(MyTimer.class.getSimpleName(), "error invoking:"+targetMethod.getName(),e);
cancelTimer(code);
Log.d(MyTimer.class.getSimpleName(), "timer:"+code+" has been cancelled");
}
}

}

private static class ThreadRunner implements Runnable{
Activity targetObject;
Method targetMethod;

public ThreadRunner(Activity targetObject, Method targetMethod) {
super();
this.targetObject = targetObject;
this.targetMethod = targetMethod;
}

@Override
public void run() {
try {
targetMethod.invoke(targetObject, new Object[]{});
} catch (Exception e) {
Log.d(MyTimer.class.getSimpleName(), "error invoking:"+targetMethod.getName(),e);
}
}
}
}
CAVEAT: While I haven't found anything in the API to do this (easily), it is possible that it is in there somewhere. If it is then let me know, and I'll update this.

This code is made open under the CC BY-SA licence.

Wednesday 21 April 2010

Has to be done really ...


                    *                        *                      
                     *                      *                       
                      *   **************   *                        
                     ************************                       
                   ****************************                     
                **********************************                  
               ******    ****************    ******                 
              ********   *****************  ********                
             ****************************************               
            ******************************************              
            ******************************************              
                                                                    
  ******                                                  ******    
 ********   ******************************************   ********   
 *********  ******************************************   *********  
 *********  ******************************************   *********  
 *********  ******************************************   *********  
 *********  ******************************************   *********  
 *********  ******************************************   *********  
 *********  ******************************************   *********  
 *********  ******************************************   *********  
 *********  ******************************************   *********  
 *********  ******************************************   *********  
 *********  ******************************************   *********  
 ********   ******************************************   ********   
  ******    ******************************************    ******    
            ******************************************              
            ******************************************              
             *****************************************              
               ************************************                 
                     *********       *********                      
                     *********       *********                      
                     *********       *********                      
                     *********       *********                      
                     *********       *********                      
                     ********        ********                       
                      *******         *******