Tag Archives: html5

Viewport Meta Tag

Default Meta Tag

This is the default meta tag required to display the HTML5 app as it is without scaling, i.e. a scale of 1.0.

<meta name="viewport" content=" user-scalable = no, target-densitydpi = device-dpi, initial-scale=1, maximum-scale=1, width=320"/>

Note that we have used ‘device-dpi’ for ‘target-densitydpi’ as the default. Other values are ‘low-dpi’ (120dpi), ‘medium-dpi’ (160dpi), ‘high-dpi’ (240dpi).

Reference: http://darkforge.blogspot.com/2010/05/customize-android-browser-scaling-with.html

window.devicePixelRatio

An app built for the iPhone usually follows the ‘medium-dpi’ setting and if the device-DPI of a phone/tablet is lower than the ‘medium-dpi’, the HTML5 UI will appear larger (scaled up) which is generally acceptable. However, if the device-DPI is higher than the ‘medium-dpi’ that the app is developed based on. The HTML5 app will shrink and look too small compared to the physical screen dimensions, and this is not acceptable. To correct this, we need to set the ‘target-densitydpi’ value from ‘device-dpi’ to ‘medium-dpi’ for devices with higher resolutions.

In Javascript, we can use window.devicePixelRatio for Webkit browsers. A value of 1 is a 1:1 ratio of a virtual pixel to a physical pixel, a value >1 represents a high-density display.

If we do not want our HTML5 app to appear smaller than the DPI that the HTML5 app was designed for, the meta for ‘viewport’ will need to be altered dynamically as below:

if (window.devicePixelRatio>1) { // DPI higher than medium-dpi
var metatags = document.getElementsByTagName('meta');
for(cnt = 0; cnt < metatags.length; cnt++) {
var element = metatags[cnt];
if (element.getAttribute('name') == 'viewport') {
element.setAttribute('content','user-scalable = no,target-densitydpi = medium-dpi,initial-scale=1,maximum-scale=1, width=320');
}
}
}

Reference: http://stackoverflow.com/questions/1230019/how-to-set-viewport-meta-for-iphone-that-handles-rotation-properly

Tagged ,

Apple Push Notification Service (APNS)

iOS Developer Portal

The main purpose here is to set up the Apple Push Notification service SSL Certificates for both sandbox (development) and production environments.

In summary, the steps are:
1) Generate a Certificate Request. Normally, this CSR (Cert Signing Request) should have already been generated as the first step to configure a development certificate. Reuse the CSR if it is available, if not run “Keychain” -> Access Certificate Assistant -> Request a Certificate From a Certificate Authority and save the CSR to a file

2) Create an App ID with a specific Bundle Seed ID. Note that bundle IDs with wildcards will not be eligible to configure for push notifications. Goto your provisioning profile and use this App ID.

3) Once this App ID has been created, goto the Configure link to enable the Apple Push Notification service for this App ID. Reuse the CSR file in step 1 to generate the Development and Production Push certificates.

4) Download both certificates and save as .cer files

5) Double click both .cer files to install in Keychain Access and export the APN Certificates. For the development cert, select both the cert and the private key entries in Keychain Access and export to a .p12 file and enter a keystore password for the file. Do the same for the production cert and there will be two .p12 files which will be used as keystores for the Provider

Reference: http://mobiforge.com/developing/story/programming-apple-push-notification-services
Reference: http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Introduction/Introduction.html

Apple Push Notification Service Provider for Java (Java PNS)

This is the provider/server which will be sending the push notifications to the iOS devices. It does this by sending messages to the APNS which will then send the message to the target devices.

Java PNS is suitable for backend systems running Java. There are some commandline tools to get started here: http://code.google.com/p/javapns/wiki/CommandLineTools

The development .p12 file will be used to do the SSL authentication to the APNS. But the other important parameter required is the “device token”.

Reference: http://code.google.com/p/javapns/wiki/ReadMeFirst

iOS App and Device Token

The iOS app has to be set up to register push notifications and get back a device token. This token will be used by the APNS as the destination ID to set messages to. The Java PNS provider will use this device token to send a message.

In XCode, the Code Signing certificate would be the development certificate that has been configured to use the specific App ID configured with push notifications.

The following codes in AppDelegate.m will register the device for push notifications and receive a unique device token such as “a11ee50de6531b8a6f36a7aa30035df7c9e26b555a883d741c5e3d9bcd7961fb”:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
[window addSubview:viewController.view];
[window makeKeyAndVisible];

NSLog(@"Registering for push notifications...");
[[UIApplication sharedApplication]
registerForRemoteNotificationTypes:
(UIRemoteNotificationTypeAlert |
UIRemoteNotificationTypeBadge |
UIRemoteNotificationTypeSound)];

}

- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {

NSString *str = [NSString stringWithFormat:@"ios_device_token=%@",deviceToken];self.invokeString = str; self.invokeString = str; NSLog(@"didRegisterForRemoteNotificationsWithDeviceToken: %@", str);

}

- (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)err {

NSString *str = [NSString stringWithFormat: @"Error: %@", err];
NSLog(str);

}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {

for (id key in userInfo) {
NSLog(@"key: %@, value: %@", key, [userInfo objectForKey:key]);
}

}

This device token will need to be passed to the HTML5 app and this is done by setting the device token to the invokeString variable which will be available in the HTML5 as a JS variable. This is because for each user account, we need to store the device token, so that messages related to this account can be sent to the correct device. Therefore, the HTML5 app will need to know the device token and then call a webservice on the server to store the device token with the logged in user ID.

Reference: http://mobiforge.com/developing/story/programming-apple-push-notification-services

Tagged , ,

Google Cloud Messaging (GCM)

GCM Intro
To begin using GCM, we must have a Google ID. Please refer to the following Getting Started guide link below.

Reference: http://developer.android.com/guide/google/gcm/gs.html

Android App and Registration ID

The Android app will need to initiate a registration intent to the C2DM server with the email address of our Google ID and the app ID to enable C2DM for the device. If successful, the C2DM server will return the app a Registration ID which should be passed to our application server to store to the database and use it for notifications later

We need to configure the following in AndroidManifest.xml to enable the Intents for registration and receiving registrations.

Codes:
<!-- Only this application can receive the messages and registration result -->
<permission android:name="com.yourapp.permission.C2D_MESSAGE" android:protectionLevel="signature" />
<uses-permission android:name="com.yourapp.permission.C2D_MESSAGE" />
<!-- This app has permission to register and receive message -->
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />

<application android:icon=”@drawable/icon” android:label=”@string/app_name” android:debuggable=”true”>
<!– Only C2DM servers can send messages for the app. If permission is not set – any other app can generate it –>
<receiver android:name=”.App$MyGcmReceiver” android:permission=”com.google.android.c2dm.permission.SEND”>
<!– Receive the actual message –>
<intent-filter>
<action android:name=”com.google.android.c2dm.intent.RECEIVE” />
<category android:name=”com.yourapp” />
</intent-filter>
<!– Receive the registration id –>
<intent-filter>
<action android:name=”com.google.android.c2dm.intent.REGISTRATION” />
<category android:name=”com.yourapp” />
</intent-filter>
</receiver>

</application>

The following codes in App.java will register the device for push notifications and receive a unique Registration ID such as:
“APA91bFkFObBt_BO8K4LL4rbf3wS6uTM32GoVaBG2G4GAE2QhF-sRFZGCGFGXYtuTj9fYDWk0Ec6wFgnsS8QyiLzJlZqe-mML2IufVwWka7_sFQbH2zAUhJ7cAVGud38QzUmnidbyATZ”

Codes:
...
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

mc = new MyClass(this, super.appView);
super.appView.addJavascriptInterface(mc, “MyClass”);
Intent registrationIntent = new Intent(“com.google.android.c2dm.intent.REGISTER”);
registrationIntent.putExtra(“app”, PendingIntent.getBroadcast(this, 0, new Intent(), 0)); // boilerplate
registrationIntent.putExtra(“sender”, [value of Project ID]);
String deviceId = Settings.System.getString(getContentResolver(),Settings.System.ANDROID_ID);
mc.setDeviceid(deviceId);
startService(registrationIntent);
}

public static class MyGcmReceiver extends BroadcastReceiver {
private Context context;
@Override
public void onReceive(Context context, Intent intent) {
this.context = context;
if (intent.getAction().equals(“com.google.android.c2dm.intent.REGISTRATION”)) {
handleRegistration(context, intent);
} else if (intent.getAction().equals(“com.google.android.c2dm.intent.RECEIVE”)) {
handleMessage(context, intent);
}
}

private void handleRegistration(Context context, Intent intent) {
String registration = intent.getStringExtra(“registration_id”);
if (intent.getStringExtra(“error”) != null) {
// Registration failed, should try again later.
Log.d(“gcm”, “registration failed”);
String error = intent.getStringExtra(“error”);
if(error == “SERVICE_NOT_AVAILABLE”){
Log.d(“gcm”, “SERVICE_NOT_AVAILABLE”);
}else if(error == “ACCOUNT_MISSING”){
Log.d(“gcm”, “ACCOUNT_MISSING”);
}else if(error == “AUTHENTICATION_FAILED”){
Log.d(“gcm”, “AUTHENTICATION_FAILED”);
}else if(error == “TOO_MANY_REGISTRATIONS”){
Log.d(“gcm”, “TOO_MANY_REGISTRATIONS”);
}else if(error == “INVALID_SENDER”){
Log.d(“gcm”, “INVALID_SENDER”);
}else if(error == “PHONE_REGISTRATION_ERROR”){
Log.d(“gcm”, “PHONE_REGISTRATION_ERROR”);
}
} else if (intent.getStringExtra(“unregistered”) != null) {
// unregistration done, new messages from the authorized sender will be rejected
Log.d(“gcm”, “unregistered”);
} else if (registration != null) {
//Log.d(“gcm”, “registration: “+registration);
mc.setRegistrationid(registration);
}
}

private void handleMessage(Context context, Intent intent)
{
//Do whatever you want with the message
NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
int icon = R.drawable.notificationicon; // icon from resources
CharSequence tickerText = “Alert from Your App”; // ticker-text
long when = System.currentTimeMillis(); // notification time
Context context = mContext.getApplicationContext(); // application Context
CharSequence contentTitle = “Your App Name”; // text
CharSequence contentText = intent.getStringExtra(“payload”); // expanded message

Intent notificationIntent = new Intent(context, App.class); // call App class, ie. launch this App
notificationIntent.setAction(Intent.ACTION_MAIN);
notificationIntent.addCategory(Intent.CATEGORY_LAUNCHER);
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);

// the next two lines initialize the Notification, using the
// configurations above
Notification notification = new Notification(icon, tickerText, when);
notification.defaults |= Notification.DEFAULT_LIGHTS;
notification.ledARGB = 0xff00ff00;
notification.ledOnMS = 300;
notification.ledOffMS = 1000;
notification.flags |= Notification.FLAG_SHOW_LIGHTS;
notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
notificationManager.notify(10001, notification);
}
}

The handleMessage() method will do the necessary to display a notification message on the device and when the notification is clicked, it will launch your app.

The MyClass class holds the Device ID and Registration ID and it is exposed to the HTML5 app using this line of code “super.appView.addJavascriptInterface(mc, “MyClass”)”. In the HTML5 app, we simply call “window.MyClass.getDeviceid()” and “window.MyClass.getRegistrationid()” to get the Device ID and Registration ID. So the HTML5 app will be able to call another web service to our app server with the account username, Device ID and Registration ID to be stored into our system. As the Registration ID might be changed periodically by the GCM server, we need to keep updating the Registration ID tied to each Device ID.

Reference: http://developer.android.com/guide/google/gcm/gs.html
Reference: http://developer.android.com/guide/google/gcm/gcm.html

Sending a Message from the App Server

Follow this GCM Demo guide from Google – http://developer.android.com/guide/google/gcm/demo.html and integrate the codes into your Java app server.

 

Tagged , ,

HTML5 Offline Solution

Although PhoneGap is an offline solution where the HTML, CSS and JS files etc are held in the assets/www folder within the app, there are some cases where you might want the app to point to a remote server where the HTML5 file and its CSS/JS are located. They can be downloaded onto your device and ran as offline files when the device has no Internet connection.

The requirements to trigger the HTML5 offline cache are detailed below and each requirement must be implemented with care, otherwise it will not work:

1) The MIME Type for a Manifest file.
This is very important. Previously I have followed a solution to add a mime type by using a .htaccess file and place it together with index.html of my HTML5 app, but it didn’t work.

In my case, I was using the Tomcat server and the surefire way that the .manifest file will be correctly used by the server, is to modify “/conf/web.xml” and add in the mime mapping as below:
<mime-mapping>
<extension>manifest</extension>
<mime-type>text/cache-manifest</mime-type>
</mime-mapping>

2) Specify the Manifest File
This is a standard configuration. For every HTML file that wants to use the cache manifest, they have to update their tag to

3) Configure the Manifest File
The manifest file is a text file that you have configure to tell the HTML5 app what is to be cached for offline use. A sample file is below:
CACHE MANIFEST

# Version 0.1

# Explicitly cached entries
index.html
jqtouch/jqtouch.css
jqtouch/jqtouch.js
jqtouch/jquery-1.4.2.min.js
js/common.js
images/1.png
images/2.png

# All other resources (e.g. sites) require the user to be online.
NETWORK:
*

* note that the manifest file only caches static resources such as .html, .js and images, so files like JSP pages will not be applicable here.

***VERY IMPT: Each entry to be cached must be correctly named and if the entry is there, the physical file must be there, otherwise the whole caching will fail!

4) For iOS, steps 1-3 are sufficient for HTML5 offline caching to work. However, in the case of Android, we need to fix the Android Shell Native App to enable HTML5 caching:

In your App.java class, there are these lines of codes to be added as part of the solution to enable HTML5 caching:
public class App extends DroidGap {
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

...

super.appView.getSettings().setDomStorageEnabled(true);
super.appView.getSettings().setAppCacheMaxSize(1024*1024*8);
super.appView.getSettings().setAppCachePath("/data/data/com.yourdomain/cache");
super.appView.getSettings().setAllowFileAccess(true);
super.appView.getSettings().setAppCacheEnabled(true);
super.appView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);

...
}
...
}

Reference: http://alex.tapmania.org/2010/11/html5-cache-android-webview.html

Tagged , ,

HTML5 App Within a Native Shell App

The 2 previous postings were about getting the native apps up and running for their respective Android and iOS environments. The official guide takes you to a Hello World index.html which merely displays a static message. In reality, the app that your user will interact with is the UI of the index.html or the HTML5 app. The underlying native app that runs the index.html is what I call a Shell Native App.

So, the actual primary app is a HTML5 file with various supporting web libraries to fulfill the functions of a full-fledged mobile app. Generally, it should have the following supporting technologies to give it the native look and feel and experience:

1) jQuery (www.jquery.com)
2) AJAX (With AJAX, our mobile application can send data to and retrieve data from a server asynchronously (in the background) without interfering with the UI experience of the existing page.)
3a) jQTouch (www.jqtouch.com)
3b) jQuerymobile (www.jquerymobile.com)

As for the Shell Native Apps, they host the app icon, the splash screen, PhoneGap libraries and other native information. If you require certain native functions, it is inevitable that you will need to modify the natives codes for your Shell Native Apps. A good example would be the implementation of Push Notifications for both iOS and Android, you are unable to do this in the HTML5 app, which means you have to put in codes in your Shell Native App.

In summary, PhoneGap is a bridge that allows the HTML5 app to call PhoneGap Javascript (JS) methods which will call PhoneGap native methods in your Shell Native Apps, of course there are native events that can call JS events which you can use in your HTML5 apps. Not all native methods can be called from JS, so some native programming would be expected depending on your app requirements.

Tagged , ,