Sonntag, 17. Mai 2015

Know thy tags...

Mit den beiden Swing-Klassen RTFEditorKit und HTMLEditorKit lässt sich recht schnell ein einfacher HTML-nach-RTF-Converter schreiben. Wie, ist unter anderem in diesem Stackoverflow-Post beschrieben.

Aufpassen muss man allerdings bei einigen Tags. Zumindest <em> und <strong> werden nicht in RTF-Codes umgesetzt, man sollte sie also von Hand in <b> und <i> konvertieren.

Freitag, 15. Mai 2015

Mac OS X-Quicktipp: eingebauten Webserver nutzen

Mac OS X hat schon seit vielen Versionen einen eigenen Webserver an Board. Früher konnte man ihn über ein eigenes Modul in den Systemeinstellungen konfigurieren. Unglücklicherweise hat Apple es vor einiger Zeit entfernt. Der Webserver an sich ist aber noch vorhanden und funktoniert auch.

Bevor man ihn mit sudo apachectl start startet, legt man den Inhalt unter /Library/WebServer/Documents ab. Für den Schreibzugriff auf dieses Verzeichnis sind Admin-Rechte erforderlich.

Danach kann man im Browser mit http://localhost/... auf die Seiten zugreifen.

Weitere Kommandos für apachectl sind stop und restart.

Zwar bringen viele Tools und Sprachen ihren eigenen Webserver mit, aber ich finde, es ist gut zu wissen, dass man auch ohne Download schnell mal lokale Tests aufsetzen kann.

Donnerstag, 14. Mai 2015

Mac-Quicktipp: Schlagschatten in Screenshots loswerden

Kennen Sie das auch? Dinge, die nur ein klein wenig nerven, toleriert man recht lange. Bei mir waren das die Schlagschatten, die Mac OS X Screenshots hinzufügt. Dabei sind nur zwei Eingaben im Terminal nötig, um Bloggern das Leben leichter zu machen:

defaults write com.apple.screencapture disable-shadow true schaltet den Schlagschatten aus.

killall SystemUIServer sorgt dafür, dass die Änderungen übernommen werden.

Samstag, 9. Mai 2015

Living in the past (Teil 2)

In meinem vorherigen Post habe ich angedeutet, dass man unter neueren Android-Versionen Live Folder noch nutzen kann, obwohl Google die korrespondierenden Klassen mit API-Level 14 für veraltet erklärt und den Zugriff über den Home Screen ausgebaut hat.

Das Installieren einer App, die Live Folder bereitstellt, klappt selbst unter Android 5.1 noch reibungslos. Die beteiligten Frameworksklassen sind also (zum Glück) noch vorhanden. Eigentlich ist das auch logisch, denn das harte Entfernen hielte ich schon für einen ziemlich signifikanten Einschnitt. Über den Home Screen ist das Hinzufügen von Live Foldern aber nicht mehr möglich. Die Frage ist also, wie man Zugriff erhält. Hierzu muss man wissen, dass Live Folder keine Benutzeroberfläche bereitstellen, sondern nur die Daten, und zwar in Gestalt eines (auf eine ganz bestimmte Weise konfektionierten) Content Providers.

Die Idee ist deshalb, selbst für die UI zu sorgen, und den Content Provider einfach nach seinen Daten zu fragen. Die folgende Activity zeigt einen entsprechenden Proof of Concept, den ich Tin Opener genannt habe. Ich werde die App in den nächsten Tagen auf Bitbucket bereitstellen. Wenn das erledigt ist, füge ich diesem Post einfach einen Kommentar hinzu.

public class TinOpenerActivity extends ListActivity 
    implements LoaderManager.LoaderCallbacks<Cursor> {

    private static final String TAG = 
            TinOpenerActivity.class.getSimpleName();
    private static final int RQ_PICK_LIVE_FOLDER = 42;
    private static final int URL_LOADER = 0;

    private Uri uri;
    private String[] mFromColumns = {
            LiveFolders.NAME, LiveFolders.DESCRIPTION
    };
    private int[] mToFields = {
            android.R.id.text1, android.R.id.text2
    };
    private SimpleCursorAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setListAdapter(adapter = new SimpleCursorAdapter(this, 
                android.R.layout.simple_list_item_2, null, 
                mFromColumns, mToFields, 0));
        getListView().setOnItemClickListener(
            new AdapterView.OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> parent, 
                                    View view, int position, 
                                    long id) {
                Cursor c = (Cursor) adapter.getItem(position);
                if (c != null) {
                    String url = c.getString(
                        c.getColumnIndex(LiveFolders.INTENT));
                    if (url != null) {
                        Intent i = new Intent(Intent.ACTION_VIEW);
                        i.setData(Uri.parse(url));
                        startActivity(i);
                    }
                }
            }
        });
        Intent i = new Intent(LiveFolders.ACTION_CREATE_LIVE_FOLDER);
        Intent chooser = Intent.createChooser(i, 
                getString(R.string.choose));
        startActivityForResult(chooser, RQ_PICK_LIVE_FOLDER);
    }

    @Override
    protected void onActivityResult(int requestCode, 
                                    int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if ((RESULT_OK == resultCode) && 
                (RQ_PICK_LIVE_FOLDER == requestCode)) {
            if (data != null) {
                uri = data.getData();
                if (uri != null) {
                    getLoaderManager().initLoader(URL_LOADER, 
                            null, this);
                }
            }
        }
    }

    @Override
    public Loader<Cursor> onCreateLoader(int loaderID, 
                                         Bundle args) {
        switch (loaderID) {
            case URL_LOADER:
                return new CursorLoader(this, uri, 
                        null, null, null, null);
            default:
                return null;
        }
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        adapter.changeCursor(data);
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
    }
}

Die App TinOpener ist sehr einfach gestrickt. Sie besteht aus einer ListActivity, die ihre Daten von einem SimpleCursorAdapter bezieht. So wie sich das gehört, geschieht das Laden mit Hilfe eines Loaders im Hintergrund.

Die eigentlich spannende Frage ist, wie Live Folder gefunden werden. Hierfür baue ich ein Intent mit der Action LiveFolders.ACTION_CREATE_LIVE_FOLDER und lasse es auf die systemeigene Intent-Auswahl los. Sind mehrere Live Folder installiert, kann der Benutzer einen auswählen. Ist nur einer vorhanden, wird dieser automatisch angezeigt.

Statt in eine Activity könnte man dies auch in ein Appwidget packen. Na, hat jemand Lust darauf?

Nachtrag: Ja, das Listing enthält keine Kommentare... Ich werde dies in der Version auf Bitbucket nachholen. Und den Fall berücksichtigen, dass gar kein Live Folder vorhanden ist. Dann sollte zumindest ein entsprechender Hinweis erfolgen.

Donnerstag, 7. Mai 2015

Living in the past (Teil 1)

In diesem Post schwelgen wir in einer (weit zurück liegenden) Vergangenheit…

Android hat viele Features kommen und gehen sehen. Ein schönes Beispiel hierfür sind die sogenannten Live Folder. Diese wurden mit API-Level 3 (Cupcake) eingeführt und mit API-Level 14 (Ice Cream Sandwich) für veraltet erklärt. Hier ein paar Screenshots unter Android 2.3.7.

Tippen und Halten an einer freien Stelle des Home Screens öffnet das folgende Popup-Menü:

Live Folder #1

Tippt man auf Folders, öffnet sich ein weiteres Menü. Hier ist eine App von mir, die Live Folder zur Verfügung stellt, zu sehen. LiveFolderDemo war Bestandteil meines Buches Android 3.

Live Folder #2

In der Folgeauflage, Android 4, musste ich das Projekt schon wieder entfernen, da Google die API für veraltet erklärt und in der Entwicklerdoku mit deutlichen Worten von der Nutzung abgeraten hat:

Screenshot der Entwicklerdoku

Tippt man auf den Namen eines Live Folders, wird ein Icon auf dem Hintergrund des Home Screens abgelegt.

Live Folder #3

Das Antippen des Icons öffnet den Live Folder.

Live Folder #4

Ich habe auf Bitbucket ein Reopsitory abgelegt, das die App LiveFolderDemo enthält. Denn auch wenn man den Code in modernen Android-Versionen (eigentlich…) nicht mehr nutzen kann, wäre es doch schade, in einfach so zu löschen…

Und wenn Sie wissen wollen, was es mit eigentlich auf sich hat, warten Sie einfach auf einen der folgenden Posts.

Sonntag, 3. Mai 2015

TKWeek 1.8.0

Nach einer etwas längeren Pause gibt es ein Update von TKWeek. Es waren ein paar Anpassungen an Lollipop nötig. Insbesondere die Navigation musste ich anpassen. Jetzt sollte aber wieder alles wie gewohnt funktionieren.

Screenshot TKWeek

Samstag, 18. April 2015

Android-Apps im Browser

Auf der Google IO 2014 wurde ein Proof of Concept gezeigt, der einige Android-Apps unter Chrome OS lauffähig machte. Im September desselben Jahres folgte dann eine erste Beta-Version der App Runtime for Chrome. Die Software basiert auf Googles Sandbox-Lösung Native Client. Seit einiger Zeit kann über den Chrome Web Store die Beta-Version einer App namens ARC Welder geladen werden. Das englische Verb to weld bedeutet schweißen – passend dazu zeigt das Programmsymbol den kleinen grünen Roboter mit Schutzbrille. Das Tool integriert eine .apk-Datei in die Laufzeitumgebung. Wie man hierzu vorgeht, ist auf den Chrome-Entwicklerseiten beschrieben. Als Appetithappen ein paar Screenshots:

ARC Welder Setup (1)
ARC Welder Setup (1)

ARC Welder Setup (2)
ARC Welder Setup (2)

ARC Welder Setup (3)
ARC Welder Setup (3)

Meine App TKWeek im Browser
Meine App TKWeek im Browser

Was Google mit ARC noch vorhat, ist nicht ganz klar. Tatsache ist, dass Besitzern von Chromebooks auf einen Schlag eine Unmenge an Programmen zur Verfügung stehen würde, wenn sie Apps direkt aus dem Play Store herunterladen könnten. Das Gleiche gilt für Windows-, Linux- und Mac OS X-Anwendern, die Chrome als Browser nutzen. Das Bundlen einiger weniger, vorausgewählter Apps würde doch den ganzen Aufwand kaum rechtfertigen, oder? Derzeit müssen .apk-Dateien einzeln installiert werden. Um ein Programm in die Laufzeitumgebung zu übernehmen, muss also das Installationsarchiv vorliegen. Dies wird Google dem klassischen Endanwender nicht zumuten wollen.

Note to myself: Ich muss bei Gelegenheit mal herausfinden, ob sich ARC-Apps kennen, oder jeweils in ihrer eigenen Welt leben.

Die Ausführungsgeschwindigkeit von TKWeek war auf meinem Surface 3 Pro übrigens recht gut. Dass ARC noch mit Stabilitätsproblemen kämpft, ist angesichts des Beta-Status völlig ok. Wie auch der Android-Emulator kämpft ARC derzeit mit der Hardwareanbindung: es stehen nur sehr wenige Sensoren zur Verfügung.

Dienstag, 14. April 2015

Seifenkisten auf Abwegen

Der Zugriff auf (üblicherweise) leichtgewichtige REST-Webservices ist unter Android kein Problem – sein Java-Erbe verhilft dem kleinen grünen Roboter  zu einem ansehnlichen (Netz)Werk(zeug)-Koffer. Nur mit den (meist) etwas schwergewichtigeren SOAP-Webservices will es “out of the box” nicht so recht klappen. Zwar hat Java mit JAX-WS ab Java SE 6 entsprechende Klassenbibliotheken an Board, aber Androids Harmony-Spin-off ist irgendwie bei Java 1.5 stehen geblieben (naja, skurrile Erweiterungen und Portierungen mal außen vor gelassen). Glücklicherweise gibt es zu jedem Java Specification Request eine Referenzimplementierung. Bis einschließlich JAX-WS RI 2.2.6 ist “nur” Java SE 5 erforderlich. Warum also nicht einfach die Runtime-Bibliotheken mit einer Android-App ausliefern, und auf diese Weise auf SOAP-Services zugreifen können? Der Abschnitt Jar dependency der Release Notes listet auf, welche Bibliotheken der App hinzugefügt werden müssen. Eigentlich könnte man das Unterfangen an dieser Stelle abbrechen, denn die 18 Jars bringen üppige 4,8 MB auf die Waage. Wer möchte das schon als zusätzlichen Ballast mitgeben, “nur” um einen Webservice aufzurufen? Aber falls der Forscherdrang siegt, und man die Jars in das libs-Verzeichnis des Android-Projekts kopiert, wartet die nächste unangenehme Überraschung.

Screenshot: Fehlermeldung beim Hinzufügen eines Jars
Screenshot: Fehlermeldung beim Hinzufügen eines Jars

Google nennt den Versuch, Jars hinzuzufügen, die bestimmte Paket-Namensräume abdecken, “Ill-advised or mistaken usage of a core class” und unterbindet die Integration. Fairerweise muss man sagen, dass die Motivation durchaus legitim ist. Bestimmte Paketnamen sind eben der Standardklassenbibliothek vorbehalten. Schade nur, wenn sie dann nicht vorhanden sind. Glücklicherweise lässt Google dem Entwickler ein Hintertürchen offen, in Gestalt der Option --core-library, die man dem monierenden Tool (dex) übergeben kann. Wie, ist freilich kaum herauszubekommen. Ganz so einfach geht das nämlich nicht, wenn man mit Hilfe von Gradle baut. Hier ein Rezept, das zumindest bei mir funktioniert. In der Modul-spezifischen build.gradle-Datei ist folgendes Quelltextfragment einzufügen:

   dexOptions {  
     preDexLibraries = false  
   }  
   project.tasks.withType(com.android.build.gradle.tasks.Dex) {  
     additionalParameters=['--core-library']  
   }  

Jetzt klappt der Build zwar immer noch nicht, aber zumindest erhält man eine andere Fehlermeldung.

Screenshot: Fehlermeldung beim Build
Screenshot: Fehlermeldung beim Build

Auch dies bekommt man in den Griff, indem man build.gradle abermals erweitert:

   packagingOptions {  
     exclude "META-INF/LICENSE"  
   }  
   

Nun lässt sich das Projekt bauen und starten. Leider klappt der Zugriff auf den Webservice aber trotzdem nicht. In der Konsole findet man die Meldung, dass die Klasse java.awt.Image nicht gefunden wird.

Screenshot: Klasse java.awt.Image nicht gefunden
Screenshot: Klasse java.awt.Image nicht gefunden

Tja, schade. Wieder ein guter Zeitpunkt, das ganze sein zu lassen.

Zwinkerndes Smiley

Aber hey, warum nicht weiter forschen…? Dass es eigentlich nicht funktionieren kann, “einfach so” eine entsprechende Klasse zu bauen, ist logisch. Aber vielleicht lässt sich auf diese Weise ja herausfinden, was von java.awt.Image benötigt wird. Eine leere Implementierung lässt den Start-Vorgang dann auch etwas später abbrechen. Diesmal fehlt javax.activation.DataHandler. Diese Klasse gehört zum JavaBeans Activation Framework. Hierzu könnten Sie dessen activation.jar in das libs-Verzeichnis kopieren. Ich spare mir das und lege stattdessen wieder eine Dummy-Klasse an. Warum, sehen Sie gleich…

Screenshot: Fehler beim Initialisieren der Klasse DatatypeFactory
Screenshot: Fehler beim Initialisieren der Klasse DatatypeFactory

Diesmal schlägt das Initialisieren der Klasse DatatypeFactory fehl, weil eine andere (die Implementierungsklasse) nicht vorhanden ist:

Screenshot: Die Klasse org.apache.xerces.jaxp.datatype.DatatypeFactoryImpl ist nicht vorhanden
Screenshot: Die Klasse org.apache.xerces.jaxp.datatype.DatatypeFactoryImpl ist nicht vorhanden

Das ist sogar vergleichsweise einfach erklärbar, weil es so spezifiziert wurde. Die Doku der Klasse DatatypeFactory beschreibt den Initialisierungsvorgang, beim Aufruf der Methode newInstance(). Dort ist auch Folgendes zu lesen: “Note that you must supply your own implementation (such as Xerces); Android does not ship with a default implementation.” Die Idee ist nun, den Teil von Xerces hinzuzufügen, der die fehlende Klasse enthält. Es gibt sogar ein passendes Jar, xercesImpl.jar. Wiederstehen Sie aber der Versuchung, die Datei in das libs-Verzeichnis zu kopieren. Dann nämlich gibt es eine Fehlermeldung, die meiner Meinung nach mit dem zu alten Klassenformat der Binärdistribution zu tun hat. Der Bau von Xerces aus den Sourcen klappt auch nicht so ohne Weiteres, weil weitere Libs aus dem Apache-Fundus vonnöten sind.

Irgendwie frustrierend, oder? Es scheint einen vermeintlichen Rettungsanker zu geben, in Gestalt einer Android-Portierung von Xerces. Aber auch die hilft nicht, weil alle Paketnamen mit dem Präfix mf. versehen wurden. Deshalb findet DatatypeFactory die benötigte Implementierung DatatypeFactoryImpl nicht (sie befindet sich ja im Paket mf.org.apache.xerces.jaxp.datatype). Wenn Sie jetzt sagen “Dann nutze ich eben die Übergabe zum Beispiel mit System.setProperty()”, muss ich Sie wieder enttäuschen. Dann gibt es zur Laufzeit eine Exception, weil die Implementierung nicht von der originalen DatatypeFactory ableitet, sondern der aus einem Paket, das mit mf. beginnt.

Wie geht es jetzt weiter? Nun, ich habe als letzten Versuch die wenigen benötigten Klassen aus der Xerces-Source-Distribution in das Projekt kopiert. Damit wird tatsächlich alles gefunden. Glauben Sie, es funktioniert?

Screenshot: Noch eine Exception
Screenshot: Noch eine Exception

Man könnte natürlich noch analysieren, was es mit den IllegalAnnotationExceptions auf sich hat, aber darauf hatte ich dann doch keine Lust mehr. JAX-WS und Android mögen sich also offensichtlich nicht besonders gerne. Wie man aber doch noch auf SOAP-Services zugreifen kann, lesen Sie in einer der kommenden KaffeeKlatsch-Ausgaben.

Sonntag, 5. April 2015

Outtakes, 20150405

Wussten Sie, dass Sie ab Android 5.1 android.text.format.Time nicht weiter verwenden sollten? Die Klasse war als Alternative zu java.util.Calendar und java.util.GregorianCalendar gedacht. Ein Time-Objekt repräsentiert einen Zeitpunkt, der sekundengenau angegeben werden kann. Die Klasse ist nicht threadsicher und berücksichtigt keine Schaltsekunden. Zudem sind mehrere Problemen bekannt: Für die Berechnung werden derzeit 32-Bit-Integerzahlen verwendet. Deshalb können nur Werte zwischen 1902 und 2037 verlässlich dargestellt werden. Ferner führt der Aufruf von switchTimezone() zu einem falschen Datum, wenn der aktuelle Wert aufgrund eines Sommerzeit-/Winterzeit-Übergangs gar nicht vorkommen kann. Schließlich funktionieren Parse- und Formatieroperationen nur mit dem ASCII-Zeichensatz zuverlässig. Google rät deshalb dazu, stattdessen GregorianCalendar zu verwenden. Skurril…

PS: Nicht wundern - dass ich für Klassen aus der Java-Standardklassenbibliothek Links in die Android-Doku setze, ist gewollt

Dienstag, 31. März 2015

Google wäre nicht Google…

…wenn es nicht auch in Android 5.1 Änderungen an der Oberfläche gegeben hätte. Der folgende Screenshot zeigt eine Beispiel-App unter Android 5.0.

Screenshot Nexus 5, API-Level 21

Screenshot Nexus 5, API-Level 21

Dieselbe App nun auf demselben Nexus 5, diesmal mit API-Level 22.

Screenshot Nexus 5, API-Level 22

Screenshot Nexus 5, API-Level 22

Mal ehrlich, bringt’s das? Möglicherweise reagiere ich hier zu dünnhäutig, aber faktisch ist es kaum mehr möglich, über Android zu schreiben. Naja, vielleicht doch, denn bis es Android 5.1 auf viele Geräte schafft, vergehen ja noch viele Monate. Smiley

Donnerstag, 19. Februar 2015

Sinnlose Änderungen

Es gibt Momente, in denen man schreiend davonlaufen möchte. Änderungen um der Änderung willen. Der Stein des Anstoßes: bisher hieß auf dem Startbildschirm von Android Studio der Menüpunkt zum Importieren fremder Projekte Import Non-Android Studio project. Sagt ziemlich genau, um was es geht. Und ist kurz und bündig. Seit Android Studio 1.1.0 heißt das Ding Import project (Eclipse ADT, Gradle, etc.. Der vermeintliche Mehrwert durch die Nennung von Quellen ist lächerlich gering. Welche Aussagekraft hat etc.?

Dafür passen Screenshots in Tutorials und Büchern nicht mehr und Beschreibungen werden ungenau. Und wir Autoren müssen uns anhören, dass unsere Bücher nicht aktuell sind. Wegen so einem Unsinn. Himmel, solche sinnlosen Aktionen zeigen nur, wie weit Google sich von denen, die die Plattform groß gemacht haben, entfernt hat.

Hier der jetzt veraltete Screenshot aus einem noch nicht erschienenen Buch:

Veralteter Screenshot

Und hier ein Screenshot vom 19. Februar. Würde mich nicht wundern, wenn sie es bis zum Erscheinen des Buches noch einmal ummodeln…

"Neuer" Screenshot

Freitag, 9. Januar 2015

Hello and goodbye Surface 3 Pro

Eigentlich bin ich mit meinem Surface 3 Pro mehr als zufrieden. Das, wofür ich es haben wollte, bewältigt es mit Bravur. Surfen, Musik, Fotos und Filme gucken, Bücher und Artikel schreiben, coden… Wenn da nur nicht dieses grässliche gelbe Etwas wäre. Nein, ich meine kein Vogonen-Arbeiterschiff. Ich meine das hier:

Foto: Display eines Surface 3 Pro

Der Balken ist auch bei anderer Hintergrundfarbe zu sehen

auch bei weißen Hintergrund gut zu sehen

Der gelbe Balken auf der linken Seite der Anzeige ist etwa 1 cm breit und reicht von ganz oben bis unten über das Display.  Es wird im Web viel spekuliert, was der Defekt letztlich sein könnte. Mir war es, ehrlich gesagt, egal. Ich wollte einfach nur ein Gerät, das seiner Preisklasse entsprechend performt.

Microsoft hat, das muss ich deutlich sagen, sehr schnell ein Austauschgerät verschickt. Leider hat es dasselbe Problem, wenn auch in milderer Form. Und… es klickt gelegentlich, was das alte in den 4 Monaten, in denen es bei mir war, nicht getan hat. Ich werde aber das Austauschgerät erst einmal behalten. Warum? Trotz zeitgemäßer Backup-Strategien war ich 4 Tage offline. Das nervt. Auf die gleiche Auszeit habe ich keine Lust. Deshalb hoffe ich einfach, dass das Austauschgerät auf Dauer tut.

Hat hier jemand ein Surface 3 Pro? Kennt ihr den gelben Balken auch? Schreibt mir…

Montag, 29. Dezember 2014

Android Studio-Tipp: Projekt aus Recent Projects entfernen

Auf dem Willkommensbildschirm von Android Studio gibt es eine Liste der kürzlich geöffneten Projekte:

Screenshot: Android Studio-Willkommensbildschirm

Wenn man die leeren muss, um z. B. Screenshots für ein Buch anzufertigen Smiley, kann man mit den Cursortasten das gewünschte Projekt ansteuern oder mit der Maus darüber fahren und dann die Taste Entf drücken.

Samstag, 27. Dezember 2014

Tipp: Android-Log-Tags kurz halten

Es gilt unter Android als common practice, eine Konstante TAG zu definieren, die den Namen einer Klasse enthält, und bei Log-Ausgaben übergeben wird. Google macht das in seinen Beispielen immer so:


public class DigitalWatchFaceService extends CanvasWatchFaceService {

  private static final String TAG = "DigitalWatchFaceService";

  ..

Ich finde folgende Variante schöner, weil sie sich m. E. besser beim Refactoren verhält:


public class C64WatchFaceWearableConfigActivity extends Activity {

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

  ..

 

Das Ergebnis ist dasselbe. Allerdings ist mir heute trotzdem dieses Codeschnipsel auf die Nase gefallen, wenngleich aus einem anderen Grund.

Screenshot: Fehlerausgabe in Logcat

Die Ausgabe auf der Konsole:

java.lang.IllegalArgumentException: Log tag "C64WatchFaceWearableConfigActivity" exceeds limit of 23 characters

Stand heute sagt die Doku zur Klasse android.util.Log nichts dazu.

Fazit: Klassennamen kurz halten. Smiley

Mittwoch, 24. Dezember 2014

Linktipp: Commodore VIC-II Color Analysis

Für mein Zifferblatt im C64-Look war ich auf der Suche nach den “richtigen”, möglichst originalgetreuen Farben. Da es unzählige Emulatoren gibt, könnte man natürlich einfach einen Screenshot machen und die Farben abgreifen. Aber das verschiebt das Problem nur, denn: Wo haben die Emulatoren die Farben her? Seit es Truecolor gibt, sind wir gewöhnt, im RGB-Farbraum nach Belieben zu stöbern. Aber der C64 kannte nur 16 Farben, und die waren nirgendwo als RGB-Werte hinterlegt, sondern wurden vom Videochip erzeugt. Wie der das tut, und wie man doch noch zu RGB-Werten kommt, ist im großartigen Artikel Commodore VIC-II Color Analysis beschrieben.

Dienstag, 23. Dezember 2014

Stylisches Zifferblatt für Android Wear

Da mein Android-Buch ein Kapitel zu Android Wear enthalten wird, wollte ich die neue Watch Face API ausprobieren. Herausgekommen ist C64 Tribute Watch Face.

Für Neugierige ohne Android Wear ein Screenshot:

Screenshot: C64 Style Watch Face

Der Cursor hat zwar keine Funktion, blinkt aber (in etwa) so schnell wie ein C64.

Mittwoch, 17. Dezember 2014

Alte Dateiformate: .mdi

Beim Aufräumen meiner Festplatte bin ich über eine Datei mit der Endung .mdi gestolpert. MDI steht hier für Microsoft Office Document Imaging, ein proprietäres Format, das mit Office 2010 eingestellt wurde. Ein Doppelklick bewirkt also in Verbindung mit modernen Office-Versionen gar nichts mehr. Allerdings gibt es von Microsoft ein Commandline-Tool, das solche Dateien in TIFF umwandeln kann. Die Daten sind also dank dieses MDI to TIFF File Converters nicht verloren. Der Setup-Assistent installiert das Programm nach C:\Program Files (x86)\modiconv. Bitte bei der Nutzung in der Eingabeaufforderung beachten, dass der Vorgang nur startet, wenn der Parameter –log … mit angegeben wird. Sonst werden nur die Aufrufparameter ausgegeben. Wenn Sie das Tool nicht mehr benötigen, können Sie es wie gewohnt über die Systemsteuerung deinstallieren.

Sonntag, 14. Dezember 2014

Tipp: Navigationsproblem in der Android-Kontakte-App lösen

Meine App TKBirthdayReminder delegiert das Anlegen von Kontakten an die dafür zuständige App. Das – immer noch geniale - Intent-System macht dies möglich. Nachdem der Anwender einen neuen Kontakt hinzu gefügt hat, erscheint in der App ein Dialog, der das Geburtsdatum abfragt. So zumindest die Theorie. Ganz offenkundig hat das schon seit geraumer Zeit nicht mehr funktioniert. Da ich das Programm etwas fitter für Lollipop machen wollte, habe ich mal wieder damit  gespielt, und bin prompt in das Problem gelaufen. Die Doku weiß hierzu folgendes zu berichten:

In Android 4.0 (API version 14) and later, a problem in the contacts app causes incorrect navigation. When your app sends an edit intent to the contacts app, and users edit and save a contact, when they click Back they see the contacts list screen. To navigate back to your app, they have to click Recents and choose your app.

Glücklicherweise folgt postwendend die Lösung:

To work around this problem in Android 4.0.3 (API version 15) and later, add the extended data keyfinishActivityOnSaveCompleted to the intent, with a value of true. Android versions prior to Android 4.0 accept this key, but it has no effect.

Also ein simples…

editIntent.putExtra("finishActivityOnSaveCompleted", true);

Freitag, 12. Dezember 2014

HTML-Dialoge mit Feinschliff

Erinnern Sie sich noch an meine Frage aus meinem vorangehenden Post, was mit dem dort gezeigten Dialog nicht 100%-tig passt? Gucken Sie sich mal diese Version an:

Screenshot: HTML-Dialog mit "richtigen" Font

Es ist die Schrift. Die hier hat keine Serifen.

Swing beizubringen, in HTML nicht Times New Roman zu verwenden, ist einfach. Zum einen gibt es die Client Property HONOR_DISPLAY_PROPERTIES. Außerdem kann die Zeichensatzfamilie auch in HTML selbst gesetzt werden:

<body style="font-family: ...">

Freitag, 5. Dezember 2014

Breite von Dialogen

Was ist eine geeignete Breite für Dialoge? Die Frage mag sich komisch anhören. Was ich meine, ist folgendes… Nehmen wir an, Sie haben eine Menge Fließtext, zum Beispiel die Linzenzbestimmungen Ihrer Software. Um die anzuzeigen, bietet es sich an, sie in Swing in eine JEditorPane zu packen, die wiederum von einer JScrollPane (denken Sie an meinen vorherigen Post) gekapselt wird. Mit der JEditorPane können prima einfache HTML-Texte angezeigt werden, deren Links sogar anklickbar sind. Die Breite der Komponente ergibt sich aus der Breite der scroll pane; diese bezieht sie vom Layoutmanager des übergeordneten Containers. Hat der Dialog also in sich eine passende Größe, bekommt auch der Fließtext eine geeignete Breite. Was aber, wenn ein entsprechendes ordnendes Element fehlt?

Screenshot: viel zu breiter Dialog

Solche Monster möchte und sollte man niemandem zumuten. Zumal die Lösung recht einfach ist. Die Basis vieler Swing-Komponenten, java.awt.Component, kennt seit Java SE 5 die Methode setPreferredSize(). Der kann man eine bevorzugte Größe mit auf den Weg geben.

Nur – sind wir jetzt wirklich weiter? Woher sollen wir im Zeitalter von Hoch-DPI-Anzeigen passende Pixelwerte nehmen? Mein pragmatischer Vorschlag ist, sich am verwendeten Zeichensatz und einem Stück Text mit geeigneter Länge zu orientieren. Ich habe das in Notes and Tasks umgesetzt, der entsprechende Code ist eingecheckt. Als Text verwende ich den berühmten Blindtext Lorem ipsum: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy. Hier die Methode, die die Größe berechnet:

 private static final String LOREM = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy";  
   
 /**  
  * Gets the rectangle for one line of lorem ipsum  
  *  
  * @param font font  
  * @return size  
  */  
 public static Rectangle2D getSizeForLoremIpsum(Font font) {  
     Graphics2D g = (Graphics2D) Application.getInstance().getGraphics();  
     return font.getStringBounds(LOREM, g.getFontRenderContext());  
 }  

Und so wird sie verwendet:

 Rectangle2D r = FontUtilities.getSizeForLoremIpsum(pane.getFont());  
 pane.setPreferredSize(new Dimension((int) r.getWidth(), 12 * (int) r.getHeight()));  

Screenshot: Dialog mit geeigneter Breite

Sieht hübsch aus, oder?

Anmerken möchte ich noch, dass sich die grundsätzliche Idee auch in anderen Frameworks prima umsetzen lässt. Gefällt Ihnen meine Idee? Schreiben Sie mir.

Sonntag, 30. November 2014

UI-Schluckauf, Ausgabe 30.11.14

Regelmäßige Leser dieses Blogs wissen ja, dass ich gelegentlich auf UI-Faux pas hinweise, um Entwickler zu sensibilisieren. Heute ist es wieder einmal soweit. Gesehen im (nach wie vor grandiosen) True Image von Acronis. Zunächst… der Klassiker… ja wo isses denn?

Screenshot: Acronis True Image 2015 für PC (1/2)

Lokalisierungen sprengen oft Grenzen (unsichtbarer) Container. Warum bei so viel Platz überhaupt etwas fehlt, ist zwar ein Rätsel. Eine pragmatische Lösung wäre zumindest (wenn es schon feste Boxen sein müssen), den Inhalt scrollbar zu machen. Das scheint hier nicht möglich. Übrigens hat die Größe des Anwendungsfensters hier keine Auswirkungen. Ja, bzw. nein, natürlich lassen wir nicht den Text über die gesamte Breite fließen. Aber abschneiden sollten wir ihn trotzdem nicht.

Zweitens… …äh.. ja, wer bist du denn?

Screenshot: Acronis True Image 2015 für PC (2/2)

Haben Sie das rotierende Kreischen bemerkt? Nein? Gucken Sie nochmal… Haben Sie eine Ahnung, wofür das ist? Ich auch nicht. Es mag sein, dass in bestimmten Situationen ersichtlich ist, wofür es steht. Ich habe aber nicht herausbekommen, was dazu passieren muss. Merke: jede Komponente sollte einen erkennbaren Sinn haben. Keine Funktion – keine Komponente.

Was halten Sie von meinen Findlingen? Schreiben Sie mir…

Donnerstag, 20. November 2014

Lektüretipp: Android 5 in der aktuellen iX

In der seit heute erhältlichen Ausgabe 12/2014 der iX stelle ich spannende neue Funktionen in Android 5 (Lollipop) für Anwender und Entwickler vor.
Leider hat an drei Stellen das Fehlerteufelchen seine Spuren hinterlassen:
  1. Im Satz Sie lässt sich über die Attribute elevation und translation... muss es translationZ heißen.
  2. Bei Listing 1 ist der Dateiname falsch, es ist nicht ix.html, sondern natürlich ix.xml.
  3. Der Begleittext zu Abbildung 3 ist nicht ganz korrekt, der Screenshot zeigt nicht den Batterie Historian, sondern die Aufrufseite des Battery Savers. Der Fließtext hingegen ist korrekt.
Ich wünsche trotzdem viel Spaß bei der Lektüre des Artikels.

Sonntag, 2. November 2014

Eclipse-Quicktipp: Pextools

Manchmal sind es ganz kleine Dinge, bei denen man sich fragt, warum sie nicht einfach da sind. Zum Beispiel das Anzeigen des Verzeichnisses, in dem eine bestimmte Datei liegt. Oder das Öffnen einer Shell mit diesem Verzeichnis als Start. Oder das Ablegen des Pfades auf dem Clipboard?

Sei’s drum, das Schöne an Eclipse sind die unzähligen Plug-ins, die solche Dinge unkompliziert nachrüsten. Zum Beispiel Pextools.

Screenshot: Pextools

Die Installation via Update-Site ist unkompliziert. Das Plug-in erweitert das Kontextmenü des Package Explorers um das Untermenü PexTools.

Freitag, 31. Oktober 2014

Batteriestatus im Android-Emulator

Für einen Artikel wollte ich einen Screenshot des neuen Battery Savers von Lollipop machen. Eigentlich kein Ding. …sollte man meinen… Wie ich herausgefunden habe, muss aber zunächst in der Konfiguration des AVDs hw.battery=yes eingetragen werden. Dann kann man in einer Telnet-Sitzung (zum Beispiel mit PuTTY, localhost 5554) den Befehl power ac off eingeben. Nun steht der Battery Saver zur Verfügung.

Screenshot #1

Screenshot #2

Screenshot #3

Screenshot #4

Bei aktivierter Stromsparfunktion erscheinen Statuszeile (und ggf. die Buttonleiste am unteren Rand) in orange. Ein letzter Tipp: setzen Sie in den PuTTy-Einstellungen unter Telnet den Telnet Negotiation mode auf Psssive.

Mittwoch, 15. Oktober 2014

Gut oder nicht gut?

Heute hat das Java-AutoUpdate eine neue Version der JRE installiert. Dabei ist mir zum ersten Mal folgender Dialog begegnet:

Screenshot: Warnhinweis auf zu alte Java-Versionen

Kennen Sie den?

Ein Klick auf Siehe Liste mit Java-Versionen zeigt bei mir:

Screenshot: Liste veralteter Java-Versionen

Tatsächlich gäbe es einen neueren Patchlevel für Java 7. Nur, warum wird nicht einfach angeboten, den zu installieren? Die Aufforderung zur Deinstallation halte ich für verfrüht, zumal in der Oracle Java SE Support Roadmap Stand 16. April 2014 öffentliche Updates bis mindestens April 2015 in Aussicht gestellt werden.

Was denken Sie?

Sonntag, 12. Oktober 2014

Back to … BASIC

Ein kleiner Wochenendend-Spaß. Als die Pixel noch groß wie Bauklötzchen waren.
 10 LET FARBE = 2  
 11 MODE(1) : COLOR FARBE,0  
 12 LET X = 64  
 13 LET Y = 32  
 20 IF X < 0 THEN LET X = 127  
 21 IF X > 127 THEN LET X = 0  
 22 IF Y < 0 THEN LET Y = 63  
 23 IF Y > 63 THEN LET Y = 0  
 24 SET(X,Y)  
 40 LET A$ = INKEY$  
 41 IF A$ < "1" OR A$ > "4" THEN GOTO 50  
 42 LET FARBE = ASC(A$) - 48  
 43 COLOR FARBE  
 44 GOTO 20  
 50 IF A$ = "A" THEN LET X = X - 1 : GOTO 20  
 51 IF A$ = "S" THEN LET X = X + 1 : GOTO 20  
 52 IF A$ = "W" THEN LET Y = Y - 1 : GOTO 20  
 53 IF A$ = "Z" THEN LET Y = Y + 1 : GOTO 20  
 98 IF A$ = " " THEN GOTO 999  
 99 GOTO 40  
 999 END  

Screenshot des Emulators VZEM

Das Malprogramm in Aktion

Ja, damals (1983) konnte man sich als 13-Jähriger gut damit die Zeit vertreiben. Dem Familien-Farbfernsehgerät die drei öffentlich-rechtlichen Kanäle genommen, stattdessen ein Ding namens Homecomputer daran angeschlossen. Und eine völlig neue Welt entdeckt.

Samstag, 11. Oktober 2014

Benachrichtigungen auf Wearables

In meinem vorherigen Post hatte ich Ihnen berichtet, dass Min Time nun Benachrichtigungen zum zeitlichen Verlauf eines Talks ausgibt, und dass diese auch auf Android Wear-Geräten erscheinen. Letzteres ist grundsätzlich unspektakulär, weil es – eigentlich – von Haus aus geschieht. Natürlich wäre dies nicht bloggenswert, wenn es nicht ein entscheidendes Caveat gäbe. Hierzu ein Beispiel:
1:  NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(  
2:      context).setPriority(NotificationCompat.PRIORITY_HIGH)  
3:      .setContentTitle(context.getString(R.string.app_name))  
4:      .setContentText(str)  
5:      .setOngoing(true)  
6:      .setWhen(resumed)  
7:      .setSmallIcon(R.drawable.ic_launcher_mintime)  
8:      .setContentIntent(notificationClickedIntent);  
9:  NotificationManagerCompat notificationManager = NotificationManagerCompat  
10:      .from(context);  
11:  notificationManager.notify(CountdownActivity.NOTIFICATION_ID,  
12:      notificationBuilder.build());  
Fällt Ihnen an diesem Stück Quelltext irgendetwas auf? Wer Android kennt, wundert sich vielleicht, dass nicht mehr der Builder aus der Android-Standardbibliothek verwendet wird. sondern NotificationCompat.Builder. Das ist Absicht. Tatsächlich fordert Google in der Dokumentation ausdrücklich dazu auf. Im Klartext bedeutet dies, dass man die Bibliothek android-support-v4.jar einbinden muss. Obiger Code erzeugt tatsächlich eine Benachrichtigung; dummerweise erscheint die aber nicht auf einem Wearable. Den Grund habe ich nach langer Suche in dem Posting How to Create a Custom Ongoing Notification on Android Wear von Carlos Hwa gefunden. Wohlgemerkt, Google schreibt diesbezüglich (Stand 11.10.14) nichts:
Screenshot der Seite http://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html
Wenn man es weiß, ist die Lösung einfach. Aus meinem Quelltextfragment einfach setOngoing() entfernen, und die Benachrichtigung erscheint wie erwartet auf der Uhr. *AUTSCH* Die Nutzung des Kompatibilitätspakets verläuft zum Glück schmerzfrei. Dass die Größe der App zunimmt (von 0,11 MB auf 0,37 MB im Falle von Min Time) ist unschön, aber vor dem Hintergrund aktueller Bandbreiten sicher zu verschmerzen. Übrigens hatte die Integration des Jars eine Zeitlang den Effekt, dass eine App angeblich für viele (> 30) Sprachen lokalisiert war, obwohl es in der eigentlichen Anwendung gar keine Vorkehrungen hierfür gab (wohl aber im Jar). Das scheint aber behoben worden zu sein.

Freitag, 10. Oktober 2014

Neues von Min Time

Ich hatte Lust, mal wieder an Min Time zu schrauben. Hier zwei Teaser-Fotos; die App landet demnächst in aktualisierter Version im Store.

Min Time auf einer LG G

Screenshot: Min Time-Benachrichtigung

Dabei “durfte” ich auch wieder ein paar Android-Merkwürdigkeiten kennenlernen; lassen Sie sich überraschen.

Sonntag, 5. Oktober 2014

Geschichte der Homecomputer. Spielen.

Für meinen Vortrag Geschichte der Homecomputer habe ich heute ein paar Fotos gemacht. Hier das Spiel Dragonriders of Pern aus dem Jahr 1983. Leider kann ich keine Bilder des laufenden Spiels zeigen, weil ich zwar einen 800XL, aber kein Diskettenlaufwerk, habe.

Dragonriders of Pern 1

Dragonriders of Pern 2

Dragonriders of Pern 3

Hier nun das Spiel Crossbow. Diesmal als Modul.

Crossbow 1

Crossbow 2

Crossbow 3

Zum Schluss noch zwei Joysticks. Ein Competition Pro, und ein eher seltenes Stück, ein Speedking.

Competition Pro

Speed King