2010-12-30

Wichtiger Hinweis

Zum 31. Januar 2011 endet meine Domain Thomas-Kuenneth.com (man beachte den Bindestrich).

Ab 1. Februar bin ich nicht mehr für den Inhalt der oben genannten Domain verantwortlich. Bitte löschen Sie ggf. etwaige Lesezeichen. Die richtige, gültige Adresse meiner Homepage ist http://www.ThomasKuenneth.com.

2010-12-19

TKWeek 1.2.7

Ich habe heute die Version 1.2.7 von TKWeek im Android Market abgelegt. Das Modul Tage zwischen zwei Daten kann jetzt nicht mehr nur die Zahl der Tage berechnen, sondern auch die ungefähre Anzahl der Wochen, Monate und Jahre. Auf dem kleinen HTC Tattoo sieht das so aus:
Tage zwischen zwei Daten auf dem HTC Tattoo
Tage zwischen zwei Daten auf dem HTC Tattoo
Die Änderungen können die Grundlage für ein oft gewünschtes Feature bilden, nämlich die Berechnung der Anzahl der Werktage zwischen zwei Daten. Das kommt dann in die nächste Version.
Wie üblich wünsche ich viel Spaß mit der App.

2010-12-18

Große Fenster gezähmt

Wie Sie sich vielleicht noch erinnern, hatte ich vor etwa einem Monat über “zu große” Fenster geschrieben. Konkret ging es um das Problem, dass das Android Emulator-Fenster manchmal nicht mehr vollständig auf den Bildschirm passt. Mein Lösungsvorschlag war, mittels Tastatur den gerade benötigten Ausschnitt anzufahren. Das ist nicht sonderlich elegant, aber zumindest halbwegs praktikabel.
Skaliertes Emulator-Fenster
Skaliertes Emulator-Fenster
Bei den Arbeiten am Buch bin ich nun über eine unscheinbare Emulator-Kommandozeilenoption gestolpert, die das Ganze noch viel bequemer macht.
Eclipse “Run Configurations”-Dialog
Eclipse “Run Configurations”-Dialog
Geben Sie unter Additional Emulator Command Line Options einfach -scale 0.5 (wobei Sie je nach Bildschirmgröße variieren dürfen) ein. Das war’s.

2010-12-02

Hätten Sie’s gewusst?

Es ist wirklich schön, dass in unserer komplizierten Zeit manche Dinge trotzdem einfach sind. Nur – drauf kommen muss man. Wie würden Sie vorgehen, wenn Sie sich über interne Abläufe von Android informieren möchten? Klar, in den Quelltext gucken. Da Android open source ist, kann man sich den ohne großes Brimborium besorgen: http://source.android.com/. Wenn man eine lokale Kopie haben möchte, muss man sich nur in git und repo einarbeiten. Aber wollen Sie das? Nur, um ein paar Zeilen Quelltext nachzulesen? Dann gibt es ja noch die Möglichkeit, online im Repository zu browsen. Aber wenn man nicht gerade die komplexe Baumstruktur im Schlaf aufsagen kann, wird auch das schnell frustrierend. Die Lösung: http://www.google.com/codesearch
Wenn Sie jetzt stöhnen, “oh mein Gott, das ist doch kalter Kaffee”.. Klar, aber hätten Sie schon im ersten Absatz des Blogbeitrags daran gedacht? Nein? Ich auch nicht. Und plötzlich sind auf der Platte wieder mehr als 2 GB frei.

2010-11-17

Große Fenster zähmen

Dass die Bildschirme mobiler Geräte immer größer werden, ist an sich eine sehr erfreuliche Sache. Wenn man seine Android-App im Emulator mit einem GALAXY Tab-Image testen möchte, kann das aber zu einem gewissen Problem werden.
Mein Reise-Notebook löst 1280 mal 800 Pixel auf, das Display des begehrten Gadgets 1024 mal 600. Wenn man sich überlegt, dass ein Windows-Fensterrahmen einen gewissen Platzbedarf hat und das Emulatorfenster selbst auch noch bestimmte Dinge anzeigt, wird klar, dass das den Bildschirm nicht reicht. Wäre das nur ein optisches Problem, könnte man sicher damit leben. Nur manchmal werden einfach Teil der emulierten GUI “unsichtbar”.
Beispiel eines aus dem sichtbaren Bereich ragenden Fensters
Beispiel eines aus dem sichtbaren Bereich ragenden Fensters
Haben Sie schon einmal versucht, so ein Fenster mit der Maus zu verschieben? Das geht nämlich nicht so ohne weiteres. Zum Glück kann man das Fenster in der Taskleiste mit gedrückter Shift-Taste mit der rechten Maustaste anklicken und im dann erscheinenden Kontextmenü Verschieben auswählen. Jetzt können Sie das Fenster mit den Cursortasten positionieren. Ein Druck auf die Enter-Taste beendet diesen Modus.
Geöffnetes Kontextmenü
Geöffnetes Kontextmenü
Leider schnappt aber noch die “Einrastfalle” von Windows 7 zu. Ganz bekommen Sie das auch nicht gelöst, aber zumindest teilweise:
Systemsteuerung
Systemsteuerung
Öffnen Sie die Systemsteuerung und klicken auf Erleichterte Bedienung.
Center für erleichterte Bedienung
Center für erleichterte Bedienung
Unter Center für erleichterte Bedienung klicken Sie bitte auf Funktionsweise der Maus ändern.
Verwenden der Maus erleichtern
Verwenden der Maus erleichtern
Auf der Seite Verwenden der Maus erleichtern setzen Sie einen Haken vor Verhindern, dass Fenster automatisch angeordnet werden, wenn sie an den Rand des Bildschirms verschoben werden.
Probieren Sie das alles einfach mal aus – am oberen Rand klappt es nicht 100%, aber man kann das Fenster zumindest halbwegs kontrollieren. Denken Sie an dieser Stelle noch daran, dass es schon hilft, vom Portrait- in den Landschaftsmodus zu wechseln.

2010-11-10

Gekonnt optimiert – bessere Layouts mit layoutopt

Das Android SDK bietet unzählige Goodies, so zum Beispiel das Tool layoutopt. Dieser nützliche kleine Helfer überprüft einzelne Layout-Dateien oder ganze Verzeichnisse und meldet Probleme und Verbesserungsmöglichkeiten.
Screenshot des Tools layoutopt
Screenshot des Tools layoutopt
Das Programm nimmt Änderungen nicht selbst vor, gibt aber an, in welcher Zeile welche Änderungen vorgenommen werden sollten. Bitte denken Sie aber daran, Ihre Anwendung nach solchen Änderungen im Emulator ausführlich zu testen, sowohl unter verschiedenen Android-Versionen als auch unter verschiedenen Bildschirmauflösungen und Pixeldichten.

2010-10-23

Riesiges Lebkuchenmännchen auf dem Google Campus

Wie üblich zieht kurz vor der Veröffentlichung einer neuen Android-Version das zugehörige Maskottchen auf dem Google-Campus ein, diesmal also ein riesiges Lebkuchenmännchen. …Das hier verlinkte Video zeigt die Aufstellarbeiten…

2010-10-20

Abschied von Desktop und Menüleiste?

Apple hat heute über Neuigkeiten aus dem Mac-Universum berichtet und zahlreiche interessante Produkte vorgestellt oder angekündigt. Die berühmte Lifestyle-Suite iLife wurde beispielsweise auf die Version 11 gehoben. Bemerkenswert ist in diesem Zusammenhang, dass Cupertino – zumindest in iPhoto und GarageBand – den Vollbildmodus stark ausgebaut hat. Wenn man sich die entsprechenden Szenen in dem Video ansieht, wird deutlich, dass die neue Darstellung stark an das iPhone/iPad erinnert. Die Zukunft wird zeigen, inwieweit sich diese Transformation nach und nach auf andere Anwendungen ausweitet. Zumindest für das ebenfalls heute vorgestellte FaceTime für Mac OS X existiert eine entsprechende Darstellungsvariante. Ob irgendwann der klassische Desktop und die Menüleiste vollständig obsolet werden, muss sich noch zeigen. Tatsache ist, dass Apple mit dem für Sommer 2011 erwarteten Version Mac OS X Lion einige Konzepte aus der iOS-Welt auf den Desktop bringt.

2010-10-06

Gar nicht verklemmt – der ClipboardManager

Das Konzept einer systemweiten Zwischenablage findet sich in vielen Betriebssystemen, so natürlich auch in Android. In diesem Beitrag zeige ich Ihnen, wie leicht sich der ClipboardManager in eigenen Anwendungen einsetzen lässt. Hier zunächst ein Screenshot meines Demos:
Screenshot der Demoanwendung
Screenshot der Demoanwendung
Nachdem Sie im Eingabefeld einen beliebigen Text eingetippt und Teile des Textes oder den ganzen Text markiert haben, öffnen Sie durch Tippen und Halten ein systemweites Popup-Menü.
Popupmenü zum Übernehmen von Text
Popupmenü zum Übernehmen von Text
Mit Ausschneiden oder Kopieren wird der markierte Text in die Zwischenablage befördert.
Die Schaltfläche Klick mich! holt sich den aktuellen Text aus der Zwischenablage, wandelt ihn in Großbuchstaben um und setzt das Ergebnis als Text des Eingabefeldes.
Und so sieht das entsprechende Programm aus:

package com.thomaskuenneth.codesnippets;

import android.app.Activity;
import android.os.Bundle;
import android.text.ClipboardManager;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;

public class MainActivity extends Activity {

  private EditText field;
  private Button button;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    field = (EditText) findViewById(R.id.field);
    button = (Button) findViewById(R.id.button);
   
    final ClipboardManager manager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
   
    button.setOnClickListener(new OnClickListener() {
     
      @Override
      public void onClick(View v) {
        CharSequence s = manager.getText();
        if (s != null) {
          String newString = s.toString().toUpperCase();
          manager.setText(newString);
          field.selectAll();
          field.setText(manager.getText());
        }
      }
    });
  }
}
Java2html

Einfach, oder? Haben Sie Fragen dazu? Schreiben Sie mir…

2010-09-22

Rolle rückwärts – CountDownTimer kann’s

Manchmal sind es die kleinen Dinge, die dem Programmierer das Leben merklich erleichtern. Deshalb möchte ich in loser Folge nützliche Helfer vorstellen, die leider allzu oft in der riesigen Android-Klassenbibliothek untergehen. CountDownTimer beispielsweise ruft regelmäßig eine bestimmte Methode auf, bis eine vorher festgelegte Zeit verstrichen ist. Hier das Quelltextfragment aus der Android-Doku:

    new CountDownTimer(30000, 1000) {

         public void onTick(long millisUntilFinished) {
             view.setText("seconds remaining: " + millisUntilFinished / 1000);
         }

         public void onFinish() {
             view.setText("done!");
         }
      }.start();

Der Timer des Beispiels läuft also insgesamt 30 Sekunden, wobei einmal pro Sekunde die Methode onTick aufgerufen wird. Laut Doku ist garantiert, dass ein nachfolgender Aufruf erst zum Zuge kommt, wenn der vorherige abgearbeitet wurde (durch ein synchronized auf das Timer-Objekt). Sie können das ausprobieren, indem Sie ein Thread.sleep(2000); einfügen. Nach Ablauf der 30 Sekunden wird onFinish aufgerufen – es sei denn, es wurde vorher cancel aufgerufen. Nicht ausdrücklich dokumentiert ist übrigens, dass Sie jederzeit erneut start aufrufen können. Auch das können Sie ausprobieren, indem Sie in onTick folgendes einfügen:

      if (millisUntilFinished / 1000 == 3)
        cdt.start();

Kennen Sie nützliche Klassen, die selten in Beispielen auftauchen? Schreiben Sie mir…

2010-09-13

Präferenzen mit Häkchen – DialogPreference leichtgemacht

Unter Android ist es extrem einfach, Anwendungseinstellungen zu verwalten. Für die Benutzeroberfläche stehen eine Reihe von Komponenten zur Verfügung, beispielsweise CheckBoxPreference (Häkchen ist gesetzt/nicht gesetzt) und ListPreference (aus einer Liste wird genau ein Element ausgewählt). Eine Kombination aus beiden (also eine Liste mit Checkboxen) ist im Standard nicht vorhanden, kann aber mit sehr wenigen Zeilen Code realisiert werden:

/**
 * PickCountriesPreference.java
 *
 * TKWeek (c) Thomas Künneth 2010
 * Alle Rechte beim Autoren. All rights reserved.
 */
package com.thomaskuenneth.android.util;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Locale;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.ScrollView;

public class PickCountriesPreference extends DialogPreference {

  private static final String TAG = PickCountriesPreference.class
      .getSimpleName();

  private SharedPreferences prefs;
  private Hashtable<String, CheckBox> ht;

  public PickCountriesPreference(Context context, AttributeSet attrs) {
    super(context, attrs);
    ht = new Hashtable<String, CheckBox>();
  }

  public static boolean isSelected(Context context, Locale l) {
    SharedPreferences prefs = context.getSharedPreferences(TAG,
        Context.MODE_PRIVATE);
    return prefs.getBoolean(l.getCountry(), true);
  }

  @Override
  protected View onCreateDialogView() {
    Context context = getContext();
    prefs = context.getSharedPreferences(TAG, Context.MODE_PRIVATE);
    ScrollView v = new ScrollView(context);
    LinearLayout layout = new LinearLayout(context);
    layout.setOrientation(LinearLayout.VERTICAL);
    v.addView(layout);
    layout.addView(getCheckbox(context, Locale.GERMANY));
    layout.addView(getCheckbox(context, Locale.US));
    return v;
  }

  private View getCheckbox(Context context, Locale l) {
    CheckBox cb = new CheckBox(context);
    String country = l.getCountry();
    ht.put(country, cb);
    cb.setText(l.getDisplayCountry());
    cb.setChecked(prefs.getBoolean(country, true));
    return cb;
  }

  @Override
  protected void onDialogClosed(boolean positiveResult) {
    if (positiveResult) {
      Editor editor = prefs.edit();
      Enumeration<String> e = ht.keys();
      while (e.hasMoreElements()) {
        String country = e.nextElement();
        editor.putBoolean(country, ht.get(country).isChecked());
      }
      editor.commit();
    }
  }
}
Java2html

Android stellt hierfür die Klasse DialogPreference zur Verfügung, die abstrakt ist, also nicht direkt verwendet werden kann. Deshalb leitet meine Klasse PickCountriesPreference von ihr ab und implementiert die Methode onCreateDialogView. Sie liefert eine View, die den Inhaltsbereich des anzuzeigenden Dialogs repräsentiert. In meinem Fall ist das eine ScrollView, die wiederum ein LinearLayout enthält. Hierin sind CheckBoxes enthalten. Die einzige Aufgabe, die noch zu tun ist: das Setzen der Werte in den SharedPreferences. Wie das gemacht wird, finden Sie in der Methode onDialogClosed.

Der hier gezeigte Quelltext ist – Sie sehen das sicher – an die Bedürfnisse von TKWeek angepasst. Er lässt sich aber leicht verallgemeinern.

2010-09-11

Herbstcampus

Am Sonntag ist startet der diesjährige Herbstcampus. An insgesamt vier Tagen geht es wieder rund in Sachen Softwareentwicklung. Insgesamt sechs Tutorien machen als Tanztages-Veranstaltungen Appetit auf Grails, JSF, Codequalität in .NET und Scala. Am Montag starten die regulären Vorträge. Einen ausführlichen Zeitplan finden Sie hier. Ich werde übrigens auch eine Session halten: Liebling, ich habe die GUI geschrumpft. Vielleicht schauen Sie ja mal rein – ich würde mich freuen.

2010-09-10

TKWeek 1.2.2

Screenshot von TKWeek 1.2.2
Screenshot von TKWeek 1.2.2
Ich habe eine neue Version von TKWeek im Android Market abgelegt, die in den Einstellungen einen Abschnitt für Nationalfeiertage enthält. Aktuell kennt meine App zwar nur recht wenige solcher Feiertage, aber ich möchte das im Laufe der Zeit ausbauen (…ähm, erweitern…). Neu an Bord ist der amerikanische Unabhängigkeitstag (Independence Day, 4. Juli). Ferner wird der Tag der deutschen Einheit jetzt auch auf diese Weise behandelt.
Haben Sie Ideen oder Wünsche für meine App? Schreiben Sie mir…

2010-08-30

Ein bischen Apps-Arithmetik

Als Bummler zwischen den Welten lese ich selbstverständlich auch ein Weblog rund um das Apple-Ökosystem: theAppleBlog. Dort ist zu lesen, dass Cupertinos AppStore derzeit eine Viertel Millionen Apps anbietet – eine ohne Zweifel beeindruckende Zahl. Der Weblog-Beitrag bezieht sich dabei übrigens auf Auswertungen der Site 148Apps.biz. Nun kann man dem Beitrag entnehmen, dass derzeit etwa 50.000 Entwickler knapp 630 Anwendungen pro Tag hochladen. Rein rechnerisch ergibt das 0,0126 Apps pro Entwickler und Tag. Nochmal umgerechnet vergehen also etwa 80 Tage (1 / 0,0126), bis derselbe Entwickler wieder eine Anwendung heraus bringt. Was halten Sie davon? Wie lange würden Sie für den Bau einer App veranschlagen? Schreiben Sie mir…

2010-08-28

AccountManager: dem Geheimnis auf der Spur

Lange Zeit hatten Anwendungsentwickler das Problem, vom Benutzer Dinge abfragen zu müssen, die dem System (seinem Handy) eigentlich bekannt sind: zum Beispiel die Daten zu seinem Google Konto. Schon seit Eclair gibt es deshalb die Klasse AccountManager, die als zentrale Speicherstelle der vom Benutzer angemeldeten Onlinekonten fungiert. Die Idee ist, sich vom AccountManager einfach ein so genanntes Token geben zu lassen, um dann mit diesem Token auf einen Webservice zuzugreifen. Damit müsste es dann eigentlich ganz einfach sein, zum Beispiel mit Google Contacts oder Google Calendar zu reden. …oder…?

Wer sich mal den Spaß gemacht hat, das Web daraufhin abzusuchen, wird wahrscheinlich zu dem Schluss kommen, dass es so ohne weiteres offenbar doch nicht geht. …geht ja wohl! Wie üblich bei mir, zunächst der Quelltext:

package com.thomaskuenneth;

import java.io.InputStream;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

public class TKSimpleTasks extends Activity {

  private static final String TAG = TKSimpleTasks.class.getSimpleName();

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    AccountManager am = AccountManager.get(this);
    Account[] accounts = am.getAccountsByType("com.google");
    if (accounts.length < 1) {
      finish();
    }
    String authTokenType = "cl";
    Bundle options = null;
    am.getAuthToken(accounts[0], authTokenType, options, this,
        new AccountManagerCallback<Bundle>() {

          public void run(AccountManagerFuture<Bundle> arg0) {
            try {
              Bundle b = arg0.getResult();
              String token = b
                  .getString(AccountManager.KEY_AUTHTOKEN);
              Log.d(TAG, token);

              HttpGet get = new HttpGet(
                  "https://www.google.com/calendar/feeds/default/owncalendars/full");
              get.addHeader("Authorization", "GoogleLogin auth="
                  + token);

              HttpClient httpclient = new DefaultHttpClient();
              HttpResponse r = httpclient.execute(get);
              InputStream is = r.getEntity().getContent();
              StringBuilder sb = new StringBuilder();
              int ch;
              while ((ch = is.read()) != -1) {
                sb.append((char) ch);
              }

              ((TextView) findViewById(R.id.tv)).setText(sb
                  .toString());

            } catch (Throwable thr) {
              Log.e(TAG, thr.getMessage(), thr);
            }
          }
        }, null);
  }
}

Zunächst holt man sich eine Instanz des AccountManagers:

AccountManager am = AccountManager.get(this);

Anschließend sucht man nach geeigneten Konten:

Account[] accounts = am.getAccountsByType("com.google");

Das in der Einleitung angesprochene Token erhalten Sie durch Aufruf der Methode getAuthToken. Der eigentlich spannende Teil diesbezüglich steht hier:

Bundle b = arg0.getResult();
String token = b.getString(AccountManager.KEY_AUTHTOKEN);

Sie werden jetzt vielleicht sagen, dass Sie ähnliche Quelltextfragmente recht oft finden. Das stimmt. Aber ich behaupte, kaum jemand schreibt, was es mit dem ominösen authTokenType auf sich hat. Damit steuern Sie, auf welchen Teil oder Bereich eines Google Kontos Sie Zugriff haben möchten. Das Beispiel liest den Kalender aus. Das eigentliche Lesen ist hier nur skizzenhaft ausprogrammiert. Ganz wichtig – und das werden Sie sehr selten finden – ist das Setzen des Tokens im Header des HTTP-GETs.

Bitte beachten Sie noch, dass Ihre Anwendung die Rechte GET_ACCOUNTS, USE_CREDENTIALS und INTERNET braucht. Haben Sie Fragen, Anregungen oder Kommentare? Schreiben Sie mir.

2010-08-22

Netzwerk-Status abfragen

Android bietet eine ganze Reihe von Auskunftsfunktionen. Heute zeige ich Ihnen, wie Sie den Netzwerk-Status ermitteln. Wie üblich, zunächst das – diesmal extrem kurze – Progrämmchen:

package com.thomaskuenneth;

import android.app.Activity;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.widget.TextView;

public class TestActivity extends Activity {

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    ConnectivityManager connMgr =
(ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);

    NetworkInfo mobile = connMgr
        .getNetworkInfo(ConnectivityManager.TYPE_MOBILE);

    String roaming =
"roaming ist " + (mobile.isRoaming() ? "ein" : "aus");

    ((TextView) findViewById(R.id.view)).setText(roaming);
  }
}

Das Ermitteln einer ConnectivityManager-Instanz verläuft nach dem Ihnen bereits gut bekannten Muster. Mit getSystemService wird ein bestimmter Service abgefragt und das Ergebnis auf den gewünschten Typ gecastet. Die Methode getNetworkInfo liefert Informationen zu Netzwerktypen. Im konkreten Fall (ConnectivityManager.TYPE_MOBILE) möchte ich wissen, ob im aktuell verwendeten Netz geroamt wird. Hierfür gibt es eine eigene Methode, isRoaming. Damit das Ganze auch funktioniert, muss die App das Recht android.permission.ACCESS_NETWORK_STATE anfordern.

2010-08-20

Schon gesehen? - Zauberhaftes Unboxing

In meinem Bericht über das iPad hatte ich geschrieben, dass ich auf ein Unboxing verzichte, weil es das tausendfach im Netz gibt. Heute bin ich auf eine besonders charmante Variante gestoßen. Aber sehen Sie selbst...

2010-08-19

Lichtgestalten – wieder Spaß mit Sensoren

Wenn ich es sogar mit dem folgenden Screenshot schaffe, Ihre Aufmerksamkeit zu erregen, kann ich mich wirklich glücklich schätzen… :-)

Screenshot des Testprogramms für den Helligkeitssensor  
Screenshot des Testprogramms für den Helligkeitssensor

In dieser Folge der Reihe Spaß mit Sensoren zeige ich Ihnen, wie Sie den Helligkeitssensor von Androiden auslesen. Zunächst der Quelltext der Anwendung:

package com.thomaskuenneth;

import android.app.Activity;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.widget.TextView;

public class Test extends Activity {

  private TextView view;

  private SensorManager manager;
  private SensorEventListener listener;
  private Sensor sensor;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    view = (TextView) findViewById(R.id.view);

    manager = (SensorManager) getSystemService(SENSOR_SERVICE);
    listener = new SensorEventListener() {

      public void onAccuracyChanged(Sensor sensor, int accuracy) {
      }

      public void onSensorChanged(SensorEvent event) {
        if (event.values.length > 0) {
          float light = event.values[0];
          view.setText(Float.toString(light));
        }
      }

    };
   
    sensor = manager.getDefaultSensor(Sensor.TYPE_LIGHT);
    if (sensor != null) {
      manager.registerListener(listener, sensor,
          SensorManager.SENSOR_DELAY_NORMAL);
    } else {
      view.setText("keinen Helligkeitssensor gefunden");
    }
  }

  @Override
  protected void onDestroy() {
    if (sensor != null) {
      manager.unregisterListener(listener);
    }
    super.onDestroy();
  }
}
Java2html

Ein Großteil des Listings ist bereits bekannt; zunächst wird eine Referenz auf einen SensorManager besorgt und im Anschluss ein SensorEventListener registriert. Der einzige Unterschied zum letzten Beispiel besteht darin, dass ich die Methode getDefaultSensor aufrufe. Das ist einerseits praktisch, weil ich nicht wie bei getSensorList über die Liste iterieren muss. Um zu prüfen, ob ein entsprechender Sensor (im Beispiel ist dies Sensor.TYPE_LIGHT) vorhanden ist, reicht also eine Abfrage auf null. Allerdings sagt die Doku, dass es sich bei dem gelieferten Sensor um einen zusammengesetzten oder kombinierten Sensor handeln kann, dessen Daten möglicherweise gemittelt oder gefiltert wurden. Ob das in der Praxis tatsächlich Auswirkungen hat, habe ich beim Helligkeitssensor meines Nexus One nicht weiter überprüft.

Übrigens hat Wikipedia eine schöne Übersicht, die Werte auf Lichtverhältnisse abbildet. Erweitern Sie doch mein Progrämmchen und machen es zu einer hübschen Helligkeitssensor-App (sofern es die nicht schon ohnehin gibt :-)).

2010-08-10

Rasender Reporter

Heute möchte ich Ihnen zeigen, wie leicht sich Androiden in mobile Diktiergeräte verwandeln lassen. Dies bedeutet:

  • Aufnehmen von Geräuschen mit dem eingebauten Mikrofon
  • Abspielen der Aufnahme

Der erste Teil, das Aufnehmen, ist sehr schnell erledigt:

protected void recordToFile() {
  recorder = new MediaRecorder();
  recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
  recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
  recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
  File f = new File(Environment.getExternalStorageDirectory(),
      "recording.3gp");
  try {
    f.createNewFile();
    recorder.setOutputFile(filename = f.getAbsolutePath());
    recorder.prepare();
    recorder.start();
  } catch (IOException e) {
    Log.d(TAG, "could not start recording", e);
  }
}
Java2html

Ruft man diese Methode auf, wird das durch das Mikrofon aufgenommene Audiosignal in eine Datei auf der externen Speicherkarte geschrieben.

Stellen Sie sich eine Anwendung mit zwei Schaltflächen zum Starten und Stoppen der Aufnahme vor. Dann könnte das reagieren auf Klicks auf den Start-Knopf so aussehen:

startRecording.setOnClickListener(new View.OnClickListener() {
  public void onClick(View v) {
    startRecording.setEnabled(false);
    stopRecording.setEnabled(true);
    stopRecording.requestFocus();
    recordToFile();
  }
});
Java2html

Das Anklicken des Stopp-Knopfs beendet die Aufnahme und startet die Wiedergabe. Dies widerum könnte man so realisieren:

stopRecording.setOnClickListener(new View.OnClickListener() {
  public void onClick(View v) {
    stopRecording.setEnabled(false);
    // die Aufnahme stoppen
    recorder.stop();
    recorder.release();
    // die Datei abspielen
    playAudioFile(filename);
  }

});
Java2html

Bleibt eigentlich nur noch ein Blick auf die Abspielroutine:

protected void playAudioFile(String filename) {
  final MediaPlayer player = new MediaPlayer();
  player.setOnCompletionListener(new OnCompletionListener() {

    public void onCompletion(MediaPlayer player) {
      player.release();
      startRecording.setEnabled(true);
      startRecording.requestFocus();
    }
  });
  try {
    player.setDataSource(filename);
    player.prepare();
    player.start();
  } catch (Throwable thr) { // IllegalArgumentException,
    // IllegalStateException, IOException
    Log.d(TAG, "could not play audio", thr);
  }
}
Java2html

Diese Methode bringt insofern ein klein wenig Würze in die doch sehr einfachen Tätigkeiten, als sie einen Listener registriert, der nach dem Ende des Abspielvorgangs in Aktion tritt. Er gibt die nun nicht mehr benötigten Ressourcen frei und reaktiviert eine Schaltfläche – die Starttaste sollte natürlich während einer Wiedergabe nicht angeklickt werden können.

2010-08-08

Hands on - iPad

Dieses Weblog dreht sich vor allem um Java und Android. Gelegentlich müssen Sie, liebe Leserin, lieber Leser, aber auch mit Posts zu anderen mobilen Geräten rechnen. Heute ist so ein Tag. :-)

Dieser Beitrag handelt nämlich von meinem neuesten Gadget, ein iPad 32 GB, nur W-LAN. Meine Freunde und Kollegen wissen, dass ich so einen Kauf nie kategorisch ausgeschlossen habe, insofern sollte der Schock nicht allzu groß ausfallen. :-) Die Frage, warum man sich als Android-Freak ein iPad holt, nagt vielleicht aber doch. Dabei ist die Antwort hierauf vergleichsweise einfach. Nichts ist hinderlicher für gute Arbeit als Scheuklappen. Um die Konzepte der "eigenen" Plattform wirklich zu verstehen, muss man auch andere kennen. Und - Apple liefert in vielen Punkten verdammt überzeugende Konzepte.

So, das obligatorische Unboxing wurde unzählige Male dokumentiert, so dass dieser Post gerne darauf verzichten kann. Ich möchte mich vielmehr darauf konzentrieren, Ihnen die ersten Eindrücke zu vermitteln. Die Apple-typisch sehr schöne Verpackung enthält nur ganz wenige Teile - das iPad, ein USB-Kabel, ein Netzteil und ein kleines Mäppchen mit Apple-Stickern und gaaaaaaanz wenig Dokumentation. Nach dem Anschließen an den Rechner möchte das Gerät aktiviert und registriert werden, wobei die Registrierung mit einem "unbekannten Fehler" scheiterte. ...naja, dann eben keine Werbe-Mails... Nach dem fast schon obligatorischen Firmware-Update hat mein Mac meine iTunes-Bibliothek auf das iPad geschaufelt.

Da ich bisher kein iOS-Gerät benutzt habe, war das Entdecken des Systems richtig aufregend und spannend. Es macht Spass, sozusagen einen Deckel aufzumachen, um zu sehen, was sich in einer Kiste befindet. Das Einstellen des iPads, das Anpassen an die eigenen Bedürfnisse, das Konfigurieren von Browser und E-Mail-Client gehen Apple-typisch flott und elegant von statten. Nach wenigen Minuten bin ich mit dem heimischen Funknetz verbunden und lese die Nachrichten meines Google Mail-Kontos. Auch der Erstkontakt mit den mobilen Versionen der Apple-Software (iTunes, App-Store, ...) verläuft freundlich. Und dann die ersten Apps - Google Earth, Kindle for iPad, ein Autorennen, das Wort-Knobelspiel Bookworm - was ich dem iPad auch vorsetze, es geht souverän damit um. Die Reaktionsgeschwindigkeit ist hoch, das System antwortet prompt, Animationen sind praktisch immer butterweich.

Die Handhabung... Flach auf den Tisch legen macht definitiv keinen Spaß, ein Dock oder ein Ständer stand noch nicht zur Verfügung. Also musste ich das gute Stück in der Hand halten. Das fühlt sich an sich ganz gut an, das Gewicht des Geräts zieht aber nach einer geraumen Zeit doch merklich "nach unten". Die Konsequenz ist, dass man regelmäßig die Haltung wechselt, mal hat man das iPad in der linken, dann wieder in der rechten Hand. Tolles Display hin oder her, auf diese Weise ein Buch lesen möchte ich definitiv nicht. Da ist mir mein - sorry Apple - Kindle merklich lieber.

Ich habe mir das iPad als komfortables Surfbrett für zuhause geholt. Hier macht es einen exzellenten Job. Auch als "Monster iPod" hat es ohne Frage Klasse, aber fürs Video Gucken muss in jedem Fall ein Dock her. Ist das iPad ein Gerät für draußen? Ganz ehrlich, ich würde es nicht ständig mit mir herum tragen wollen.

2010-08-06

Hands on – UI Stencils für Android

Vor einiger Zeit hatte ich in meinem Eintrag Scribbeln in seiner schönsten Form über Zeichenschablonen für das UI-Prototyping von iPhone-Anwendungen berichtet. Einer meiner Leser hatte mich vor kurzem (leider nur per E-Mail :-)) darauf aufmerksam gemacht, dass es diese Schablonen nun auch für Android gibt. Da ich neugierig bin, habe ich mir das Android Stencil Kit aus den USA kommen lassen. Zunächst das obligatorische Unboxing:
Die noch ungeöffnete Verpackung
Die noch ungeöffnete Verpackung
Der Inhalt des Umschlags
Der Inhalt des Umschlags
Schablone, Bleistift und Sticker
Schablone, Bleistift und Sticker
Bestellt habe ich das Ganze am 27. Juli. Die Zeit, die die Schablone über den großen Teich gebraucht hat, ist also recht kurz gewesen. Auch der Zustellprozess war absolut reibungslos – kein Gang zum Zollamt nötig.
Die Schablone selbst macht einen wertigen Eindruck, ist allerdings recht dünn. Ob sie eine raue Behandlung während des Transports schadlos überstanden hätte, vermag ich nicht zu sagen. Für die tägliche Arbeit sollte sie aber gut gerüstet sein. Auch der mitgelieferte Druckbleistift mit Radierer (ZEBRA Modell #2) gefällt. Seine Optik ist einem echten Bleistift nachempfunden – ich war ehrlich gesagt für einen winzigen Augenblick drauf und dran, ihn anzuspitzen. :-) Seine Mine ist 0,7 mm dick/dünn.
Ein erster Versuch
Ein erster Versuch
Das Bild oben zeigt meine erste Skizze. Offen gesagt musste ich etwas üben, bis die Mine des Stifts zielsicher die Aussparungen in der Schablone traf – wobei ich dies vor allem meinem zeichnerischen Nicht-Talent zuschreibe. :-) Nach etwas Übung sollte das Scribbeln aber flott von der Hand gehen. Empfehlen möchte ich, sich von der Homepage des Herstellers eine Vorlage in Gestalt eines PDFs herunter zu laden, dass schemenhaft ein fiktives Gerät vorgibt und so das Zeichnen wesentlich erleichtert.
Ausgedruckte Vorlage
Ausgedruckte Vorlage
Die Vorlage enthält Felder, um beispielsweise Projektnamen und Datum zu erfassen.
Die Schablone “simuliert” einen Bildschirm mit 480 mal 800 Punkten auf etwa 7,3cm mal 11cm. Einige Bildschirmsymbole, beispielsweise die Zoom-Schaltflächen werden durch mehrere Aussparungen auf der Schablone zusammengesetzt. Alles in allem macht die Arbeit mit diesem Hilfsmittel sehr großen Spaß; die Ergebnisse wirken, zumindest aus der Hand des Laien, wesentlich ästhetischer als reine Von-Hand-Zeichnungen. Seinen Preis ist das Paket meiner Meinung nach in jedem Fall wert.