Chrome for Android freezes when scrolling

On my phone, scrolling web pages on Chrome recently became unbearably ugly. Every few seconds when scrolling Chrome will freeze completely, apparently no longer reacting to touch input, only to come back to life a split second later.

I haven’t seen this suggestion around, and it’s the only workaround that actually worked on my Samsung Galaxy Note III Neo.

Open chrome, and type chrome://flags in the address bar.

Look for the “Touch Scrolling Mode” setting (#touch-scrolling-mode), and set it to touchcancel.

On my phone, that fixed the scrolling problem completely, and the only drawback seems to be that I don’t have that fancy decelerated animation anymore. He, it never was that smooth to begin with, so I guess I’ll live with that 🙂

Hope it helps!

Advertisements

How long does it take for the Google Play Store to publish my app in beta?

Your mileage may vary, but it shouldn’t take significantly longer than a couple of hours. If you’re not too unlucky, it should probably take just one hour, both when you’re publishing an app for the first time, and when you’re releasing an update for an existing app.

If it’s taking significantly longer than that (I’m talking more than one day here), you may have been fooled by Google’s peculiar requirements for deployments.

This fooled me twice (so I guess, shame on me).

When you publish an app for the first time, you need to create a Google Group or a Google+ community for testers before the link to your app becomes available. If you try to access the link (even using your own Google account) without being a member of the configured group/community, you’re greeted with a 404 page.

Last time I published an app though, setting an existing Google Group for testers (one that I had configured for a different app) didn’t seem to work. When I created a new Community on Google+, it worked right away. I don’t know if I was just unlucky or if you need a dedicated Google Group for every app, but if you still get a 404 after setting up the Google Group, just try with Google+.

After you’ve become a tester for the app (by visiting your app’s page on the Play Store) it usually takes another hour or so before the APK becomes available for download.

Stack notifications on Android (plus: get users to see your notifications on JellyBean – phone/phablet UI)

This is something I put together for WhatsHare, a small open source app that I published; I couldn’t find a tutorial that had all of this together in one place.

My app (actually, just one particular Activity) has no UI: it receives an Intent, does its thing and finish()es. The only way I found to notify the user that it’s done something is to add a Notification, as dialogs would get too in the way.

I want the notification to be shown every single time, as it must tell the user “ok, I did something” or “an error occurred”, because she has no other way of knowing if the app even worked!

On the other hand, I don’t want to pollute the notification area with lots of “Success!” notifications, as they’re ultimately pointless once the user has seen them.

The options were to either:

  1. automatically cancel() the notification once the message has been displayed, or
  2. show the message every time, but collapse all notifications into a single one, with an increasing counter

I preferred the latter approach, as I think it feels a little more predictable for the user (also, the other solution requires dedicated services/timers, so it’s probably more complicated).

Here’s the final code , comments follow:

@SuppressWarnings("deprecation")
private void showNotification(int sharedWhat) {
    String title = getString(R.string.app_name);
    // this will be routed to onNewIntent(), SendToGCMActivity is this class
    Intent onNotificationDiscarded = new Intent(this,
            SendToGCMActivity.class);
    PendingIntent notificationIntent = PendingIntent.getActivity(this, 0,
            onNotificationDiscarded, 0);
    Notification notification = null;
    String content = getString(R.string.share_success,
            getString(sharedWhat), outboundDevice.type);
    
    // notificationCounter is a private static AtomicInteger
    int notificationNumber = notificationCounter.incrementAndGet();

    NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
            .setSmallIcon(R.drawable.notification_icon, 0)
            .setContentTitle(title)
            .setContentText(content)
            .setTicker(content)
            .setContentIntent(notificationIntent)
            .setDeleteIntent(PendingIntent.getActivity(this, 0, onNotificationDiscarded, 0))
            // update the counter
            .setNumber(notificationNumber);

    if (Build.VERSION.SDK_INT > 15) {
        notification = buildForJellyBean(builder);
    } else {
        notification = builder.getNotification();
    }

    // notifications disappear after the user taps on them
    notification.flags |= Notification.FLAG_AUTO_CANCEL;
    NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

    // cancel previous notification to clean up garbage in the status bar
    nm.cancel(notificationNumber - 1);
    // add new notification
    nm.notify(notificationNumber, notification);
}

@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private Notification buildForJellyBean(NotificationCompat.Builder builder) {
    // for some reason Notification.PRIORITY_DEFAULT doesn't show the counter
    builder.setPriority(Notification.PRIORITY_HIGH);
    return builder.build();
}

protected void onNewIntent(final Intent intent) {
    if (intent.hasExtra(Intent.EXTRA_TEXT)) {
        // do your thing
        // ...code goes here
        showNotification(R.string.link);
    } else {
        // user clicked on the notification
        notificationCounter.set(0);
    }
    // this is needed cause even if we set android:theme="@android:style/Theme.NoDisplay"
    // an invisible overlay is laid out on top of the caller activity, we don't want that
    finish();
}

It works like this:

  • you define a static counter for all your notifications, so that the NotificationManager treats them as separate
  • AUTO_CANCEL is set so when the user taps on the notification it disappears
  • setDeleteIntent() is called to have an Intent routed to the Activity’s onNewIntent() method whenever the notification is discarded, so we can reset the counter. If users tap on the notification, onNewIntent() is also called because of notificationIntent being set as content intent
  • since this activity has an intent-filter for action.SEND of type text/plain, we know that an Intent with no EXTRA_TEXT field can only be passed explicitly — that is, by setting the class target when creating the Intent –. We could add some other extra if we wanted to make extra sure that the Intent comes from that specific class
  • whenever a notification needs to be stacked, we cancel() the previous one (using its id, which is always the current counter minus one) and create a new one with the updated count

That’s the magic behind stacked notifications: Android does not show your notifications if you’re just updating them, so you need to cancel the old one and add the new one. If you don’t need to show the message every time, just update the previous notification using its id (that must not change, so no counter at all).

About JellyBean: if you don’t raise the notification’s priority to at least PRIORITY_HIGH it won’t show the counter on phones/phablets for some reason. Also, to make sure that messages are displayed every time on phone/phablets you must set them as tickers; tablets instead show the full message set by setContentText() anyway.

A final note: I’m using NotificationCompat.Builder instead of plain Notification.Builder to support older devices. You can find that class in the support library (android.support.v4.app package). If you’re targeting honeycomb and higher, just use Notification.Builder.

HttpPost requests executed multiple times (Apache HttpClient)

This is something I noticed on Android, but from what I read it also involves the desktop Java version.

I was sending POST requests to an API server, and I was getting some random 400 Bad Request responses from time to time. I wish Apache provided an easy way to log the plain text version of Http requests, but I couldn’t find a better way to see what the app was sending than sending the same request to my PC when failing.

So to log requests I start netcat (sudo nc -l 80 on a mac) or a very minimal server in python (it’s more or less the same as the example on Twisted’s front page) and route them there whenever an error occurs.

try {
   response = client.execute(post,
                  new BasicResponseHandler());
} catch (IOException e) {
   if (DEBUG_FAILED_REQUESTS) {
      post.setURI(URI.create(DEBUG_FAILED_REQUESTS_SERVER));
      try {
         client.execute(post, new BasicResponseHandler());
      } catch (IOException e1) {
         e1.printStackTrace();
      }
   }
}

I don’t know if it’s my router, but sometimes connections from the Android device to my PC get blocked: to make them work I just open a browser on the Android, go to some website and then try again with my internal IP (192.168.0.whatever). It always works, no idea why.

Using this code I discovered that my post requests were executed 4 times each, nearly at the same time. I discovered that it’s the default behavior, and you must provide your own RetryHandler if you want the HttpClient to work otherwise.

In my case, my calls are sent to Google’s shortener service, and for some reason sometimes it just rejects requests. If you wait a little bit between attempts you increase your chance of getting valid responses. So this is what I did:

HttpPost post = new HttpPost(SHORTENER_URL);
String shortURL = null;
int tries = 0;
try {
    post.setEntity(new StringEntity(String.format(
            "{\"longUrl\": \"%s\"}",
            getURL(encodedID, encodedAssignedID))));
    post.setHeader("Content-Type", "application/json");
    DefaultHttpClient client = new DefaultHttpClient();
    // disable default behavior of retrying 4 times in a burst
    client.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(
            0, false));
    String response = null;
    while (response == null && tries < RETRY_COUNT) {
        try {
            response = client.execute(post,
                    new BasicResponseHandler());
        } catch (IOException e) {
            // maybe just try again...
            tries++;
            Utils.debug("attempt %d failed... waiting", tries);
            try {
                // life is too short for exponential backoff
                Thread.sleep(RETRY_SLEEP_TIME * tries);
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
        }
    }
    Utils.debug("response is %s", response);
    if (response != null) {
        JSONObject jsonResponse = new JSONObject(response);
        shortURL = jsonResponse.getString("id");
    } else if (DEBUG_FAILED_REQUESTS) {
        Utils.debug("attempt %d failed, giving up", RETRY_COUNT);
        debugPost(post, client);
    }
} catch (JSONException e) {
    e.printStackTrace();
} catch (UnsupportedEncodingException e) {
    e.printStackTrace();
}

where debugPost() is a method that calls my PC to log the request, and Utils.debug() is just a small utility method I wrote to log messages with logcat using String.format() if format args are passed to it (it also takes care of splitting messages that would be truncated by logcat itself).

You could choose to implement exponential backoff very easily, but since it’s a blocking operation for the user in my case I preferred not to.

Create a diff for i18n strings.xml files to manage localization on Android

Keeping all your strings.xml files synchronized in Android projects can be painful, as Eclipse doesn’t tell you which strings have no localized version in which language. Android is perfectly happy with it as well, it just uses the default (usually English) string in the app, much for the joy of your non-English users.

I came up with a simple Python script that just scans your res/values-** folders for strings.xml files and, using your default res/values/strings.xml as reference, outputs a list of missing strings for each file, along with the original value set for the key.

So, if for instance your res/values/strings.xml is this:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">My App</string>
    <string name="title_activity_main">My Activity</string>
    <string name="hello_world">Hello, World!</string>
</resources>

and your, say, res/values-it/strings.xml is this:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">La mia App</string>
    <string name="title_activity_main">La mia Activity</string>
</resources>

and your… res/values-fr/strings.xml? is this:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Mon App</string>
    <string name="hello_world">Bonjour, Monde!</string>
</resources>

the script would output:

Missing in /home/whatever/wherever/.../App/res/values-it/strings.xml:
<string name="hello_world">Hello, World!</string>

Missing in /home/whatever/wherever/.../App/res/values-fr/strings.xml:
<string name="title_activity_main">My Activity</string>

So the idea is that you can cut and paste those lines in the appropriate files to translate them.

The script also outputs some warnings in case it finds duplicate keys in any of the strings.xml files.
Your localized strings.xml files may have more <string> items than the default, as no check is performed against that.

I put the script it in a folder within my Android projects that is simply ignored by Android (I usually call it not_in_apk or something like that), so if you put it elsewhere remember to change the path at line 23

path_to_default = '../res/values/strings.xml'

to the path to your default strings.xml file (absolute or relative, it should work anyway).

I didn’t do much testing, so it may not work for you… Worst thing that can happen is.. it doesn’t work 🙂
It won’t mess with your files, I promise you that.

Here’s the script! Run it with python i18n.py.

Last note: this script only takes strings.xml files into account, you should run Android Lint to check for strings to be translated in other XML files (stringarrays.xml and other files).

Remove (bogus) recent workspaces from Eclipse Juno in Mac OS

I never used more than one workspace until Android forced me to do so (you can’t configure debug keystores on a project basis, so you really need different workspaces).

I’ve seen two bad things about working with several workspaces thus far:

  1. you lose all your settings when creating a new workspace (Eclipse only asks to copy perspectives from the current workspace, which is something but not enough)
  2. Eclipse kept showing me a bogus workspace that I never created in the File/Switch Workspace menu, plus it didn’t understand when I renamed one, so it started showing that workspace with both names (old and new)

The first issue can be solved by following these steps (which of course I discovered after doing them by hand… silly me).

As for the second, on MacOS go to your Eclipse installation folder (I have it in Applications), then open configuration/.settings/org.eclipse.ui.ide.prefs with a text editor.

The variable you’re looking for is called RECENT_WORKSPACES, where workspaces are listed one for each line (I can actually see the ‘\n’ as line separators). Delete the silly ones, restart Eclipse, done!