36

Unfortunately, there's a multitude of cookie managers for Android. The cookies for HttpURLConnection are maintained by java.net.CookieManager and the cookies for WebView are maintained by android.webkit.CookieManager. These cookie repositories are separate and require manual synchronization.

My app uses both HttpURLConnections and shows WebViews (it's a native-HTML hybrid). Naturally, I want both to share all cookies - so I will have a transparent session all across.

More Specifically:

  1. When a cookie is set/changed in an HttpURLConnection, I want the WebViews to see this change as well.
  2. When a cookie is set/changed in a WebView, I want the next HttpURLConnections to see this change as well.

Simply put - I'm looking for a two-way sync. Or even better, to have them both use the same cookie repository. You can assume both are active in the same time (like on different tabs).

Questions:

  1. Is there a way to make both use the same cookie repository?

  2. If not, what is the recommended practice to do the manual sync? When exactly should I sync and how?

Related Question: This question tackles a similar issue, but only implements one-way sync (HttpURLConnection -> WebView).

My Best Idea So Far: I really want to avoid a manual sync, so I tried to think how to make both use the same repository. Maybe I can create my own core handler which extends java.net.CookieManager. I will set it as the core cookie handler using java.net.CookieHandler.setDefault(). Its implementation will be a proxy to the android.webkit.CookieManager handler instance (for every function I'll simply access the webkit manager).

Community
  • 1
  • 1
talkol
  • 12,564
  • 11
  • 54
  • 64

1 Answers1

66

I've implemented my own idea. It's actually pretty cool. I've created my own implementation of java.net.CookieManager which forwards all requests to the WebViews' webkit android.webkit.CookieManager. This means no sync is required and HttpURLConnection uses the same cookie storage as the WebViews.

Class WebkitCookieManagerProxy:

import java.io.IOException;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.CookieStore;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

public class WebkitCookieManagerProxy extends CookieManager 
{
    private android.webkit.CookieManager webkitCookieManager;

    public WebkitCookieManagerProxy()
    {
        this(null, null);
    }

    public WebkitCookieManagerProxy(CookieStore store, CookiePolicy cookiePolicy)
    {
        super(null, cookiePolicy);

        this.webkitCookieManager = android.webkit.CookieManager.getInstance();
    }

    @Override
    public void put(URI uri, Map<String, List<String>> responseHeaders) throws IOException 
    {
        // make sure our args are valid
        if ((uri == null) || (responseHeaders == null)) return;

        // save our url once
        String url = uri.toString();

        // go over the headers
        for (String headerKey : responseHeaders.keySet()) 
        {
            // ignore headers which aren't cookie related
            if ((headerKey == null) || !(headerKey.equalsIgnoreCase("Set-Cookie2") || headerKey.equalsIgnoreCase("Set-Cookie"))) continue;

            // process each of the headers
            for (String headerValue : responseHeaders.get(headerKey))
            {
                this.webkitCookieManager.setCookie(url, headerValue);
            }
        }
    }

    @Override
    public Map<String, List<String>> get(URI uri, Map<String, List<String>> requestHeaders) throws IOException 
    {
        // make sure our args are valid
        if ((uri == null) || (requestHeaders == null)) throw new IllegalArgumentException("Argument is null");

        // save our url once
        String url = uri.toString();

        // prepare our response
        Map<String, List<String>> res = new java.util.HashMap<String, List<String>>();

        // get the cookie
        String cookie = this.webkitCookieManager.getCookie(url);

        // return it
        if (cookie != null) res.put("Cookie", Arrays.asList(cookie));
        return res;
    }

    @Override
    public CookieStore getCookieStore() 
    {
        // we don't want anyone to work with this cookie store directly
        throw new UnsupportedOperationException();
    }
}

And use it by doing this on your application initialization:

android.webkit.CookieSyncManager.createInstance(appContext);
// unrelated, just make sure cookies are generally allowed
android.webkit.CookieManager.getInstance().setAcceptCookie(true);

// magic starts here
WebkitCookieManagerProxy coreCookieManager = new WebkitCookieManagerProxy(null, java.net.CookiePolicy.ACCEPT_ALL);
java.net.CookieHandler.setDefault(coreCookieManager);

Testing

My initial testing show this is working well. I see cookies shared between the WebViews and HttpURLConnection. I hope I'll not run into any issues. If you try this out and discover any problem, please comment.

ol_v_er
  • 27,094
  • 6
  • 48
  • 61
talkol
  • 12,564
  • 11
  • 54
  • 64
  • This method also allowed me to get around some issues with the java.net.CookieManager. A website was settings cookies with a slightly modified domain and the .net CookieManager wasn't cooperating. This solution fixed that. – thepenguin77 Nov 24 '13 at 00:39
  • You are passing the `CookiePolicy` as parameter to the overwritten constructor. Did you actually find a way to make androids `CookieManager` use that policy or don't you care about the policy so far? – ohcibi Jan 13 '14 at 13:16
  • 1
    how about persisting the CookieStore when the android app closes? – Aivan Monceller Jul 17 '14 at 08:39
  • does it persist cookies and reload on app start ? If not then, how can we implement it ? – coding_idiot Aug 06 '14 at 11:24
  • Works with Retrofit.. thnxx !! – ZealDeveloper Nov 18 '14 at 09:32
  • If the server tries to delete cookies with expires as a date in the past, then this solution will not succeed in deleting the cookie. This is clearly mentioned in the javadoc [link](developer.android.com/reference/android/webkit/…, java.lang.String))) "The cookie being set will be ignored if it is expired." I also verified the same in Android 4.2.1 device. – garnet Apr 19 '15 at 10:39
  • 2
    If you're your targets are API level 21+, you might need to consider the changes in the [Webview-behaviour](http://developer.android.com/about/versions/android-5.0-changes.html) with cookies and mixed-content. This implementation relies on the android.webkit.CookieManager after all. – Seb B. Nov 10 '15 at 07:10
  • 1
    For some reason, in my case, the code doesn't go beyond the following line: `// get the cookie String cookie = this.webkitCookieManager.getCookie(url);` I'm not sure what kind of an issue happens here. I'm not getting any exception so it might be some lock or a deadlock... – Alexander K Jan 18 '16 at 09:33
  • The stacktrace hangs on the following method: at com.android.org.chromium.android_webview.AwCookieManager.nativeGetCookie(Native Method) at c.a.o.c.android_webview.AwCookieManager.getCookie(AwCookieManager.java:50) at c.a.webview.chromium.CookieManagerAdapter.getCookie(CookieManagerAdapter.java:58) at WebkitCookieManagerProxy.get(WebkitCookieManagerProxy.java:78) at c.a.okhttp.internal.http.HttpEngine.prepareRawRequestHeaders(HttpEngine.java:540) ... at c.a.webview.chromium.WebViewContentsClientAdapter.shouldInterceptRequest(WebViewContentsClientAdapter.java:283) – Alexander K Jan 23 '16 at 06:41
  • @AlexanderK I faced same issue as you mentioned. I narrowed down the problem to happen in Android 4.2, 4.3. It works fine in Android 5.0 and above. The logs indicate: Line 32: 01-05 03:52:52.718: W/dalvikvm(6423): Invalid indirect reference 0x41594650 in decodeIndirectRef Googling the above error leads to some stackoverflow links which are related to JNI calls. CookieManager.getCookie also uses JNI calls. Have you found any solution for this issue? talkol, Any suggestion? – garnet Mar 14 '17 at 12:45