Gingerbread NDK Awesomeness

Category: Google    |    92 views    |    Add a Comment  |   

[This post is by Chris Pruett, an outward-facing Androider who focuses on the world of games. —Tim Bray]

We released the first version of the Native Development Kit, a development toolchain for building shared libraries in C or C++ that can be used in conjunction with Android applications written in the Java programming language, way back in July of 2009. Since that initial release we’ve steadily improved support for native code; key features such as OpenGL ES support, debugging capabilities, multiple ABI support, and access to bitmaps in native code have arrived with each NDK revision. The result has been pretty awesome: we’ve seen huge growth in certain categories of performance-critical applications, particularly 3D games.

These types of applications are often impractical via Dalvik due to execution speed requirements or, more commonly, because they are based on engines already developed in C or C++. Early on we noted a strong relationship between the awesomeness of the NDK and the awesomeness of the applications that it made possible; at the limit of this function is obviously infinite awesomeness (see graph, right).

With the latest version of the NDK we intend to further increase the awesomeness of your applications, this time by a pretty big margin. With NDK r5, we’re introducing new APIs that will allow you to do more from native code. In fact, with these new tools, applications targeted at Gingerbread or later can be implemented entirely in C++; you can now build an entire Android application without writing a single line of Java.

Of course, access to the regular Android API still requires Dalvik, and the VM is still present in native applications, operating behind the scenes. Should you need to do more than the NDK interfaces provide, you can always invoke Dalvik methods via JNI. But if you prefer to work exclusively in C++, the NDK r5 will let you build a main loop like this:

void android_main(struct android_app* state) {
    // Make sure glue isn't stripped.
    app_dummy();

    // loop waiting for stuff to do.
    while (1) {
        // Read all pending events.
        int ident;
        int events;
        struct android_poll_source* source;

        // Read events and draw a frame of animation.
        if ((ident = ALooper_pollAll(0, NULL, &events,
                (void**)&source)) >= 0) {
            // Process this event.
            if (source != NULL) {
                source->process(state, source);
            }
        }
        // draw a frame of animation
        bringTheAwesome();
    }
}

(For a fully working example, see the native-activity sample in NDK/samples/native-activity and the NativeActivity documentation.)

In addition to fully native applications, the latest NDK lets you play sound from native code (via the OpenSL ES API, an open standard managed by Khronos, which also oversees OpenGL ES), handle common application events (life cycle, touch and key events, as well as sensors), control windows directly (including direct access to the window’s pixel buffer), manage EGL contexts, and read assets directly out of APK files. The latest NDK also comes with a prebuilt version of STLport, making it easier to bring STL-reliant applications to Android. Finally, r5 adds backwards-compatible support for RTTI, C++ exceptions, wchar_t, and includes improved debugging tools. Clearly, this release represents a large positive ?awesome.

We worked hard to increase the utility of the NDK for this release because you guys, the developers who are actually out there making the awesome applications, told us you needed it. This release is specifically designed to help game developers continue to rock; with Gingerbread and the NDK r5, it should now be very easy to bring games written entirely in C and C++ to Android with minimal modification. We expect the APIs exposed by r5 to also benefit a wide range of media applications; access to a native sound buffer and the ability to write directly to window surfaces makes it much easier for applications implementing their own audio and video codecs to achieve maximum performance. In short, this release addresses many of the requests we’ve received over the last year since the first version of the NDK was announced.

We think this is pretty awesome and hope you do too.

  • No Related Post

 

Have Androids. Will Travel.

Category: Google    |    96 views    |    Add a Comment  |   

[The first part of this post is by Reto Meier. —Tim Bray]

From c-base in Berlin to the Ice Bar in Stockholm, from four courses of pasta in Florence to beer and pretzels in Munich, and from balalikas in Moscow to metal cage mind puzzles in Prague - one common theme was the enthusiasm and quality of the Android developers in attendance. You guys are epic.

For those of you who couldn’t join us, we’re in the middle of posting all the sessions we presented during this most recent world tour. Stand by for links.

Droidcon UK

We kicked off our conference season at Droidcon UK, an Android extravaganza consisting of a bar camp on day 1 and formal sessions on day 2. It was the perfect place for the Android developer relations team to get together and kick off three straight weeks of Google Developer Days, GTUG Hackathons, and Android Developer Labs.

Android Developer Labs

The first of our Android Developer Labs was a return to Berlin: home to c-base (a place we never got tired of) and the Beuth Hochschule für Technik Berlin. This all day event cost me my voice, but attracted nearly 300 developers (including six teams who battled it out to win a Lego Mindstorm for best app built on the day.)

Next stop was Florence which played host to our first Italian ADL after some fierce campaigning by local Android developers. 160 developers from all over Italy joined us in beautiful Florence where the Firenze GTUG could not have been more welcoming. An afternoon spent with eager developers followed up by an evening of real Italian pasta - what’s not to love?

From the warmth of Florence to the snow of Stockholm where we joined the Stockholm GTUG for a special Android themed event at Bwin Games. After a brief introduction we split into six breakout sessions before the attendees got down to some serious hacking to decide who got to bring home the Mindstorm kit.

Google Developer Days

The Google Developer Days are always a highlight on my conference schedule, and this year’s events were no exception. It’s a unique opportunity for us to meet with a huge number of talented developers - over 3,000 in Europe alone. Each event featured a dedicated Android track with six sessions designed to help Android developers improve their skills.

It was our first time in Munich where we played host to 1200 developers from all over Germany. If there was any doubt we’d come to the right place, the hosting of the Blinkendroid Guinness World Record during the after-party soon dispelled it.

Moscow and Prague are always incredible places to visit. The enthusiasm of the nearly 2,500 people who attended is the reason we do events like these. You can watch the video for every Android session from the Prague event and check out the slides for each of the Russian sessions too.

GTUG Hackathons

With everyone in town for the GDDs we wanted to make the most it. Working closely with the local GTUGs, the Android and Chrome teams held all-day hackathon bootcamps in each city the day before the big event.

It was a smaller crowd in Moscow, but that just made the competition all the more fierce. So much so that we had to create a new Android app just for the purpose of measuring the relative volume of applause in order to choose a winner.

If a picture is a thousand words, this video of the Prague Hackathon in 85 seconds will describe the event far better than I ever could. What the video doesn’t show is that the winners of “best app of the day” in Prague had never developed for Android before.

In each city we were blown away by the enthusiasm and skill on display. With so many talented, passionate developers working on Android it’s hard not to be excited by what we’ll find on the Android Market next. In the mean time, keep coding; we hope to be in your part of the world soon.

On To South America

[Thanks, Reto. This is Tim again. The South American leg actually happened before the Eurotour, but Reto got his writing done first, so I’ll follow up here.]

We did more or less the same set of things in South America immediately before Reto’s posse fanned out across Europe. Our events were in São Paulo, Buenos Aires, and Santiago; we were trying to teach people about Android and I hope we succeeded. On the other hand, I know that we learned lots of things. Here are a few of them:

  • Wherever we went, we saw strange (to us) new Android devices. Here’s a picture of a Brazilian flavor of the Samsung Galaxy S, which comes with a fold-out antenna and can get digital TV off the air. If you’re inside you might need to be near a window, but the picture quality is fantastic.

  • There’s a conventional wisdom about putting on free events: Of the people who register, only a certain percentage will show up. When it comes to Android events in South America, the certain-percentage part is wrong. As a result, we dealt with overcrowded rooms and overflow arrangements all over the place. I suppose this is a nice problem to have, but we still feel sorry about some of the people who ended up being overcrowded and overflowed.

  • Brazilians laugh at themselves, saying they’re always late. (Mind you, I’ve heard Indians and Jews and Irish people poke the same fun at themselves, so I suspect lateness may be part of the human condition). Anyhow, Brazilians are not late for Android events; when we showed up at the venue in the grey light of dawn to start setting up, they were already waiting outside.

  • I enjoyed doing the hands-on Android-101 workshops (I’ve included a picture of one), but I’m not sure Googlers need to be doing any more of those. Wherever you go, there’s now a community of savvy developers who can teach each other through the finer points of getting the SDK installed and working and “Hello World” running.

  • Brazil and Argentina and Chile aren’t really like each other. But each has its own scruffy-open-source-geek contingent that likes to get together, and Android events are a good opportunity. I felt totally at home drinking coffee with these people and talking about programming languages and screen densities and so on, even when we had to struggle our way across language barriers.

The people were so, so, warm-hearted and welcoming and not shy in the slightest and I can’t think about our tour without smiling. A big thank-you to all the South-American geeks and hackers and startup cowboys; we owe you a return visit.

  • No Related Post

 

Processing Ordered Broadcasts

Category: Google    |    80 views    |    Add a Comment  |   

[This post is by Bruno Albuquerque, an engineer who works in Google’s office in Belo Horizonte, Brazil. —Tim Bray]

One of the things that I find most interesting and powerful about Android is the concept of broadcasts and their use through the BroadcastReceiver class (from now on, we will call implementations of this class “receivers”). As this document is about a very specific usage scenario for broadcasts, I will not go into detail about how they work in general, so I recommend reading the documentation about them in the Android developer site. For the purpose of this document, it is enough to know that broadcasts are generated whenever something interesting happens in the system (connectivity changes, for example) and you can register to be notified whenever one (or more) of those broadcasts are generated.

While developing Right Number, I noticed that some developers who create receivers for ordered broadcasts do not seem to be fully aware of what is the correct way to do it. This suggests that the documentation could be improved; in any case, things often still work(although it is mostly by chance than anything else).

Non-ordered vs. Ordered Broadcasts

In non-ordered mode, broadcasts are sent to all interested receivers “at the same time”. This basically means that one receiver can not interfere in any way with what other receivers will do neither can it prevent other receivers from being executed. One example of such broadcast is the ACTION_BATTERY_LOW one.

In ordered mode, broadcasts are sent to each receiver in order (controlled by the android:priority attribute for the intent-filter element in the manifest file that is related to your receiver) and one receiver is able to abort the broadcast so that receivers with a lower priority would not receive it (thus never execute). An example of this type of broadcast (and one that will be discussing in this document) is the ACTION_NEW_OUTGOING_CALL one.

Ordered Broadcast Usage

As mentioned earlier in this document, the ACTION_NEW_OUTGOING_CALL broadcast is an ordered one. This broadcast is sent whenever the user tries to initiate a phone call. There are several reasons that one would want to be notified about this, but we will focus on only 2:

  • To be able to reject an outgoing call;

  • To be able to rewrite the number before it is dialed.

In the first case, an app may want to control what numbers can be dialed or what time of the day numbers can be dialed. Right Number does what is described in the second case so it can be sure that a number is always dialed correctly no matter where in the world you are.

A naive BroadcastReceiver implementation would be something like this (note that you should associate this receiver with the ACTION_NEW_OUTGOING_CALL broadcast in the manifest file for your application):

public class CallReceiver extends BroadcastReceiver {
  @Override
  public void onReceive(Context context, Intent intent) {
    // Original phone number is in the EXTRA_PHONE_NUMBER Intent extra.
    String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);

    if (shouldCancel(phoneNumber)) {
      // Cancel our call.
      setResultData(null);
    } else {
      // Use rewritten number as the result data.
      setResultData(reformatNumber(phoneNumber));
  }
}

The receiver either cancels the broadcast (and the call) or reformats the number to be dialed. If this is the only receiver that is active for the ACTION_NEW_OUTGOING_CALL broadcast, this will work exactly as expected. The problem arrises when you have, for example, a receiver that runs before the one above (has a higher priority) and that also changes the number as instead of looking at previous results of other receivers, we are just using the original (unmodified) number!

Doing It Right

With the above in mind, here is how the code should have been written in the first place:

public class CallReceiver extends BroadcastReceiver {
  @Override
  public void onReceive(Context context, Intent intent) {
    // Try to read the phone number from previous receivers.
    String phoneNumber = getResultData();

    if (phoneNumber == null) {
      // We could not find any previous data. Use the original phone number in this case.
      phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
    }

    if (shouldCancel(phoneNumber)) {
      // Cancel our call.
      setResultData(null);
    } else {
      // Use rewritten number as the result data.
      setResultData(reformatNumber(phoneNumber));
  }
}

We first check if we have any previous result data (which would be generated by a receiver with a higher priority) and only if we can not find it we use the phone number in the EXTRA_PHONE_NUMBER intent extra.

How Big Is The Problem?

We have actually observed phones with a priority 0 receiver for the NEW_OUTGOING_CALL intent installed out of the box (this will be the last one that is called after all others) that completely ignores previous result data which means that, in effect, they disable any useful processing of ACTION_NEW_OUTGOING_CALL (other than canceling the call, which would still work). The only workaround for this is to also run your receiver at priority 0, which works due to particularities of running 2 receivers at the same priority but, by doing that, you break one of the few explicit rules for processing outgoing calls:

“For consistency, any receiver whose purpose is to prohibit phone calls should have a priority of 0, to ensure it will see the final phone number to be dialed. Any receiver whose purpose is to rewrite phone numbers to be called should have a positive priority. Negative priorities are reserved for the system for this broadcast; using them may cause problems.”

Conclusion

There are programs out there that do not play well with others. Urge any developers of such programs to read this post and fix their code. This will make Android better for both developers and users.

Notes About Priorities

  • For the NEW_OUTGOING_CALL intent, priority 0 should only be used by receivers that want to reject calls. This is so it can see changes from other receivers before deciding to reject the call or not.

  • Receivers that have the same priority will also be executed in order, but the order in this case is undefined.

  • Use non-negative priorities only. Negative ones are valid but will result in weird behavior most of the time.

  • No Related Post

 

Analytics for Android Apps

Category: Google    |    89 views    |    Add a Comment  |   

[This post is by Alexander Lucas, an Android Developer Advocate bent on saving the world 5 minutes. —Tim Bray]

With the addition of custom variables to the Mobile Analytics SDK for Android, it strikes me as a good time to cover something many of you might not have known was possible — using Google Analytics to easily track app usage. Using the mobile SDK is a handy way to get real data on how users interact with your Android apps. So today I’m going to explain how to track usage of your application with Google Analytics.

Prereqs Ahoy!

Before you take off running with this shiny new toy, there’s a few things you’ll need to set up first:

  • Download the mobile SDK. Download and installation instructions are available in the getting started section of the Mobile SDK docs, but the summarized version is:

    • Download the zip file from the download page

    • Put the libGoogleAnalytics.jar file in your project’s /libs directory

    • Be sure the following lines are in your AndroidManifest.XML file:
      <uses-permission android:name="android.permission.INTERNET" />
      <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

  • You’re going to need a Google Analytics account. Go to google.com/analytics and set up an account if you don’t already have one. Then set up a profile for your Android application. When you’re done you’ll see a javascript snippet to insert into your “site”. Copy the part that looks like UA-XXXXXXX-X. You’ll use this in the Android application to tell Analytics which profile the data is being sent for.

Get Tracking

Previous Google Analytics users are going to find a lot of this familiar. In fact, we’ve made a point of keeping the interface as familiar as possible.

First, get your tracker object, and initialize it using the UA code for the Analytics profile you want to track. It makes the most sense to do this in the onCreate() method for your activity main, so it only fires when your application starts up.

GoogleAnalyticsTracker tracker;
protected void onCreate(Bundle savedInstanceState) {
  ...
  tracker = GoogleAnalyticsTracker.getInstance();
  tracker.start(“UA-1234-1”, this);
  …
}

The mobile SDK provides support for the 3 main types of data sent to the Google Analytics servers: Pageviews, events, and custom variables.

Pageviews

A pageview is a standard means to measure traffic volume to a traditional website. Given that this is going into an Android app and not a website, it’s going to be up to you to decide what a “pageview” means. Depending on the type of app, each Activity or different views within the same activity (for instance, different tabs within a TabActivity) could count as a pageview.

Whenever you want to trigger a pageview, call the trackPageView() method. It only takes one parameter, the URL you want a pageview counted towards.

tracker.trackPageView("/HomeScreen");

Pageviews make the most sense as full screen transitions, which in most cases will mean “one pageview per Activity.” Therefor it makes the most sense to put the call to trackPageView in the onCreate() method for each activity in your application. An exception would be if you were using a TabActivity, or other scenario where there were multiple full-screen transitions which all occurred within the same Activity, and conceptually mapped to seperate full-screen “pages” being viewed.

Events

In Analytics, events are designed to track user interaction to that doesn’t map to pageviews, like hitting play/pause/stop in a multimedia app. This maps very well to Android usage — Any form of interaction, from hitting certain buttons to adding/removing data from the datastore, can be tracked using Events.

Events are a little more complicated than pageviews, but just slightly. Instead of 1 parameter, you have 4: Category, Action, Label (optional), Value (optional).

To see how to make use of these, let’s imagine you had a media player application, and wanted to track how many times play, pause, and stop were clicked. The code would look like this:

   playButton.setOnClickListener(new OnClickListener() {
     @Override
     public void onClick(View v) {
     ...
       tracker.trackEvent(
           "Media Player",  // Category
           "Click",  // Action
           "Play", // Label
           0);       // Value
     }
   });

   pauseButton.setOnClickListener(new OnClickListener() {
     @Override
     public void onClick(View v) {
     ...
       tracker.trackEvent(
           "Media Player",  // Category
           "Click",  // Action
           "Pause", // Label
           0);       // Value
   });

   stopEventButton.setOnClickListener(new OnClickListener() {
     @Override
     public void onClick(View v) {
     ...
       tracker.trackEvent(
           "Media Player",  // Category
           "Click",  // Action
           "Stop", // Label
           currentVideo.getPositionInSeconds());       // Value
   });

   myMediaPlayer.setFinishedListener(new FinishedListener() {
     @Override
     public void onFinished(View v) {
     ...
       tracker.trackEvent(
           "Media Player",  // Category
           "Video Finished",  // Action
           "Stop", // Label
           currentVideo.getLengthInSeconds());       // Value
   });

Remember that in the Google Analytics web interface, this data is displayed hierarchically — For instance, if you click on Categories in the left nav, and then on “Media Player”, you’ll see a list of all the different possible values of “Action” which have happened in the “media Player” category. Clicking on “Click” will show all the labels which were sent in the Media Player category with an action of “Click”.

The 4th parameter, “value”, is optional, and behaves differently from the others. It’s meant to be cumulative; In this example, I’m sending the amount of video watched when a video is either stopped or allowed to finish. This is aggregated server-side, and when I go to look at my data I’ll be able to see the total time people have spent watching videos using my application.

Custom Variables

The new hotness! Custom variables are name-value pair tags that you can insert in your tracking code in order to refine Google Analytics tracking. The easiest way to think of this is as meta-data accompanying your pageviews and events. Using this metadata, it becomes easy to split off and look at segments of your data, much the same way you use labels in Gmail. One Android-specific example would be to have a “AppType” status with “Full” or “Lite” depending on whether the user has the full version of the app or not. You could then use the Analytics web interface to look at only the “Lite” users, and see how their usage / userbase differs from the “Full” segment. Custom variables are a ridiculously powerful analytical tool, but they’re also a deep topic. I heartily recommend giving the docs a once-through before implementing them in your Android application. Especially make sure to read the section on scoping. Twice. I’m mean it… I’ll wait.

There are 4 parameters in a custom variable: Index (1 to 5 inclusive), Name, Value, and Scope (Optional, defaults to Page Scope).

The place in your code where setCustomVar() will be called depends largely on what scope that variable will be:

  • Visitor scope: Call once the first time your application is run on a device. Don’t create any custom variables at the same index, or they will overwrite the first one. Useful for sending data about which version of the app is being used, what kind of phone, lite vs full version of the app, or anything that won’t change during the lifetime of the installation of that application.

  • Session scope: Call once at the beginning of every Activity startup. Will apply to all pageviews and events for the lifecycle of the activity, unless a different custom variable is created at the same index.

  • Page scope: Call right before trackEvent or trackPageView that the custom variable should apply to, every time that method is called. If no scope is specified, this is the default.

The call to set a custom variable will look like the following:

// Scopes are encoded to integers:  Visitor=1, Session=2, Page=3
tracker.setCustomVar(1, "Navigation type", "Button click", 3);

Choose a Dispatch Mode

In order to optimize for battery life, a request isn’t actually sent out to the server every time you fire a pageview or custom variable. Instead, all the pageviews, events, and their associated custom variables are stored in a local SQLITE database until they’re dispatched as a group to the server. You can set this up to happen one of two ways: Either have the dispatch occur automatically every n seconds, or manually when you call “dispatch” in code. The mode is chosen when you call the start method on your tracker.

Manual dispatch looks like this:

// No time increment sent as a parameter
tracker.start(“UA-1234-1”, this);
…
// Call this when you want to send the entire event queue to the server
tracker.dispatch();

The timed automatic dispatch looks similar, but sends an extra parameter (the number of seconds between dispatches). In timed dispatch, you never have to manually call dispatch.

// Dispatch all queued pagevies/events every 300 seconds (5 minutes)
tracker.start("UA-YOUR-ACCOUNT-HERE", 300, this);

It’s important to remember that Google Analytics uses the timestamp for when it receives your data, not when the actual pageview/event occurred. This can potentially lead to inaccurate Analytics data, since events can be sent on different days than when they occurred, so take care to dispatch regularly.

The end result

Let’s go back to that onCreate() method we used to instantiate the tracker earlier, and see what it looks like with all the pieces in place:

GoogleAnalyticsTracker tracker;

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

tracker = GoogleAnalyticsTracker.getInstance();
tracker.start(“UA-1234-1”, this);

if(isFirstTimeRunningApplication()) {
 tracker.setCustomVar(1, “App Type”, “Demo”, 1);
}
tracker.trackPageView("/HomeScreen");

…
}

How to look at all this data

There are two ways you can approach this. First, Google Analytics has a pretty snazzy web interface, which does a very good job of surfacing useful information for you. If you’re new to Analytics and don’t really know what you’re looking for yet, the web interface is a great way to explore your data and understand your users.

If you already have a strong idea of the questions you want to ask (app usage across versions of the Android platform, growth rates, time-in-app per demo user vs full user, how many people beat level 3 on their first try, etc), and just want to automate the asking, Google Analytics also has a swanky data export API, with client libraries to facilitate the querying of your data in Java, Python, JavaScript, and C#.

Abiding by the TOS

Google Analytics comes with its own TOS, and it’s important to read and abide by it. The important bit, especially since this will be used inside Android applications, is that you cannot send personally identifying information to Analytics servers. This is a big deal. It means, for instance, that a visitor-level custom variable cannot contain a phone number, first name, or email address. Less intuitively, but still important, it means that if this application is a client to a web application (say, CRM software or a shopping site), you also cannot store information in Analytics which can be combined with your own backend software to identify the user, such as user ID or a transaction ID identical to the one stored on your web backend.

  • No Related Post

 

Android Browser User-Agent Issues

Category: Google    |    91 views    |    Add a Comment  |   

[This post is by Bart Sears, who manages the Android Browser team. —Tim Bray]

This posting describes some issues when browsing websites with mobile variants using large-form-factor Android devices. This posting will be of interest both to OEMs (with recommendations on how to set the User Agent string for the device) and to web site designers/administrators (with recommendations on how to decide to provide either a mobile version, a desktop version, or a large-form-factor touch device version of the site).

Details

With the advent of Android devices with larger form factors, we’ve been evaluating the best way for web sites to provide a UI appropriate for the various Android devices that are now available to consumers. We have received feedback that consumers using larger-form-factor devices often prefer the “full” or “desktop” version of the site over the “mobile” version. Most websites providing “mobile” versions key off of the HTTP User-Agent header field to determine whether to provide the full site or a mobile version.

While large-form-factor Android devices could use “User Agent Spoofing” to provide a desktop User Agent in the HTTP header, we recommend against this. There may be site customizations needed for Android devices (for example changes in the way that mouseover is used) and the site would be unable to provide these customizations if it receives a spoofed User Agent that did not indicate that this was an Android device.

Currently, Android devices provide the following (in addition to standard info) in the User-Agent: “Android”, a version number, a device name, a specific build, Webkit version info, and “Mobile”. For example, Froyo on a Nexus One has the following User Agent:

Mozilla/5.0 (Linux; U; Android 2.2.1; en-us; Nexus One Build/FRG83) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1

The “Mobile” string in the User Agent indicates that this device would prefer a version of the website optimized for Mobile (small form factor devices), if available.

We recommend that manufactures of large-form-factor devices (where the user may prefer the standard web site over a mobile optimized version) remove “Mobile” from the User Agent (and keep the rest of the User Agent as currently implemented). Web sites can then key off “Mobile” in the User Agent to decide on which UI version to present to the device. So a large screen device running Froyo would have a User Agent similar to:

Mozilla/5.0 (Linux; U; Android 2.2.1; en-us; device Build/FRG83) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Safari/533.1

Where “device” would be replaced with the actual name of the new device. Sites can continue to use “Android” in the User Agent to optimize for Android specific features and can also key off of “Mobile” to determine which UI to present.

  • No Related Post