Tuesday, April 29, 2014

Programming things to remember 2014 April

Using Android's DownloadManager with JSESSIONID


We had to download a file from the internet, which was bound to a Java http session. During login, we got the JSESSIONID in the response. For all subsequent http request we have to set a Http header: "Cookie: JSESSIONID=EEDAC46D" (in more detail see this). This also have to be given to the DownloadManager via the addRequestHeader() call:

return new DownloadManager.Request(Uri.parse(url))
                .addRequestHeader("Cookie", cookie) // here we set the JSESSIONID along with the other cookies
                .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, downloadDecorator.filePathAndName)
                .setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI | DownloadManager.Request.NETWORK_MOBILE)
                .setMimeType(MIME_TYPE_PDF)
                .setDescription("description of the download")
                .setTitle("title of the download");

        final DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);

        downloadManager.enqueue(downloadRequest); // start the download


Syncing Java and webkit cookie store


When developing an app that uses both Java's Http connections and Html pages embedded in a Webview, and both would need the same jsession, we need to synchronise them. Because both the webkit (and the Webview is part of it as well) uses different CookieStore than the Java side. Fortunately the webkit has setCookie() and sync() methods:

// java cookie store
        final CookieManager cookieManagerNet = (CookieManager) javaCookieManagerWrapper.getDefault();
        final CookieStore javaCookieStore = cookieManagerNet.getCookieStore();

        // webkit cookie store
        android.webkit.CookieManager cookieManager =  webKitCookieManagerWrapper.getInstance();

        // copy cookies
        for(HttpCookie httpCookie : javaCookieStore.get(uri)) {
            cookieManager.setCookie(uri.toString(), httpCookie.toString());
        }

        //sync
        android.webkit.CookieSyncManager.getInstance().sync();

Unfortunately that sync() only schedules a delayed Message to a Handler which is attached to another Thread. Which after a time will do the actual synchronisation. This is unpredictable and may result in the webview having different jsession than the native one. Eg. even though the user logged in the native java app, opening the webpage in a webview (which needs authentication as well) the web login is displayed.

Digging down in the android sources we find the android.webkit.WebSyncManager.SyncHandler.handleMessage() calls the android.webkit.CookieSyncManager.syncFromRamToFlash() which actually does the sync synchronously. This is handy. The downside is it's protected, but using Reflection this isn't an issue (the try-catch block is omitted for brevity):

//sync
        // not android.webkit.CookieSyncManager.getInstance().sync();
        final android.webkit.CookieSyncManager cookieSyncManager = CookieSyncManager.getInstance();

        final Class clazz = ((Object)cookieSyncManager).getClass();
        final Method syncFromRamToFlashMethod = clazz.getDeclaredMethod("syncFromRamToFlash");
        syncFromRamToFlashMethod.setAccessible(true);
        syncFromRamToFlashMethod.invoke(cookieSyncManager);
        syncFromRamToFlashMethod.setAccessible(false);


Works well from Gingerbread. Unfortunately (again) on Gingerbread there is an other cookie related issue: http://android.joao.jp/2010/11/cookiemanager-and-removeallcookie.html

No comments: