Scaled images, the Android fragmentation and a solution

Update 1
It seems like this fix doesn’t work on every device out there. Some users had scaling issues after I published this change. So I made this change optional.
/Update
Update 2
The issue seems to be not a scaling problem but a image quality problem. I have changed the source code to produce much better results
/Update

While developing an app that needs to interact with the operating system, like Minimalistic Text, every Android developer will get to a point where the Android fragmentation hits him. Sometimes a little bit and sometimes hard 🙂
Minimalistic Text has such a „problem“ since the beginning and today I want to explain what the problem is and what I did to resolve this annoying issue.

Widgets in Android

Widgets in Android get updated through packages that are sent from the application. The application can’t control directly what the widget does but it can throw settings into such a package and send it to the widget. The Launcher app then receives this package and applies it to the widget.
Due to the things Minimalistic Text can do (shadows for example) the options that such a package offers aren’t enough to bring all the settings that a user has set up to the widget.
So Minimalistic Text uses the „Bitmap approach“. The content of the widget gets rendered into a bitmap and then the bitmap gets passed to the widget (through the package). The widget itself is only an ImageView.
This approach works really well. At least on most devices. And at least for certain widget sizes… The story begins.

The problem

From the launch of Minimalistic Text until today there are complaints from users that told me that their widget stops updating. After some investigation I have found out that the package size that the app can send to the widget is limited. I think 1 MB is the maximum. If the app sends a package bigger than that limit the update will fail. The really bad thing is that the app doesn’t notice this. So the update silently fails and Minimalistic Text doesn’t know of any problem.
I worked around this issue by reducing the available widget sizes. If you want to get this problem then simply create a 4×2 Minimalistic Text widget and fill it with data so that the image that has to be sent to the widget gets as big as possible.
The only way to avoid sending such a big image through a package is to save the image as a file and only send an URI to the widget where to find it. Sounds great, doesn’t it?
I have tested this approach on my HTC device and it worked great. So I decided to publish my new bugfix into the wild and shortly after that a e-mail flood has arrived my googlemail account. Many users complained about widgets that are scaled down.
After rolling this bugfix back and googleing around a bit I found out that there is a bug in Android. If the Bitmap isn’t applied directly to a ImageView but through an URI (that gets fed by a ContentProvider) the image gets scaled down by the density of the screen.

Fragmentation everywhere

And here comes the fragmentation into play.
If this bug would be on all devices – not cool but no problem. Minimalistic Text could simply scale the image up so that the bug scales it down again and everything is fine. Well would be is the right term 😉
It seems like some vendors have fixed this bug (for some of their devices). I have a HTC Sensation that doesn’t scale anything. My Galaxy Nexus and the Nexus S do. So thanks to the fragmentation I have no clear way without a bug or with a workaround for this bug.
Pissed off by the fact that my fix doesn’t work on all devices I took this problem as beeing present and limiting the amount of widget sizes that Minimalistic Text can handle.

And it gets worse

As the screen resolutions get bigger and bigger the 1 MB limit comes nearer and nearer. The last days some people had the „widget doesn’t update any more“ issue on 4×1 widgets.
So I had to find any way to avoid this issue.

The solution

After telling this a colleague of mine and complaining about the „bad bad“ Android fragmentation and how developing for Android is like creating big if cascades to check what device is currently executing the app to activate special workarounds or hacks he asked me if it was possible to get the bytes that are displayed on the widget to check if they were scaled or not.
After coming home and bringing the kids to bed I tried to use my own ContentProvider and then checked if the image that the ImageView got from the ContentProvider has been scaled. And yes, it did. So now I have found a way to measure this bug and scale the images for the widgets the same way up as they get scaled down by the ImageView.

Dead simple.

To give you an idea of how to achieve this I will post some source code here. So if you aren’t a developer this place can be a good one to stop reading this post 😉

Some code

The first step is to make your ContentProvider „Sample“-aware. Use some kind of special data to signal the ContentProvider that you need a specified sample image.
I have added the following functions to the Minimalistic Text ContentProvider (the source code is not functional as it is only part of a bigger source file):

public static final Uri CONTENT_URI = Uri.parse("content://"
        + PROVIDER_NAME + "/widgets");

private static String getSampleFileName() {
    return "MT_Sample.png";
}

public static int getSampleRectangleSize()
{
    return 100;
}

public static Uri ensureSampleBitmap(Context context) throws IOException {
    Uri result = Uri.parse(CONTENT_URI.toString() + "/-1/"
            + Long.toString(Calendar.getInstance().getTimeInMillis()));

    File file = new File(context.getFilesDir(), getSampleFileName());
    if (file.exists())
        return result;
    FileOutputStream fOut = context.openFileOutput(getSampleFileName(),
            Context.MODE_WORLD_READABLE);
    Bitmap sampleBitmap = Bitmap
            .createBitmap(1, 1, Bitmap.Config.ARGB_8888);
    sampleBitmap.setPixel(0, 0, Color.WHITE);
    Bitmap scaledBitmap = Bitmap.createScaledBitmap(sampleBitmap, 
                                    getSampleRectangleSize(), 
                                    getSampleRectangleSize(), false);
    scaledBitmap.compress(CompressFormat.PNG, 100, fOut);
    fOut.flush();
    fOut.close();

    sampleBitmap.recycle();
    scaledBitmap.recycle();

    return result;
}

This methods allow us to create a sample image that has a dimension of 100×100 and get an URI to retrieve the image.
The code for the openFile method of the ContentProvider looks like this:

@Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
        throws FileNotFoundException {

    try {
        int widgetId = Integer.parseInt(uri.getPathSegments().get(1));

        File file = null;

        //thie widgetId -1 signals "Sample data"
        if (widgetId == -1) {
            file = new File(getContext().getFilesDir(), 
                    getSampleFileName());
        } else {
            file = new File(getContext().getFilesDir(),
                    getFileName(widgetId));
        }

        return ParcelFileDescriptor.open(file,
                ParcelFileDescriptor.MODE_READ_ONLY);
    } catch (Exception e) {
        return null;
    }
}

Now the app can measure the amount of wrong scaling by instantiating an ImageView and let the ImageView fetch the sample image:

private float calculateImageContentProviderScale()
{
    try {
        ImageView imgView = new ImageView(this);
        imgView.setImageURI(
            WidgetImageContentProvider.ensureSampleBitmap(this));
        
        imgView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
        
        return (float)WidgetImageContentProvider.getSampleRectangleSize() 
                / (float)imgView.getMeasuredHeight();
    } catch (IOException e) {
        Log.e(TAG, "Error determining the image scale factor", e);
        e.printStackTrace();
    }
    return 1;
}

This factor can be stored as a static variable because this value won’t change during the app lifetime.
Everytime a widget gets updated Minimalistic Text now uses this factor to scale the image up.

ContentProvider-Part:

public static Uri updateWidget(Context context, Bitmap widgetContent,
        int widgetId, boolean useSalt, float scaleFactor)
        throws IOException {
    String fName = getFileName(widgetId);
    FileOutputStream fOut = context.openFileOutput(fName,
            Context.MODE_WORLD_READABLE);
    
    Log.d(TAG, "Using a scale factor of " + Float.toString(scaleFactor));
    
    if(scaleFactor > 1)
    {
        float scaledHeight = widgetContent.getHeight() * scaleFactor;
        float scaledWidth = widgetContent.getWidth() * scaleFactor;

        Bitmap scaledWidgetContent = Bitmap.createScaledBitmap(
                                                widgetContent, 
                                                (int)scaledWidth, 
                                                (int)scaledHeight, 
                                                true);
        scaledWidgetContent.compress(CompressFormat.PNG, 100, fOut);
        fOut.flush();
        fOut.close();
        scaledWidgetContent.recycle();
    }
    else
    {
        widgetContent.compress(CompressFormat.PNG, 100, fOut);
        fOut.flush();
        fOut.close();            
    }

    return Uri.parse(CONTENT_URI.toString()
            + "/"
            + Integer.toString(widgetId)
            + "/"
            + (useSalt ? Long.toString(Calendar.getInstance()
                    .getTimeInMillis()) : ""));
}

Client-Part:

Bitmap textBitmap = createTextBitmap(context, settings,
        demoMode, ss.hasBeenPortrait(), events);

Uri imgUri = WidgetImageContentProvider.updateWidget(
                        context, 
                        textBitmap, 
                        settings.getAppWidgetId(), 
                        true, 
                        ss.getImageScale());
remoteView.setImageViewUri(R.id.imgContent, imgUri);
textBitmap.recycle();

I hope this helps any poor developer out there that has been stuck on the same problem as I have been until today!
If you have any further questions, simply write me an email.

Devmil

A little change

Hi everybody,

I will change the way this blog works a bit. I want to blog more into the „software development“ / „Android“ direction than about my personal day to day drivers. Also I have the plan to rise the update frequency of this blog and the language changed to English so that users outside Germany can read what I write 🙂

So let’s start off with a little Android exercise. Recently I implemented a new language selector for Minimalistic Text. The old one just used the ListPreference in the Android Settings. My main goal has been to add little flags to each language to make this dialog a little more colored.

A short research on the internet showed me that there is no way to get images into a ListPreference. So I had to implement my own Preference that is capable of a list with images.
My first thought was to derive from ListPreference and add the image functionality. But due to the (lack of) design in the ListPreference there is no way to derive a subclass that implements another visual part. The main reason for this is the fact that the ListPreference stores the selected element index into an internal variable (private) that then gets read by the dialog closed event. So a subclass would have to set the index based on its own view to use the ListPreference functionality like propagating the settings change to all listeners.

So the last possibility has been to copy the ListPreference code to my own class and make images in the list possible.

The solution constists of 6 files:

  • ImageListPreference.java
  • ImagePreferenceArrayAdapter.java
  • imagelistpreferencelayout.xml
  • empty_content.xml
  • orange_content.xml
  • clickbackground.xml
The ImageListPreference is a copy of the ListPreference and adds an array of resource ids for the images. It sets its own ListAdapter to the Dialog (an instance of ImagePreferenceArrayAdapter). The Adapter then creates the views for the items based on the imagelistpreferencelayout and handles the click events (that set the currently selected index). The 3 drawable xml files are needed to give a visual feedback for tapping on an item (the background gets changed from empty to orange based on the selector in clickbackground.xml.)
You can download the sources for the ImageListPreference here: ImageListPreference.zip
That’s how the new language selector looks like:
Sample of the new Minimalistic Text language selector

Language Selector

If you have any questions or feedback for the ImageListPreference then write me an email.
Devmil

Open Source Color Picker fĂŒr Android

Nachdem ich fĂŒr Android keinen umfassenden Color Picker gefunden habe, habe ich mich dazu entschlossen einen eigenen zu bauen. Aus der Minimalistic Text Ecke kamen Anforderungen wie „RGB-Farbregler“ und „HEX Code Eingabe“.

Also habe ich alle zusammen in einen Farbauswahldialog gesteckt. Über Tabs kann man sich entscheiden, ob man lieber HSV, RGB oder den HEX Code als Eingabemöglichkeit vorzieht. So sieht er dann in Aktion aus:

Falls Ihr ihn verwenden oder sogar daran mitentwickeln wollt: Der Source Code ist hier zu finden.

Devmil

Minimalistic Text hÀlt mich auf Trab

So, nach 2,5 Wochen melde ich mich hier auch mal wieder.

Minimalistic Text

Der Release von Minimalistic Text verlief sehr gut. Inzwischen sind es ĂŒber 20000 Downloads und fast 10000 aktive Installationen. Die letzten Wochen hat sich auch einiges an Minimalsitic Text getan. Mehrsprachigkeit, Statischer Text, Wetterdaten und viele weitere Verbesserungen sind bereits eingeflossen.

Das kleine Spaßprojekt ist schon fast zur Feierabend-Arbeit mutiert und nimmt momentan fast jede Minute ein, die ich am Rechner verbringe. Es wollen E-Mails mit Fragen beantwortet, ForenbeitrĂ€ge geschrieben und Erweiterungen entwickelt werden. Nichtsdestotrotz macht es mir nach wie vor Spaß die App weiterzubringen.

Ich konnte dank der App bereits sehr viel ĂŒber die Android Entwicklung lernen und wie in anderen Programmiersprachen / auf anderen Plattformen auch, lernt man nie aus und stĂ€ndig was Neues.

Wen man Annahmen ĂŒber die API hinaus trifft…

Vor ein paar Tagen gab es Probleme mit Minimalistic Text auf bestimmten, neueren JPU-Roms fĂŒr das Samsung Galaxy S I9000. Minimalistic Text hat einfach den Start verweigert, bzw. kein Widget mehr als „existent“ betrachtet.

Nach einigem Hin und Her und einer E-Mail Konversation mit einem betroffenen konnte ich dem Problem auf den Grund gehen:

Hintergrund ist die Tatsache, dass man in Android zwar Einstellungsdateien (Shared Preferences) anlegen, aber nicht mehr löschen kann. Jedes Widget in Minimalistic Text besitzt eine eigene Einstellungsdatei, so dass „DatenmĂŒll“ ĂŒbrig bleibt, wenn man ein Widget löscht. Minimalistic Text „weiß“ wo diese Einstellungsdateien liegen und löscht diese, wenn ein Widget vom Homescreen entfernt wird.

Außerdem hatte ich temporĂ€r das PhĂ€nomen, dass Widget-Ids zwar noch vorhanden waren, die zugehörigen Widgets aber schon lĂ€ngst vom Homescreen entfernt wurden. Um unnötiges Aktualisieren von nicht mehr sichtbaren Widgets zu verhindern prĂŒft Minimalistic Text, ob ein Widget noch da ist, indem es prĂŒft, ob es noch eine Einstellungsdatei dazu gibt.

Diese beiden „Features“ basieren darauf, dass die Einstellungsdateien unter „/data/data/de.devmil.miniamaltext/shared_prefs“ liegen.
Tun sie auch, bei (fast) allen mir bekannten GerÀten. Nur eben nicht in den neuen Samsung ROMs.

Ist ja auch nichts verwerfliches. Die API der Shared Preferences oder des Contexts gibt auch keinen Pfad zu diesen Einstellungsdateien, so dass die Annahme des Speicherplatzes schon etwas gewagt war, wenn man bedenkt wie viele unterschiedliche Android GerÀte es gibt.

Auf jeden Fall prĂŒft Minimalistic Text jetzt, ob es das vermutete Verzeichnis ĂŒberhaupt gibt. Wenn nicht, dann wird in den „mir doch egal“-Modus gegangen und jede WidgetId als vorhanden angenommen und die Einstellungsdatei nur geleert, nicht aber gelöscht.

FĂŒr das Samsung ROM habe ich inzwischen herausgefunden wo die Einstellungsdateien liegen, da ich jetzt auf die Version JPX (ebenfalls Android 2.2.1) gegangen bin und die das gleiche Verhalten zeigt.

Anscheinend hat Samsung aus Performance-grĂŒnden die Shared Preferences von „/data/data/<Package>/shared_prefs“ nach „/dbdata/databases/<Package>/shared_prefs“ umgezogen.

Die nÀchste Minimalistic Text Version wird dieses Verzeichnis auch in Betracht ziehen.

Die erste App

Ich habe heute meine erste Android App veröffentlicht.
Sie befindet sich noch in Entwicklung, lĂ€uft aber stabil genug so dass ich jetzt auf konstruktive RĂŒckmeldungen  hoffe, um die WĂŒnsche der User so frĂŒh wie möglich in die Anwendung zu bringen.

Minimalistic Text heißt sie und versteht sich als Meta-Widget zur Darstellung von Information auf eine minimalistische Art und Weise. Derzeit werden Zeit-, Datums und Akkuinformationen verarbeitet und dem User in Form von Variablen zur VerfĂŒgung gestellt.

Der User kann sich mit Hilfe des Layout Editors die Variablen so zurechtlegen, wie er es gerne hÀtte. Pro Widget lassen sich dann noch die Styles der Schriften anpassen und schon hat man ein individuelles Widget.

FĂŒr die Zukunft ist, neben vielen anderen Erweiterungen, geplant, noch mehr Informationsquellen einzubinden (Wetter z.B.). Außerdem wird es die Möglichkeit geben die Sprache pro Widget einzustellen (derzeit fest Englisch).

So sieht das Werbe Bild aus

XDA-Forumsbeitrag (von mir)
AppBrain-Link
Markt-Link (funktioniert nur vom Android Handy aus)

Hier gibt es eine sehr knappe Hilfe zu Minimalistic Text (auf Englisch)