Thoughts on Google I/O 2013

18 05 2013

Google I/O 2013 has ended and while we saw a few new things on the Android front, this year’s event was much more dialed back than 2012 as far as the platform is concerned. There was no Android update announced, much to the chagrin of some bloggers. In my opinion, this isn’t that big of a deal as the platform is in a very stable, viable place right now. The platform along with the Android Design Guidelines provide the foundation for developers to deliver almost everything possible in their apps.

There was an update to the suite of services labeled as Google Play Services, which includes APIs such as Maps, Google+ sign-in, Cloud Messaging, and Game Services. How Google is leveraging Play Services is very clever and hints at how Google intends on getting around manufacturers and carriers dragging their feet on OS updates. Google isn’t the reason for fragmentation in the market, it’s the carriers and manufacturers taking months to release updates and in some cases, never releasing them. I think we’ll see more of this sort of update in the future, getting updates to critical APIs in a push that doesn’t rely on carriers and manufacturers to cycle through their own development and test processes.

There was the announcement of Android Studio and a new build system, Gradle. Android Studio is based on the community edition of IntelliJ and while I’ve never used that IDE myself, several of my colleagues have and swear by it. I’ll be taking a closer look over the next few weeks even though the IDE is considered a Preview release. It appears to be pretty functional. While Tor Norbye stated in the intro session they plan on keeping both Eclipse/ADT and Android Studio functionally equivalent, he did let one tidbit slip out that caught my attention. He stated that “some things are just easier to do” and “some things we just can’t do in Eclipse” when discussing the features of Android Studio. This makes me wonder just how viable it will be to expend resources on two different platforms when one is clearly easier to deliver functionality for. Eclipse/ADT may stick around for a year or two, but I wouldn’t be surprised if support is dropped some time in the future.

I’m surprised it took as long as it did for Google to move away from Ant as their build system. As someone who has been using Maven since 2007, I loathe every time I have to look at an Ant script. The move to Gradle makes sense and is even a bit forward thinking on their part. I look forward to introducing dependency management and artifact repositories into my projects.

While there was a strong emphasis on gaming this year with the introduction of Google Play Gaming Services and nearly every period of the first day dedicated to gaming sessions, there were a few things of major interest to the enterprise community. Many, if not all, of our mobile clients are leveraging Google Map APIs in their native applications. I encourage all to revisit their code and take a look at the new Fuse Location provider with significant battery savings.

The biggest announcement for the enterprise centered around the Developer’s console, particularly support for Beta Testing. I wrote a blog over two years ago about Android in the Enterprise and how it just wasn’t ready yet. One of the issues I had was the inability to distribute software internally. That’s been fixed somewhat by the addition of a private channel for Google Apps customers, although I’d like to see this expanded a bit to allow enterprises to also leverage this feature. However, at nearly every one of my clients we’d have to figure out a way to distribute an application to our testers for system testing. This almost always involved system admins, network admins, etc to get some hosted disk space on an Apache server, email blasts to all the testers with a URL or IR code for them to download. This should be a thing of the past with the new Beta Testing feature. Smaller enterprises could even leverage this feature to target their production internal applications via this channel. While I’m most excited about this feature, the full fledged integration of Google Analytics right into the Developer Console is also a welcome addition. Provided you’re using GA for analytics in your app, all this information will be side by side with all the other analytics you get for free from the Google Play Store.

Advertisements




Android targetSdk and the Legacy Soft Menu Button

5 11 2012

At Google I/O 2012, Google made it a point to stress that Android developers should be leveraging the highest API level for the targetSdk attribute in our application’s manifest. They stated that essentially it should be the latest version of Android available. As I hadn’t been doing this in the past, when I got back to the client site I immediately started setting this attribute to the latest version available, API level 16 (Jelly Bean).

What I didn’t realize, is that this attribute determines whether or not the legacy Menu button appears in the soft key button tray for devices that only have soft keys. If your application isn’t up to the design standards that Google recommends for ICS+ and you have legacy menu options in your application, you’ll need to ensure that the API level you specify for targetSdk is no greater than level 10, Gingerbread. If you specify anything above that, your users will lose their ability to use Menu options.





Programmatically Locking Android Orientation

6 10 2012

On my latest project, we had some technical requirements to lock the screen in the current orientation while some background processing occurred. Once the processing was completed, orientation is returned back to whatever the orientation was before it was locked. This seemed pretty straight forward as there are several examples around the net on how to lock in portrait or landscape. Locking on the existing orientation didn’t seem that difficult, use the existing orientation value:

orientation = getRequestedOrientation();
setRequestedOrientation(orientation);

This seems pretty straight forward, until you take a look at the javadocs (or in my case, have a defect arise). We found our locking code was working when in portrait mode, however it was not working when in landscape and orientation changed to potrait. Adding some debug code and we found out when in landscape, user orientation was being returned. Looking at the javadoc, user orientation is defined as:

The user’s current preferred orientation.

This user value can come from system settings and could of any value, so we can not confidently code for the appropriate orientation. I’m unsure if it’s a defect in the platform or if it were designed this way, but locking the orientation on this value does not lock the current orientation

The absolute best way to programmatically determine orientation is to use a combination of screen width, height, and rotation. Rotation is defined with constant values of 0, 90, 180, and 270. A phone’s natural orientation is portrait, so it’s base rotation is value for portrait is 0. That means that landscape rotation could be 90 or 270 and with the phone completely flipped upside down rotation is 180. A tablet’s natural orientation is landscape, so it’s base rotation value for landscape is 0. That also means a tablet’s rotation could be 90 or 270 for portrait, and 180 if it is landscape but flipped upside down. This rotation value in combination of screen size (i.e. is x greater than y) gives us a true representation for orientation and allows us to truly lock the orientation correctly. Keep in mind, you need to account for reverse orientation.

Display display = getWindowManager().getDefaultDisplay();
int rotation = display.getRotation();

Point size = new Point();
display.getSize(size);

int lock = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;

if (rotation == Surface.ROTATION_0
		|| rotation == Surface.ROTATION_180) {
	// if rotation is 0 or 180 and width is greater than height, we have
	// a tablet
	if (size.x > size.y) {
		if (rotation == Surface.ROTATION_0) {
			lock = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
		} else {
			lock = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
		}
	} else {
		// we have a phone
		if (rotation == Surface.ROTATION_0) {
			lock = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
		} else {
			lock = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
		}
	}
} else {
	// if rotation is 90 or 270 and width is greater than height, we
	// have a phone
	if (size.x > size.y) {
		if (rotation == Surface.ROTATION_90) {
			lock = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
		} else {
			lock = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
		}
	} else {
		// we have a tablet
		if (rotation == Surface.ROTATION_90) {
			lock = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
		} else {
			lock = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
		}
	}
}




Managing Complex Mobile Transaction – An Android Implementation

12 11 2011

A colleague of mine, Jack Cox, wrote an article recently about managing complex mobile transactions. The article is written with an iOS slant but I found it very interesting and wondered if the paradigm could be implemented in Android and whether it could be effective. After sitting down and doing some brain storming, I realized the pattern lent itself very well to Android.

Let’s review some of the main components of Jack’s pattern:

  • Controllers — Typically view controllers, responsible for requesting data, processing results and presenting them to the user.
  • Commands — Encapsulates everything needed to make a service call including the data for the call itself.
  • Exception listeners — Responsible for taking action on any non-expected exception thrown from the service tier call.
  • Command Queue — Responsible for managing network requests. Controllers will place requests onto the Command Queue and listen for responses. Exception listeners will also listen for unexpected responses and deal with them appropriately.

This paradigm really lends itself well to Android’s own Command Dispatch pattern implemented by the use of Intents, Services, and Broadcast Receivers.

Overview

An Android IntentService will be used as a RESTful service delegate, hiding communication details from components that need to communicate with back end services. The IntentService is also the Android representative of the iOS NSCommandQueue (command queue) in this pattern. Intents will be used as the Command object in this interaction and meshes well with their current use within the Android operating system. BroadcastReceivers will play the part of exception listeners and will also be leveraged by Activities who are the Android representative of an iOS View Controller. The implementation details do change slightly due to the differences in operating systems and will be covered in subsequent sections. Intent Filters based off of data in the Command Intent, will allow for targeted broadcast notifications of RESTful service results (or failures). We’ll follow a convention over configuration pattern for targeting specific broadcast receivers with results from service calls.

Operation.Request and Operation.Response (Data)

Marshallable and unmarshallable data objects representing an operation request and response will need to be created for each RESTful operation. These data objects will also need to be Parcelable so the operating system can use interprocess communication (IPC) if necessary. These data objects will extend from 2 abstract classes, OperationRequest and OperationResponse, that will serve as marker interfaces, have common attributes across all services, as well as to enforce certain contractual needs required by the Rest Delegate Service.

Intents (Commands)

Intents are used as the Command objects and will contain all the information necessary to make the network call, the results of that network call, and routing information based off the Intent metadata The action and category metadata for the Intent is different throughout the lifecycle and is dependent on where in that chain as to what the value is.

Rest Delegate Service Intents – Are explicit intents and will contain the following extras:

  • “rds.service_request” – The OperationRequest and should contain everything necessary for the Rest Delegate Service to make a RESTful call to the service tier and formulate a response that can be broadcast and understood by all Broadcast Receivers.

Broadcast Receiver Intents – Are implicit intents and will identify their consumers by a combination of the Intent’s action and category metadata. The action will be a static string, “me.ericmiles.mobiletrans.ACTION_REST_RESULT”. The category will be one of three things: the fully qualified name of the response type, ie “me.ericmiles.mobiletrans.ops.LoginOperation.Response” , the fully qualified name of the exception caught during service communication, ie “org.springframework.web.client.RestClientException”, or the default exception category if no Broadcast Receivers would react to a caught exception “me.ericmiles.mobiletrans.UNKNOWN_EXCEPTION”. The category is the key component to allow broadcast receivers to filter out intents they do not care about. This will be discussed in more detail in the Intent Filter section. Broadcast Receiver Intents can have the following extras:

  • “rds.service_request” – The OperationRequest that was used to make this service call. It is sent in the Result intent in case an exception Broadcast Receiver wishes to try the request again.
  • “rds.service_response” – The OperationResponse received from the RESTful call if one is returned.
  • “rds.http_response_code” – The HTTP response code if one is returned from the RESTful call. May be used in processing by broadcast receivers.
  • “rds.exception” – The exception if one was caught.

Broadcast Receivers and Intent Filters (Listeners)

Broadcast Receivers will be used as listeners for RESTful service operation responses. These Broadcast Receivers could be registered programmatically within an Activity or can be registered statically via the Android manifest. Programmatically registered Broadcast Receivers will generally be for a specific operation for an Activity. Statically registered Broadcast Receivers will generally function as exception listeners, handling unexpected errors that transpire during RESTful communication. Broadcast Receivers extend from the Android BroacastReceiver class.

Intent filters are used throughout the Android ecosystem by Activities, Services, and Broadcast Receivers to filter Intents they are only interested in. In our communication pattern, we want to encapsulate logic to handle specific responses in their own broadcast receivers, be it regular RESTful responses or exception handlers.

Permissions

Normally broadcasts are sent across the entire device, allowing any components that has registered for an Intent to receive it. As a protective measure, to prohibit outside applications registering for our RESTful response broadcasts, we will require an Android permission to be used for all recipients of the implicit Intents used for operation responses. This permission will also be designated with the “signature” protection level, meaning that only applications that have identified using this permission and having been signed with the same certificate as the defining application can receive this intent. This will rule out any other application from being able to receive these Intents unless they somehow manage to get ahold of the signing certificate (keep those secure!!!)

Rest Delegate Service (Command Processor)

The Rest Delegate Service is responsible for RESTful communication aspect of the communication pattern. The Rest Delegate Service is an Androind IntentService; it is a special Android service that runs on an OS managed thread separate from the main UI thread. This will allow the service to handle network communication requests without blocking the user’s interaction with the UI. The Rest Delegate Service will leverage the Spring Android framework for RESTful service communication and will leverage the Gson library for marshaling and unmarshalling JSON data under the covers.

When a response is from the RestTemplate, the RestDelegateService will package up the OperationResponse, the original OperationRequest, and the HTTP response code into an implicit Intent, and fire a broadcast. This broadcast will allow any interested parties to listen for the response as there may be more than one. For example, upon successful login, not only with the MainActivity that originally fired the Command be interested, so will the Session Manager as it needs all the session information returned from the Authentication REST service. The category for the Intent will also used the fully qualified name of the OperationResponse so appropriate Intent Filters can be applied by the operating system.

If an exception is caught, there will be no OperationResponse nor will there be an HTTP response code. Instead of placing those items into the Intent, the fully qualified name of the Exception caught will be used as the category. This will allow specific Broadcast Receivers to act as exception handlers for pre-defined errors. A default, catch all will be used in case there is no match.

Activities (View Controllers)

An Activity is the controller in the communication pattern outlined in Jack’s blog entry. It is responsible for managing user interactions and what transpires during those interactions. In the Android implementation of the pattern, Activities that have RESTful service interaction will be responsible for firing off Service Intents for the Rest Service Delegate and handling those responses by managing a Broadcast Receiver that is programmatically registered for those responses with appropriate Intent Filters.

Conclusion

I think this pattern lends itself really well in the Android environment as you can see by the attached sample project. Interaction with the user gets a little tricky with the Exception listeners, particularly if you want to interact via Dialog boxes, however a there are numerous ways to skin a cat and it can be done. Check out the linked Github project and let me know your thoughts on the implementation.

Notes

I’ve included Mockey as my stubbed out service tier; it’s a pretty configurable, lightweight utility to create stubs for your service tier. If you care to use it, once started you’ll need to update the rest_endpoints.xml resource file with the appropriate IP address Mockey is running on, so your deployed application can interact with the service. Also, I’m unsure why Mockey doesn’t persist some of the configurations, but you’ll need to do the following:

  1. For the AuthenticationService’s Default scenario, also make it the “Universal Scenario Error Response”.

GitHub Project





Android Single Account, Multiple Application Prescription

23 09 2011

One of the really neat things that Google has done with all of its Android applications is that they all share a common authentication mechanism in Accounts and Sync. There is no need to enter your Google credentials for each Google based application on your device, which centralizes password changes to a single location. It also groups those common authentication applications together in the Accounts and Sync settings activity, as I’m sure everyone is familiar with:

device-2011-09-16-084016-2011-09-23-09-03.jpg

For my employer, we have several internal Android applications under development and we too wanted to employ this single mechanism to support multiple accounts paradigm in use by Google. How to do it is in the documentation, but I had to hunt for it a bit. How you obtain the token in the authentication mechanism and how you use that token in your service call is out of scope for this prescription. However, this prescription will show you how to tie all these pieces together:

1. Create your authenticator (out of scope for this).  Consult a previous blog I have on this subject for that prescription.

2. In the manifest for the application containing the authentication mechanism, alter the sharedUserId and put a static value.  Here, we’ll use com.captechventures.mobile.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.captechventures.android.authenticator"
      android:versionCode="1"
      android:versionName="1.0.0"
      android:sharedUserId="com.captechventures.mobile">

3. In the authenticator descriptor (authenticator.xml in my example), add an label to give the grouping of accounts a generic name. While not necessary, this will allow you to name the account something other than the name of the application it’s installed with (default behavior).

<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="com.captechventures.mobile"
    android:icon="@drawable/icon"
    android:smallIcon="@drawable/icon"
    android:label="@string/auth_token_label"
/>

4. Create the 2nd (or 3rd, 4th, etc) application that needs to leverage that authentication mechanism with a sync adapter. Follow the prescription outlined in step 1, however skip the steps to create the authentication mechanism. You’re still going to need a SyncAdapter.

5. In the sync adapter descriptor (syncadapter.xml in my example), specify the same account as defined by the authentication mechanism created in step #1 in the accountType attribute.

<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
    android:contentAuthority="com.captechventures.test.hc1"
    android:accountType="com.captechventures.mobile"
    android:supportsUploading="false"
/>

6. In the 2nd application’s manifest, specify a sharedUserId value and use the same value that was used in step #2. Optionally, provide a sharedUserLabel as this will be the name of your application under the account grouping.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.captechventures.test.hc1" android:versionCode="1"
android:versionName="1.0" android:sharedUserId="com.captechventures.mobile" android:sharedUserLabel="@string/app_name">
<uses-sdk android:minSdkVersion="8" />

7. When ready to publish your application, you must sign your 2nd application with the same certificate used to sign your first app. Without signing your application, Android will not allow your two apps to share resources (which you’ve specified you’d like to do with the same sharedUserId attribute in the manifest).

You now have 2 applications that can share/use the same authentication mechanism to your back end services! This makes credential management much easier on the user of your apps. A couple of points to remember:

1. When you request a sync with a content resolver, you MUST provide the Account with the appropriate accountType created in step 1 in the API call.

2. Clients are responsible for identifying if the authentication token is invalid, by making an API call to the AccountManager.

mAccountManager.invalidateAuthToken("com.captechventures.mobile", authToken);

3. The 2nd application requires that the 1st application be installed on the device for it work. You should install the authenticator with an application that will always be on your users’ devices or think of breaking that mechanism out into its own application.





Updating Enterprise Android Applications

24 03 2011

In my previous entry, I discussed the shortcomings of Android in relation to the Enterprise and how it’s not ready. Several commenters on stated we could get around this issue by adding this functionality to the application; being self-aware of application updates. My team had discussed adding this sort of functionality before my blog entry, so we decided to go ahead and pursue this. Close to 2 weeks ago, my team began gathering requirements and implementing a solution that would provide this self-awareness of application updates into our Android application. An installation problem still exists if your carrier blocks installing from external sources, but we’ll ignore that for this exercise.

Keep in mind, the original goal of creating the application was to become more familiar with Android, the APIs, the tools, documentation, etc. available for development. The application was just a proof of concept, not a necessity required by organization. All of our requirements come internally from our own team. First, we identified our requirements:

  1. Server component(s) will exist to provide latest version information as well as to store application apks.
  2. The application should be able to ping a server to determine if a newer version is available.
  3. The server should be able to inform the application if they are current, a new version is available, or if an update is to be “forced”. Other information provided from the server is location of new apk and the new version number.
    • Forced install doesn’t mimic true Android Market functionality, however we determined there would be a need to push critical application updates upon the user, even if they don’t want them.
  4. The application should auto check for app updates on launch, but only do so every 7 days.
    • We wanted to prevent unnecessary network traffic if a person launches the app several times a day.
  5. Once it has been identified a newer version exists, the check is no longer made.
    • The user is notified upon launch (every 7 days) if an update is available.
  6. The Update Check can also be made on demand whether a check has been made in the last 7 days or not.
    • Once an update has been identified the on demand Update Check becomes an on demand Install Update
      • On demand install launches a browser intent with the apk URL
      • The user must click the downloaded apk to install.
  7. If an update is forced, the user is notified of the required update.
    • A browser intent is fired to launch the Browser with the apk URL.
    • The user must click the downloaded apk to install.
    • The user is not allowed to open the app until the install is completed.

Implementing and testing this solution from an Android standpoint took about 2 weeks of spare time (read evenings and weekends) for myself. The server side component was handled by another team (ok, one person). Implementing this solution involved updating our SQLLite database, our ContentProvider, adding new menu items, creating some new AsyncTasks, updating all of our activities, etc. I consider this a significant effort. If you have multiple enterprise applications that need this same functionality, you’re going to have build time into your schedule. Some of these components could be built into a shareable library, however you’d still need to weave this into each specific application via resource descriptors, activities, etc. FYI, the patch file to support all of this was 1823 lines long.While this solution is fairly robust, an easy way out would be to provide a simple check to see if a version exists and to notify the user. This would require significantly less time to design, develop and test, but at what cost? When do you perform that check? When the user launches the app? What if the user spends a majority of their time off network and only launches when they have no connection? If you provide a manual check, you’re going to have to visit some of the requirements set out above and now you’re back to investing significant time into the SDLC.

In conclusion, you can provide this functionality inside of the application itself. You can make it as basic as possible to “just get by”, but it’s going to have its shortcomings. You could create a library to reduce development effort to integrate this into each application, but it’s still going to require time to weave it into the application and test. Why do this though when the Market is almost already there, it just needs some fine tuning? That or the Android OS needs to support enterprise deployment in a more robust manner. I still hold my belief that Android isn’t quite ready for the enterprise for this reason (among several others).





Android Development in the Enterprise

4 03 2011

Android’s just not ready yet. There I got it off my chest. Just like pulling off a Band-Aid. I’m not even talking about the myth of fragmentation (and for the most part, it is a myth) or the missing Wifi proxy settings issue. It’s what happens after you author that wonderful application your enterprise so desperately needs…how do you distribute it?

Anyone who knows me knows I’m a big evangelist for the platform, borderlining on fanboy-ism. No, I don’t constantly put down that other mobile device OS from the fruit company, I just love Android. It has its shortcomings just like any other device, no device is perfect. But for the most part, I’ve been very happy with my device and the operating system; as a consumer device, it’s wonderful.

However, I got a chance to test the waters as a developer for a proof of concept project for my employer, CapTech. I found all of the development resources for Android to be wonderfully exceptional; I was very surprised. Documentation, working demos, device simulators, support groups, and development tools all already existed and were easily accessible, not to mention very familiar as I had been developing Java/JEE applications for the last 12 years using Eclipse or some derivative of it. While not the most polished UI designer, I felt the shift from web application and server developer to Android developer fairly seamless. I would expect other IT shops to feel the same with backgrounds similar as mine.

Once our application was complete, it came time to determine how to distribute. We had a few beta testers that had Nexus Ones, custom ROMs, or rooted phones so we merely put the application on a web server and gave them the URL. This paradigm worked fine when there are only 3 or 4 users, however we would need to potentially deliver the application to our entire company of 200+ employees (not all are on Android, but hey we plan for the worst). We didn’t want to shove our application into the market and thus exposing it to the entire world for a mere 200 people. We could have housed the apk on a web server just like we did for our beta testers, but how would we manage updates to the application? Email blasts?

These weren’t even our biggest hurdles, it was the choice of carrier for our company, AT&T. Most of our employees are on the company business plan and thus, use an AT&T device. If you don’t know, AT&T blocks installation of Android apps from any other source other than the Android Market itself. Putting our application on a web server wasn’t even an option for most of our employees. I posed this question to the AT&T Developer Forum and received a less than sufficient response, “We’re looking into it”. This essentially means you won’t see something from us in quite some time, if at all.

For Enterprise distribution, Android really needs the ability to add multiple “Markets” or alternative repositories to the existing Android Market. On top of this Google needs to prohibit carriers like AT&T from locking out alternative sources altogether. I understand AT&T’s reasoning, however until a mechanism is in place, Android in the enterprise is completely shut out.

I’m including my post and response to the AT&T developer forum as I was having quite the difficulty obtaining a direct link to the thread.


My post:

My company has a business account with AT&T for our wireless solution. We have roughly 200+ employees on our AT&T wireless plan. We have developed an Android application that we’d like to distribute to our employees and ONLY our employees, so using the Android Market is not an option. However, AT&T has disabled “alternative sources” on AT&T Android devices eliminating the ability to allow our employees to install the application without rooting their phones, installing cooked ROMs, or using something like the Sideload Wonder Machine; all of which are not options in a corporate environment.

How is AT&T suggesting businesses handle this situation? Surely AT&T had foresight into this situation when they decided to cut off end user’s ability to install applications from sources other than the market? We have a legitimate business need to install enterprise applications and currently do not have the means to do so.


AT&T Reps response:

Thank you for explaining your business needs.

AT&T selected Android Market as the exclusive source for applications because it forces developers to be accountable for the apps they submit. If the Android community has issues with an app, the app can be flagged and removed.

As you are probably aware with Android, there is no approval process for applications–they are all accepted by default and Google has stated that they place apps in the Android Market within 24 hours of their submission.

At the same time, we know enterprises prefer not to use consumer storefronts and that that other platforms have methods to distribute applications directly to employees. We are looking at solutions for this now.

Sr Product Marketing Manager
Hsuan-hua Chang
http://www.facebook.com/atttdeveloper ( please join our fan page)