Oct 15

Urettmessig tilleggsavgift på bompassering? En telefon kan lønne seg.

Vi fikk nylig en faktura i posten fra Østfold Bompengeselskap AS (en del av Bro- og tunnelselskapet AS), pålydende 320 kroner. 20 kroner for bompassering, og 300 i “tilleggsavgift” (les: bot) for ikke å ha betalt. Det var bare et par problemer:

  • Vi betaler for oss når vi passerer bomstasjoner med myntinnkast, og forsikrer oss om at lyset er grønt før vi kjører.
  • Vi bor og jobber i Oslo, og følte oss ganske sikre på at vi ikke passerte en bomstasjon i Moss klokken 06:12 denne morgenen.
  • Den aktuelle morgenen var over 3 måneder før fakturaen ble sendt ut.

Tilfeldigvis var omstendighetene slik at vi husker å ha passert (og betalt) denne bomringen på ettermiddag/kveld to dager etter den angivelige snikingen. Helt tilfeldig kunne vel ikke tilleggsavgiften være, når det dreier seg om en bomring vi så godt som aldri passerer?

Selv om utallige frustrerte innlegg på nettet tilsier at en klage på tilleggsavgift for bompassering er fånyttes, tok vi en telefon for å i det minste se hva de hadde av bevis. Og bevis fantes ikke, i alle fall ikke mot oss. I følge selskapet var bildet de satt på av et helt annet skilt, og de frafalt kravet på stedet. Kjekt, men ikke spesielt tillitsvekkende.

Noe sier meg at en god andel av mottakerne av denne typen tilleggsavgifter ser det som så usannsynlig at de kommer noen vei med klagen sin at de betaler uavhengig av skyld. Jeg skriver dette i håp om at noen i det minste tar den ene telefonen for å sjekke om bildet de sitter på er av ens eget skilt.

Jun 13

A couple of new Android apps

Over the last couple of weeks, I’ve created a couple of small apps for fun, to scratch my own itch, and to experiment a little with Android Market. Check them out:

Headphone Action

Small utility for starting an app of your choice whenever your headphones are plugged into your device. My first paid app, to see how that all works. Market has a cap on how low you can set the price, so it’s priced about the minimum price for all countries (rounded off a bit where possible). Go to Headphone Action on Android Market

Næringsinnhold

Norwegian-only app, with nutritional info for over 1000 common Norwegian food items and drinks. It’s a free app, and the first I’ve made with ads (by AdMob), to check out how that works. Let’s just say I’m not browsing the yellow pages for Ferrari dealerships just yet. I’ve tried to keep the ads non-intrusive in coloring and in the categories of ads that are displayed, which results in ads only showing about 1/3 of the time. This bit is up for a bit of experimentation. Go to Næringsinnhold on Android Market.

Now.. what to make next?

Apr 19

Android: Handling longpress/longclick on map (revisited)

I’ve prevously written about my attempts to capture longpress/longclick on a map (see this post). As I wrote in that post:

“While working on my MapActivity based Android app, I wanted to be able to show a context menu when the user longpressed any point on the map (not necessarily a marker in an overlay) and perform some action related to that point. I wrongly assumed that there would be some simple method to override for this, like onLongPress(). ”

The solution I outlined in the previous post never did feel quite right. It was a bit of a mess and it could be difficult to follow what was really happening. I recently improved this design in an app of mine, and here’s the lowdown:

The main steps are as follows:

  • Subclass MapView and define the OnLongpressListener.
  • In your layout file, set up the new MapView subclass.
  • Define an instance of your new OnLongpressListener in your MapActivity.

The first point is the most important, this is the core of the solution: Subclassing MapView and adding our custom OnLongpressListener. I hope the code comments make it clear enough what is going on:

public class MyCustomMapView extends MapView {
        
        // Define the interface we will interact with from our Map
	public interface OnLongpressListener {
	    public void onLongpress(MapView view, GeoPoint longpressLocation);
	}
	
	/**
	 * Time in ms before the OnLongpressListener is triggered.
	 */
	static final int LONGPRESS_THRESHOLD = 500;
	
	/**
	 * Keep a record of the center of the map, to know if the map
	 * has been panned. 
	 */
	private GeoPoint lastMapCenter;
	
	private Timer longpressTimer = new Timer();
	private MyCustomMapView.OnLongpressListener longpressListener;

	
	public MyCustomMapView(Context context, String apiKey) {
	    super(context, apiKey);
	}

	public MyCustomMapView(Context context, AttributeSet attrs) {
	    super(context, attrs);
	}

	public MyCustomMapView(Context context, AttributeSet attrs, int defStyle) {
	    super(context, attrs, defStyle);
	}

	public void setOnLongpressListener(MyCustomMapView.OnLongpressListener listener) {
		longpressListener = listener;
	}

	/**
	 * This method is called every time user touches the map,
	 * drags a finger on the map, or removes finger from the map.
	 */
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		handleLongpress(event);
		
		return super.onTouchEvent(event);
	}

	/**
	 * This method takes MotionEvents and decides whether or not
	 * a longpress has been detected. This is the meat of the
	 * OnLongpressListener.
	 * 
	 * The Timer class executes a TimerTask after a given time,
	 * and we start the timer when a finger touches the screen.
	 * 
	 * We then listen for map movements or the finger being
	 * removed from the screen. If any of these events occur
	 * before the TimerTask is executed, it gets cancelled. Else
	 * the listener is fired.
	 *  
	 * @param event
	 */
	private void handleLongpress(final MotionEvent event) {
		
		if (event.getAction() == MotionEvent.ACTION_DOWN) {
			// Finger has touched screen.
			longpressTimer = new Timer();
			longpressTimer.schedule(new TimerTask() {
				@Override
				public void run() {
					GeoPoint longpressLocation = getProjection().fromPixels((int)event.getX(), 
							(int)event.getY());
					
					/*
					 * Fire the listener. We pass the map location
					 * of the longpress as well, in case it is needed
					 * by the caller.
					 */
					longpressListener.onLongpress(MyCustomMapView.this, longpressLocation);
				}
				
			}, LONGPRESS_THRESHOLD);
			
			lastMapCenter = getMapCenter();
		}
		
		if (event.getAction() == MotionEvent.ACTION_MOVE) {
				
			if (!getMapCenter().equals(lastMapCenter)) {
				// User is panning the map, this is no longpress
				longpressTimer.cancel();
			}
			
			lastMapCenter = getMapCenter();
		}
		
		if (event.getAction() == MotionEvent.ACTION_UP) {
			// User has removed finger from map.
			longpressTimer.cancel();
		}

	        if (event.getPointerCount() > 1) {
                        // This is a multitouch event, probably zooming.
	        	longpressTimer.cancel();
	        }
	}
}

The layout file will look somewhat like the following:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	<com.example.MyCustomMapView android:id="@+id/mapview"
		android:layout_width="fill_parent"
		android:layout_height="fill_parent"
		android:apiKey="<YOUR API KEY HERE>"
		android:clickable="true"/>
</RelativeLayout>

Make note of the android:clickable attribute. As you might know, this must be set to be able to pan, zoom or in other ways interact with your map.

The final piece is adding your onLongpressListener in your MapActivity. For the sake of the example, let’s say the previous layout file is named res/layout/map.xml. The necessary code for your MapActivity will look something like this (I only include the parts relevant for this example:

public class Map extends MapActivity {
    private MyCustomMapView mapView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.map);
        mapView = (MyCustomMapView)findViewById(R.id.mapview);

        mapView.setOnLongpressListener(new MyCustomMapView.OnLongpressListener() {
	    public void onLongpress(final MapView view, final GeoPoint longpressLocation) {
	        runOnUiThread(new Runnable() {
		    public void run() {
		        // Insert your longpress action here
		    }
		});
	    }
	});
}

To actually have your longpress open up a context menu, you need to perform some additional setup of the context menu itself. I’ve avoided including this, to make the example clearer. Any tutorial on setting up context menus should be able to guide you in the right direction.

If something is unclear in the examples, or you have trouble getting stuff working, just add a comment and I’ll try to help you out.

Nov 21

Android: Adding desktop shortcut support to your app

So you have an app with quite a bit of functionality, but you would like to offer your users some way of performing often used actions or go directly to an often used activity besides the one triggered by your apps launcher. Android offers desktop shortcuts (if you don’t know about them, try long-pressing your Android desktop) for this situation, but how do you actually implement support for this in your app?

I was not able to find any straight forward documentation or how-to on this subject, so hopefully this writeup will be helpful to someone.

The short story is that you need a specially crafted activity which must:

  • Be defined in your AndroidManifest.xml with an intent filter with the action android.intent.action.CREATE_SHORTCUT.
  • Return a result, an Intent, containing your actual shortcut. The shortcut itself is represented by another Intent.

So let’s take this step by step:

1. Creating the activity

Nothing special here. When adding your shortcut from your Android desktop this Activity will get started and act as any other Activity. Just create the activity and override onCreate().

2. Configuring the manifest

Add your activity like any other, to the <application> section. Add the following intent filter inside the definition of that activity:

<code>
            <intent -filter>
                <action android:name="android.intent.action.CREATE_SHORTCUT"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent>
</code>

Already you should be able to see your activity show up among the other shortcuts if you longpress your Android desktop and select “Shortcuts” from the menu that pops up.

3. Implement your shortcut activity’s onCreate() to return the shortcut.

This activity can do pretty much anything you want. You can for example have it pop up a list of actions to choose from. I’ll explain the simplest possible version here, for clarity.

The trick here is that you should use the result of your activity to return the shortcut you wish to add to the users Android desktop. The result is set using setResult() (see Activity API docs for details), which returns an Intent. The Intent should containt the following:

  • Another intent, which is the actual shortcut itself. This intent will be triggered when the user presses your shortcut icon.
  • The actual name of your shortcut, as you want it displayed on the users desktop
  • An icon resource to be displayed on the desktop.

The Activity will then look somewhat like this:

public class ShortcutActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		// The meat of our shortcut
		Intent shortcutIntent = new Intent("com.test.yourapp.SOME_INTENT");
		ShorthcutIconResource iconResource = Intent.ShortcutIconResource.fromContext(this, R.drawable.launcher_icon);
        
        // The result we are passing back from this activity
        Intent intent = new Intent();
        intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
        intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "Shortcut Test");
        intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
        setResult(RESULT_OK, intent);
        
        finish(); // Must call finish for result to be returned immediately
	}

}

And that’s it. If you followed these steps you should now have a fully functional Android desktop shortcut.

Sep 11

Favoritt-apps til Android

En liten oppsummering av mine mest brukte Android-apps. Jeg vil oppdatere denne artikkelen etterhvert.

Locale

Dette er for meg en av de virkelige killer-appene til Android. Dette er det gamle profil-systemet du er vant til fra andre telefoner, på steroider; Du kan få telefonen til å reagere på situasjoner som endringer i batteristatus, hvem som ringer deg, telefonorientering, tidspunkt og lokasjon(!). Min telefon er satt opp til å automatisk gjøre ting som:

  • Skru på WiFi og bluetooth når jeg spaserer inn på jobben om morgenen, i tillegg til å skru volumet helt ned for ikke å irritere kolleger med ringing fra innkommende samtaler. Lysstyrken på skjermen skrus også helt ned for å spare batteri. Displayet synes helt fint innendørs uansett.
  • Skru volumet helt ned når jeg befinner meg på en av de lokale kinoene, kontorer der jeg regelmessig deltar i møter, og andre steder der en ringende telefon vil være irriterende eller pinlig.
  • Skru av volum og vibrasjon dersom 1) det er en ukedag, 2) telefonen er koblet til laderen og 3) klokken er mellom 23 og 7.30. Hva kan jeg si, jeg verdsetter nattesøvnen. :)
  • Ved hjelp av huskeliste-appen Astrid og Astrid’s Locale plug-in kan jeg få en varsling med oppgaver med en spesifikk tagg når jeg befinner meg i nærheten av en lokasjon – som f.eks Ikea eller Clas Ohlson.

Locale er stivt priset, med en prislapp på $10.99, men for meg var det verdt det.

Mer info her.

1881 Mobilsøk

For meg er det beste med denne appen at den viser hvem som ringer når du ikke har nummeret i kontaktlisten. Den kan også benyttes for manuelle oppslag i 1881. Appen ser dog ut til å ha en del utfordringer med stabiliteten om dagen. Mer her. Gule Sider har en tilsvarende app, men denne ble etterhvert så full av bugs og (for meg) lite nyttig funksjonalitet.

Waze

Et alternativ til navigering i Google Maps. Den har en sosial del som bla. lar deg dele trafikkstatus med andre brukere. Mer her.

Trafikanten

Bor du i Oslo og kjører kollektivt er dette en må-ha. Mer her.

My Tracks

Registrerer treningsdata ved hjelp av GPS-en og lar deg dele dataene med andre via Google Docs. Mer her.

NetCounter

Holder styr på hvor mye datatrafikk telefonen din forbruker, og kan gi advarsler om du nærmer deg grenser du har konfigurert inn. Kjekt for oss som ikke har en mobilplan med ubegrenset mobiltrafikk. her.

Spotify

Trenger jeg si mer? Mer her.

Thinking Space

Super mind mapping-app. Mer her .

Remember The Milk

Huskelisteapplikasjon som fungerer suverent for GTD. Krever dog at du er betalende kunde. Mer her.

SqueezeControl

Med denne kan man benytte telefonen som kontroller for Logitechs SqueezeBox-serie. Mer her.

Chrome to Phone

Lar deg enkelt sende lenker, kart, tekst og telefonnummer direkte fra PC til telefonen. Plugins som benytter denne appen finnes til både Chrome og Firefox. Mer her.

ACast

Podcast-avspiller og RSS-leser. Mer her.


Advarsel: Skamløs egenreklame!

Putter disse i en egen seksjon, da de ikke bare er nyttige, men laget av meg.

Bysyklist Oslo

Finner ledige låser eller bysykler i Oslo. Dette er faktisk en av appene jeg har aller mest nytte av gjennom sommerhalvåret. Mer her.

Næringsinnhold

Lar deg raskt slå opp næringsinnholdet i over 1000 matvarer. Mer her

Headphone Action

Starter en valgfri app (f.eks Spotify?) når du plugger i hodetelefonene. Mer her

Older posts «