I have a Xamarin.Forms application with a webview, when I navigate through the web and click on an input to open the "file chooser dialog", there is no dialog appearing.
So, I have created my own custom WebViewRenderer, to allow some operations, but no way, I can't find any way to make it work.
I have added this, but it does not help:
Control.Settings.AllowFileAccess = true;
I have no error message at all... does someone has an idea?
PS: I have searched for other posts, but none seems to help.
Here is the code of the WebViewRenderer for Android:
using Foundation;
using SmartPixel.SoCloze.Mobile.Interop;
using SmartPixel.SoCloze.Mobile.iOS.Renderers;
using System.Net;
using WebKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace SmartPixel.SoCloze.Mobile.iOS.Renderers
{
public class HybridWebViewRenderer : WkWebViewRenderer, IWKScriptMessageHandler
{
private const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
private WKUserContentController _userController;
public HybridWebViewRenderer() : this(new WKWebViewConfiguration())
{
}
public HybridWebViewRenderer(WKWebViewConfiguration config) : base(config)
{
_userController = config.UserContentController;
var script = new WKUserScript(new NSString(JavaScriptFunction), WKUserScriptInjectionTime.AtDocumentEnd, false);
_userController.AddUserScript(script);
_userController.AddScriptMessageHandler(this, "invokeAction");
}
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
//---- Interop
if (e.OldElement != null)
{
_userController.RemoveAllUserScripts();
_userController.RemoveScriptMessageHandler("invokeAction");
HybridWebView hybridWebViewMain = e.OldElement as HybridWebView;
hybridWebViewMain?.Cleanup();
}
if (e.NewElement != null)
{
}
//---- Cookies
if (e.OldElement == null)
{
HybridWebView hybridWebViewMain = this.Element as HybridWebView;
hybridWebViewMain.OnRequestNativeSetCookie += AppCookiesManager_OnSetCookie;
hybridWebViewMain.OnRequestNativeCookieRead += HybridWebView_OnRequestNativeCookieRead;
hybridWebViewMain.OnRequestNativeCookieWrite += HybridWebView_OnRequestNativeCookieWrite;
}
}
public void DidReceiveScriptMessage(WKUserContentController userContentController, WKScriptMessage message)
{
((HybridWebView)Element).InvokeAction(message.Body.ToString());
}
private async void AppCookiesManager_OnSetCookie(Cookie cookie)
{
string js = $"document.cookie = '{cookie.Name}={cookie.Value};Secure;path=/'";
this.EvaluateJavaScript(js, null);
}
private void HybridWebView_OnRequestNativeCookieRead(string url, CookieCollection cookies)
{
}
private void HybridWebView_OnRequestNativeCookieWrite(string url, CookieCollection cookies)
{
}
}
}
I have find the solution, after hours of research, hope it will help others. I had to override the 'OnShowFileChooser' method.
I provides the full code of the Android HybridWebView:
public class HybridWebViewRenderer : WebViewRenderer
{
private const string JavascriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
private Context _context;
public HybridWebViewRenderer(Context context) : base(context)
{
_context = context;
global::Android.Webkit.WebView.SetWebContentsDebuggingEnabled(true);
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
//---- Interop
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
((HybridWebView)Element).Cleanup();
}
if (e.NewElement != null)
{
Control.AddJavascriptInterface(new JsBridge(this), "jsBridge");
}
//---- Cookies
if (e.OldElement == null)
{
Control.Settings.AllowFileAccess = true;
Control.Settings.AllowFileAccessFromFileURLs = true;
Control.Settings.AllowUniversalAccessFromFileURLs = true;
Control.SetWebViewClient(new CookieWebViewClient(Element as HybridWebView));
Control.SetWebChromeClient(new HybridWebChromeClient(_context));
}
}
}
internal class HybridWebChromeClient : WebChromeClient
{
private Context _context;
private MainActivity _mainActivity;
public HybridWebChromeClient(Context context)
{
_context = context;
_mainActivity = context as MainActivity;
}
[TargetApi(Value = 21)]
public override void OnPermissionRequest(PermissionRequest request)
{
((Android.App.Activity)_context).RunOnUiThread(() =>
{
request.Grant(request.GetResources());
});
}
public override bool OnShowFileChooser(Android.Webkit.WebView webView, IValueCallback filePathCallback, FileChooserParams fileChooserParams)
{
_mainActivity._uploadMessage = filePathCallback;
Intent i = new Intent(Intent.ActionGetContent);
i.AddCategory(Intent.CategoryOpenable);
i.SetType("*/*");
// The camera intent
Intent captureIntent = new Intent(Android.Provider.MediaStore.ActionImageCapture);
List<IParcelable> targetedShareIntents = new List<IParcelable>();
targetedShareIntents.Add(captureIntent);
captureIntent.AddCategory(Intent.ActionCameraButton);
//add camera intent to the main intent (i)
i.PutExtra(Intent.ExtraInitialIntents, targetedShareIntents.ToArray());
_mainActivity.StartActivityForResult(Intent.CreateChooser(i, "File Chooser"), 1);
return true;
}
//public override bool OnShowFileChooser(Android.Webkit.WebView webView, IValueCallback filePathCallback, FileChooserParams fileChooserParams)
//{
// _mainActivity.mUploadMessage = filePathCallback;
// Intent Fileintent = new Intent(Intent.ActionGetContent);
// Fileintent.AddCategory(Intent.CategoryOpenable);
// Fileintent.SetType("/");
// var intents = new List<Intent>();
// if (_context.PackageManager.HasSystemFeature(PackageManager.FeatureCameraAny))
// {
// Intent captureIntent = new Intent(Android.Provider.MediaStore.ActionImageCapture);
// var resolveinfolist = _context?.PackageManager?.QueryIntentActivities(captureIntent, 0);
// if (resolveinfolist != null && resolveinfolist.Count > 0)
// {
// var item = resolveinfolist[0];
// var packagename = item.ActivityInfo?.PackageName;
// var intent = new Intent(captureIntent);
// intent.SetComponent(new ComponentName(packagename, item.ActivityInfo.Name));
// intent.SetPackage(packagename);
// intent.AddFlags(ActivityFlags.GrantReadUriPermission);
// intent.AddFlags(ActivityFlags.GrantWriteUriPermission);
// File storageDir = new File(Environment.ExternalStorageDirectory, "filename");
// var file = Android.OS.Environment.GetExternalStoragePublicDirectory(Environment.DirectoryPictures);
// _mainActivity.ImageFile = storageDir;
// var uri = FileProvider.GetUriForFile(_context, _context.PackageName + ".provider", storageDir);
// intent.PutExtra(Android.Provider.MediaStore.ExtraOutput, uri);
// intents.Add(intent);
// }
// }
// intents.Add(Fileintent);
// var res = Intent.CreateChooser(intents[0], fileChooserParams.Title);
// intents?.RemoveAt(0);
// res.PutExtra(Intent.ExtraInitialIntents, intents?.ToArray());
// _mainActivity.StartActivityForResult(res, 1000);
// return true;
//}
}
internal class CookieWebViewClient : WebViewClient
{
private readonly HybridWebView _hybridWebView;
internal CookieWebViewClient(HybridWebView hybridWebView)
{
_hybridWebView = hybridWebView;
_hybridWebView.OnRequestNativeSetCookie += AppCookiesManager_OnSetCookie;
_hybridWebView.OnRequestNativeCookieRead += HybridWebView_OnRequestNativeCookieRead;
_hybridWebView.OnRequestNativeCookieWrite += HybridWebView_OnRequestNativeCookieWrite;
}
private void HybridWebView_OnRequestNativeCookieWrite(string url, CookieCollection cookies)
{
CookieManager.Instance.Flush();
}
private void HybridWebView_OnRequestNativeCookieRead(string url, CookieCollection cookies)
{
}
private void AppCookiesManager_OnSetCookie(Cookie cookie)
{
string cookieValue = cookie.Value;
string cookieDomain = cookie.Domain;
string cookieName = cookie.Name;
// Path
// Secure
// Expires
string currentCookie = CookieManager.Instance.GetCookie(cookieDomain) ?? "";
CookieManager.Instance.SetCookie(cookieDomain, cookieName + "=" + cookieValue + ";" + currentCookie);
CookieManager.Instance.Flush();
}
public override void OnPageFinished(global::Android.Webkit.WebView view, string url)
{
// Set all the cookies on the view
if (Build.VERSION.SdkInt < BuildVersionCodes.Lollipop)
CookieSyncManager.Instance.Sync();
CookieManager.Instance.Flush();
CookieManager.AllowFileSchemeCookies();
CookieManager.SetAcceptFileSchemeCookies(true);
CookieManager.Instance.AcceptCookie();
CookieManager.Instance.AcceptThirdPartyCookies(view);
CookieManager.Instance.SetAcceptCookie(true);
CookieManager.Instance.SetAcceptThirdPartyCookies(view, true);
}
}
I am working on an android application where user will login in app using their gmail account. I need to show list of youtube playlist of that user. I have used following code -
private String getAllPlayList() {
YouTube youtube = new YouTube.Builder(new NetHttpTransport(), new JacksonFactory(), new HttpRequestInitializer() {
public void initialize(HttpRequest request) throws IOException {
}
}).setApplicationName("My Project").build();
try {
HashMap<String, String> parameters = new HashMap<>();
parameters.put("part", "snippet,contentDetails");
parameters.put("mine", "true");
parameters.put("maxResults", "25");
parameters.put("onBehalfOfContentOwner", "");
parameters.put("onBehalfOfContentOwnerChannel", "");
parameters.put("key","AIzaSyASISGgWoBqDijVmzUpYCbgaWJ_ULJWdiQ");
YouTube.Playlists.List playlistsListMineRequest = youtube.playlists().list(parameters.get("part").toString());
if (parameters.containsKey("mine") && parameters.get("mine") != "") {
boolean mine = (parameters.get("mine") == "true") ? true : false;
playlistsListMineRequest.setMine(true);
}
if (parameters.containsKey("maxResults")) {
playlistsListMineRequest.setMaxResults(Long.parseLong(parameters.get("maxResults").toString()));
}
if (parameters.containsKey("onBehalfOfContentOwner") && parameters.get("onBehalfOfContentOwner") != "") {
playlistsListMineRequest.setOnBehalfOfContentOwner(parameters.get("onBehalfOfContentOwner").toString());
}
if (parameters.containsKey("onBehalfOfContentOwnerChannel") && parameters.get("onBehalfOfContentOwnerChannel") != "") {
playlistsListMineRequest.setOnBehalfOfContentOwnerChannel(parameters.get("onBehalfOfContentOwnerChannel").toString());
}
if (parameters.containsKey("key") && parameters.get("key") != "") {
playlistsListMineRequest.setKey(parameters.get("key").toString());
}
PlaylistListResponse response = playlistsListMineRequest.execute();
Log.d("PlayList",response.toString());
return response.toPrettyString();
}
catch (Exception e)
{
e.printStackTrace();
return e.getMessage().toString();
}
}
But I am getting following response -
"message" : "The request uses the mine parameter but is not properly authorized.",
Please help.
I know this kind of questions are maybe too old, but I got stock with this silly thing.
I have an AsyncTask class which is a subclass of an activity class, and right now I want to call it from another class: following codes shows what I mean:
public class STA extends Activity {
public class ListSpdFiles extends AsyncTask<Void, Void, String[]> {
private static final String TAG = "ListSpdFiles: ";
/**
* Status code returned by the SPD on operation success.
*/
private static final int SUCCESS = 4;
private String initiator;
private String path;
private SecureApp pcas;
private boolean isConnected = false; // connected to PCAS service?
private PcasConnection pcasConnection = new PcasConnection() {
#Override
public void onPcasServiceConnected() {
Log.d(TAG, "pcasServiceConnected");
latch.countDown();
}
#Override
public void onPcasServiceDisconnected() {
Log.d(TAG, "pcasServiceDisconnected");
}
};
private CountDownLatch latch = new CountDownLatch(1);
public ListSpdFiles(String initiator, String path) {
this.initiator = initiator;
this.path = path;
}
private void init() {
Log.d(TAG, "starting task");
pcas = new AndroidNode(getApplicationContext(), pcasConnection);
isConnected = pcas.connect();
}
private void term() {
Log.d(TAG, "terminating task");
if (pcas != null) {
pcas.disconnect();
pcas = null;
isConnected = false;
}
}
#Override
protected void onPreExecute() {
super.onPreExecute();
init();
}
#Override
protected String[] doInBackground(Void... params) {
// check if connected to PCAS Service
if (!isConnected) {
Log.v(TAG, "not connected, terminating task");
return null;
}
// wait until connection with SPD is up
try {
if (!latch.await(20, TimeUnit.SECONDS)) {
Log.v(TAG, "unable to connected within allotted time, terminating task");
return null;
}
} catch (InterruptedException e) {
Log.v(TAG, "interrupted while waiting for connection in lsdir task");
return null;
}
// perform operation (this is where the actual operation is called)
try {
return lsdir();
} catch (DeadServiceException e) {
Log.i(TAG, "service boom", e);
return null;
} catch (DeadDeviceException e) {
Log.i(TAG, "device boom", e);
return null;
}
}
#Override
protected void onPostExecute(String[] listOfFiles) {
super.onPostExecute(listOfFiles);
if (listOfFiles == null) {
Log.i(TAG, "task concluded with null list of files");
// tv.setText("task concluded with a null list of files");
} else {
Log.i(TAG, "task concluded with the following list of files: "
+ Arrays.toString(listOfFiles));
//tv.setText("List of files received is:\n" + Arrays.toString(listOfFiles));
}
term();
}
#Override
protected void onCancelled(String[] listOfFiles) {
super.onCancelled(listOfFiles);
Log.i(TAG, "lsdir was canceled");
term();
}
/**
* Returns an array of strings containing the files available at the given path, or
* {#code null} on failure.
*/
private String[] lsdir() throws DeadDeviceException, DeadServiceException {
Result<List<String>> result = pcas.lsdir(initiator, path); // the lsdir call to the
final Global globalVariable = (Global) getApplicationContext();
if (globalVariable.getPasswordButt() == false) {
// Calling Application class (see application tag in AndroidManifest.xml)
// Get name and email from global/application context
final boolean isusername = globalVariable.getIsUsername();
if (isusername == true) {
String username = "/" + getLastAccessedBrowserPage() + ".username" + ".txt";
//String password = "/" + CurrentURL + "password" + ".txt";
ByteArrayOutputStream baos = new ByteArrayOutputStream();
pcas.readFile(initiator, username, baos);
Log.i(TAG, "OutputStreampassword: "
+ new String(baos.toByteArray()));
String name = new String(baos.toByteArray());
if (!name.equalsIgnoreCase("")) {
globalVariable.setUsername(name);
// getCurrentInputConnection().setComposingText(name, 1);
// updateCandidates();
}
globalVariable.setIsUsername(false);
} else if (isusername == false)
Log.i(TAG, "Wrong Input Type For Username.");
// globalVariable.setUsernameButt(false);
} else if (globalVariable.getPasswordButt() == true) {
// Calling Application class (see application tag in AndroidManifest.xml)
// final Global globalVariable = (Global) getApplicationContext();
// Get name and email from global/application context
final boolean ispassword = globalVariable.getIsPassword();
if (ispassword == true) {
// String username = "/" + CurrentURL + "username" + ".txt";
String password = "/" + getLastAccessedBrowserPage() + ".password" + ".txt";
ByteArrayOutputStream baos = new ByteArrayOutputStream();
pcas.readFile(initiator, password, baos);
Log.i(TAG, "OutputStreampassword: "
+ new String(baos.toByteArray()));
String name = new String(baos.toByteArray());
if (!name.equalsIgnoreCase("")) {
globalVariable.setPassword(name);
//getCurrentInputConnection().setComposingText(name, 1);
// updateCandidates();
}
globalVariable.setIsPassword(false);
} else if (ispassword == false)
Log.i(TAG, "Wrong Input Type For Password.");
globalVariable.setPasswordButt(false);
// boolpassword=false;
}
//}
if (result.getState() != SUCCESS) {
Log.v(TAG, "operation failed");
return null;
}
if (result.getValue() == null) {
Log.v(TAG, "operation succeeded but operation returned null list");
return null;
}
return result.getValue().toArray(new String[0]);
}
}
public String getLastAccessedBrowserPage() {
String Domain = null;
Cursor webLinksCursor = getContentResolver().query(Browser.BOOKMARKS_URI, Browser.HISTORY_PROJECTION, null, null, Browser.BookmarkColumns.DATE + " DESC");
int row_count = webLinksCursor.getCount();
int title_column_index = webLinksCursor.getColumnIndexOrThrow(Browser.BookmarkColumns.TITLE);
int url_column_index = webLinksCursor.getColumnIndexOrThrow(Browser.BookmarkColumns.URL);
if ((title_column_index > -1) && (url_column_index > -1) && (row_count > 0)) {
webLinksCursor.moveToFirst();
while (webLinksCursor.isAfterLast() == false) {
if (webLinksCursor.getInt(Browser.HISTORY_PROJECTION_BOOKMARK_INDEX) != 1) {
if (!webLinksCursor.isNull(url_column_index)) {
Log.i("History", "Last page browsed " + webLinksCursor.getString(url_column_index));
try {
Domain = getDomainName(webLinksCursor.getString(url_column_index));
Log.i("Domain", "Last page browsed " + Domain);
return Domain;
} catch (URISyntaxException e) {
e.printStackTrace();
}
break;
}
}
webLinksCursor.moveToNext();
}
}
webLinksCursor.close();
return null;
}
public String getDomainName(String url) throws URISyntaxException {
URI uri = new URI(url);
String domain = uri.getHost();
return domain.startsWith("www.") ? domain.substring(4) : domain;
}}
Would you please tell me what should I do to fix this code?
Looking over the code I did not see anywhere you referenced anything from the Activity itself besides the application context so you can move the ListSpdFiles class to its own java file and pass it a context into the constructor when you make a new instance of it.
Put this class in a ListSpdFiles.java file so it is no longer an inner class.
public class ListSpdFiles extends AsyncTask<Void, Void, String[]> {
Context applicationContext;
public ListSpdFiles(Context context, String initiator, String path) {
this.initiator = initiator;
this.path = path;
applicationContext = context.getApplicationContext();
}
// The rest of your code still goes here. Replace other calls to
// getApplicationContext() with the new applicationContext field
}
You can now use this class anywhere a Context is available. You create a new instance by doing:
ListSpdFiles listSpdFilesTask = new ListSpdFiles(context, "someInitiator", "somePath");
listSpdFilesTask.execute();
In my app,i want to show twitter login everytime a user tried to share a post.
I dont want to save the twitter credentials in shared preference,but when i tried to share a post in second time,it is not showing the login fields.
can anyone figure out the problem?
public class AuthorizationActivity extends Activity {
private TwitterActivity app;
private WebView webView;
static String token, verifier;
Webservice web;
OAuthCredentialsResponse credentials;
private WebViewClient webViewClient = new WebViewClient() {
#Override
public void onLoadResource(WebView view, String url) {
final OAuthHmacSigner signer = new OAuthHmacSigner();
Uri uri = Uri.parse(url);
if (url.startsWith(Constants.OAUTH_CALLBACK_URL)) {
try {
if (url.indexOf("oauth_token=")!=-1) {
token = uri.getQueryParameter("oauth_token");
verifier = uri.getQueryParameter("oauth_verifier");
signer.clientSharedSecret = Constants.CONSUMER_SECRET;
OAuthGetAccessToken accessToken = new OAuthGetAccessToken(
Constants.ACCESS_URL);
accessToken.transport = new ApacheHttpTransport();
accessToken.temporaryToken = token;
accessToken.signer = signer;
accessToken.consumerKey = Constants.CONSUMER_KEY;
accessToken.verifier = verifier;
credentials = accessToken.execute();
signer.tokenSharedSecret = credentials.tokenSecret;
+ credentials.tokenSecret);
;
if (null != token) {
webView.setVisibility(View.INVISIBLE);
finish();
} else if (url.indexOf("error=")!=-1) {
view.setVisibility(View.INVISIBLE);
}
}} catch (IOException e) {
e.printStackTrace();
}
}
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.authorization_view);
setUpViews();
String authURL = beginAuthorization();
webView.loadUrl(authURL);
}
private void setUpViews() {
webView = (WebView) findViewById(R.id.web_view);
webView.setWebViewClient(webViewClient);
}
public String beginAuthorization() {
try {
final OAuthHmacSigner signer = new OAuthHmacSigner();
OAuthAuthorizeTemporaryTokenUrl authorizeUrl;
signer.clientSharedSecret = Constants.CONSUMER_SECRET;
OAuthGetTemporaryToken temporaryToken = new OAuthGetTemporaryToken(Constants.REQUEST_URL);
temporaryToken.transport = new ApacheHttpTransport();
temporaryToken.signer = signer;
temporaryToken.consumerKey = Constants.CONSUMER_KEY;
temporaryToken.callback = Constants.OAUTH_CALLBACK_URL;
OAuthCredentialsResponse tempCredentials = temporaryToken.execute();
signer.tokenSharedSecret = tempCredentials.tokenSecret;
authorizeUrl = new OAuthAuthorizeTemporaryTokenUrl(Constants.AUTHORIZE_URL);
authorizeUrl.temporaryToken = tempCredentials.token;
String authorizationUrl = authorizeUrl.build();
System.out.println(authorizationUrl);
return authorizationUrl;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public void authorized() {
try {
AccessToken a = new AccessToken(credentials.token,credentials.tokenSecret);
Twitter twitter = new TwitterFactory().getInstance();
twitter.setOAuthConsumer(Constants.CONSUMER_KEY, Constants.CONSUMER_SECRET);
twitter.setOAuthAccessToken(a);
twitter.updateStatus("my first post");
Toast t = Toast.makeText(this,"Successfully Shared",Toast.LENGTH_LONG);
t.show();
} catch (TwitterException e) {
throw new RuntimeException("Unable to authorize user", e);
}
}
I think you missed one parameter in the URL
that's force_login = true , This will ask user to login even the session is not expired.
To read more go to this link RestAPI
Running 3.1 (Honeycomb) on Galaxy Tab 10.1
Regardless of several different methods, I have been unable to reset the basic auth username and password for a WebView. The only way I can reset these values is to restart the app. I have searched around and have yet to find a solution and even dug into the Android source.
This code is from an activity that is created every time I want to display a webpage that requires basic auth. Some parts shouldn't have any effect but were tried out of frustration. Even when I exit this activity (which is then destroyed) and relaunch it with an intent from my main activity, the basic auth information remains and onReceivedHttpAuthRequest in the WebViewClient is never executed again.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.base_simple_v01);
findViewById(R.id.lyt_bsv01_layout).setBackgroundColor(0xFF000000);
baseContainer = (ViewGroup) findViewById(R.id.lyt_bsv01_baseContainer);
statusProgressBar = (ProgressBar) findViewById(R.id.lyt_bsv01_statusProgress);
resultNotificationTextView = (TextView) findViewById(R.id.lyt_bsv01_resultNotification);
// -- Attempt to prevent and clear WebView cookies
CookieSyncManager.createInstance(this);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.removeAllCookie();
cookieManager.removeSessionCookie();
cookieManager.setAcceptCookie(false);
// -- Attempt to clear WebViewDatabase
WebViewDatabase.getInstance(this).clearHttpAuthUsernamePassword();
WebViewDatabase.getInstance(this).clearUsernamePassword();
WebViewDatabase.getInstance(this).clearFormData();
// -- Brute force attempt to clear WebViewDatabase - didn't work
//deleteDatabase("webview.db");
//deleteDatabase("webviewCache.db");
LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
networkWebView = (WebView)vi.inflate(R.layout.social_connect, baseContainer, false);
// -- Removes white flickering in Honeycomb WebView page loading.
networkWebView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
networkWebView.getSettings().setJavaScriptEnabled(true);
networkWebView.getSettings().setSavePassword(false);
networkWebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
networkWebView.clearSslPreferences();
networkWebView.setWebViewClient(mLocalDataRequester.endorseBackendAuthWebViewClient(
new BackendAuthWebViewClient() {
#Override
public void onReceivedHttpAuthRequest (WebView view, HttpAuthHandler handler, String host, String realm) {
Toast.makeText(getApplicationContext(), "AUTH REQUESTED", Toast.LENGTH_SHORT).show();
super.onReceivedHttpAuthRequest (view, handler, host, realm);
}
#Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
Toast.makeText(getApplicationContext(), "SSL ERROR", Toast.LENGTH_SHORT).show();
super.onReceivedSslError(view, handler, error);
}
#Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
statusProgressBar.setVisibility(View.VISIBLE);
networkWebView.setVisibility(View.INVISIBLE);
}
#Override
public void onPageFinished(WebView view, String url) {
statusProgressBar.setVisibility(View.INVISIBLE);
networkWebView.setVisibility(View.VISIBLE);
}
})
);
baseContainer.addView(networkWebView);
networkWebView.setVisibility(View.INVISIBLE);
networkWebView.setBackgroundColor(0x00000000);
clearWebView();
}
private void clearWebView() {
networkWebView.loadData("", "text/html", "utf-8");
//networkWebView.clearView();
networkWebView.clearCache(false);
networkWebView.clearCache(true);
networkWebView.clearFormData();
networkWebView.clearHistory();
networkWebView.clearCache(true);
networkWebView.clearMatches();
networkWebView.freeMemory();
}
#Override
public void onResume() {
super.onResume();
networkWebView.loadUrl(mBackendNetworkConnectUrl);
WebViewDatabase.getInstance(this).clearHttpAuthUsernamePassword();
}
#Override
public void onDestroy() {
super.onDestroy();
Toast.makeText(getApplicationContext(), "Destruction", Toast.LENGTH_SHORT).show();
networkWebView.destroy();
}
This is a WebViewClient subclass that is initialized with the basic auth credentials. I have verified that the username and password change when an authentication should occur.
public class BackendAuthWebViewClient extends WebViewClient {
private AuthenticateData mAuthenticateData = null;
public BackendAuthWebViewClient() {
}
public BackendAuthWebViewClient(AuthenticateData authenticateData) {
this.mAuthenticateData = authenticateData;
}
#Override
public void onReceivedHttpAuthRequest (WebView view, HttpAuthHandler handler, String host, String realm){
handler.proceed(mAuthenticateData.mUserId, mAuthenticateData.mUserPassword);
}
#Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed();
}
public void setAuthenticatedData(AuthenticateData authenticateData) {
this.mAuthenticateData = authenticateData;
}
}
I have tried the following to no avail:
Android WebView - reset HTTP session
Clearing user's Facebook session in Webview
Delete data in the browser
Make Android WebView not store cookies or passwords
Android WebView Cookie Problem
This is interesting but necessity of the brute force would be disappointing. Though I'll try it next.
EDIT: Didn't work.
Android Webview - Completely Clear the Cache
I am pretty sure its a bug in WebViewDatabase.getInstance(this).clearHttpAuthUsernamePassword();,
because
deleteDatabase("webview.db");
does the trick for me.
Issue 25507: WebViewDatabase.clearHttpAuthUsernamePassword() does not work
Although it doesn't address the original reset WebView basic auth issue, I'm using this as a workaround. Using this SO as a reference:
Android Webview POST
This solution uses a HttpClient request (preferably in another thread or AsyncTask to avoid ANR - application not responding) and then loading that response into a WebView. Since I need to interact with links on the loaded page, I need to use loadDataWithBaseURL.
For this answer, I license all code below under Apache License 2.0.
HttpClient code - best used in another thread or AsyncTask. Variables authenticateData, method, url, and nameValuePairs will need to be defined or removed.
public String send() {
try {
// -- Create client.
HttpParams httpParameters = new BasicHttpParams();
// Set the timeout in milliseconds until a connection is established.
int timeoutConnection = 10000;
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
// Set the default socket timeout (SO_TIMEOUT)
// in milliseconds which is the timeout for waiting for data.
int timeoutSocket = 10000;
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
DefaultHttpClient httpClient = new DefaultHttpClient(httpParameters);
HttpGet httpGet;
HttpPost httpPost;
HttpDelete httpDelete;
HttpResponse httpResponse;
String authHeader;
if( authenticateData != null ) {
// -- Set basic authentication in header.
String base64EncodedCredentials = Base64.encodeToString(
(authenticateData.username + ":" + authenticateData.password).getBytes("US-ASCII"), Base64.URL_SAFE|Base64.NO_WRAP);
authHeader = "Basic " + base64EncodedCredentials;
} else {
authHeader = null;
}
// -- Send to server.
if( method == GET ) {
httpGet = new HttpGet(url);
if( authHeader != null ) {
httpGet.setHeader("Authorization", authHeader);
}
httpResponse = httpClient.execute(httpGet);
}
else if( method == POST) {
httpPost = new HttpPost(url);
if( authHeader != null ) {
httpPost.setHeader("Authorization", authHeader);
}
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
httpResponse = httpClient.execute(httpPost);
}
else if( method == DELETE) {
httpDelete = new HttpDelete(url);
httpDelete.setHeader("Content-Length", "0");
if( authHeader != null ) {
httpDelete.setHeader("Authorization", authHeader);
}
httpResponse = httpClient.execute(httpDelete);
}
else {
return null;
}
// -- Method 1 for obtaining response.
/*
InputStream is = httpResponse.getEntity().getContent();
// -- Convert response.
Scanner scanner = new Scanner(is);
// -- TODO: specify charset
String response = scanner.useDelimiter("\\A").next();
*/
// -- Method 2 for obtaining response.
String response = new BasicResponseHandler().handleResponse(httpResponse);
return response;
}
catch(SocketTimeoutException exception) {
exception.printStackTrace();
}
catch(ConnectTimeoutException exception) {
exception.printStackTrace();
}
catch(NoHttpResponseException exception) {
exception.printStackTrace();
}
catch(UnknownHostException exception) {
exception.printStackTrace();
}
catch(ClientProtocolException exception) {
exception.printStackTrace();
}
catch(IOException exception) {
exception.printStackTrace();
}
return null;
}
WebView code - should be in the activity that contains the WebView.
WebView webView = new WebView(Activity.this);
webView.loadDataWithBaseURL(url, response, "text/html", "utf-8", null);
I suggest not actually calling setHttpAuthUsernamePassword() at all.
Rather, only use onReceivedHttpAuthRequest() to handle the auth challenge dynamically every time.
This, coupled with
WebViewDatabase.getInstance(getContext()).clearHttpAuthUsernamePassword();
WebViewDatabase.getInstance(getContext()).clearUsernamePassword();
WebViewDatabase.getInstance(getContext()).clearFormData();
calls on load to clear out legacy entries, and my problem with this went away.
It turned out, there are two potential issues here.
One being the WebViewDatabase.clearHttpAuthUsernamePassword() seems not working correctly on some devices/versions of Android, in a way that calling WebView.getHttpAuthUsernamePassword() still yields the stored password after clearing the database.
This one can be addressed by implementing these methods yourself.
The second issue is, that the auth data also seems to be stored in memory, which is basically a good thing, because the WebView has not to query the database for every subsequent HTTP request. However this cache seems to be shared between all WebViews and there is no obvious method to clear it. It turns out though, that WebViews created with privateBrowsing = true, share a different cache which also behaves slightly different: After the last private browsing WebView is being destroyed, this cache seems to be cleared out completely, and the next request will actually trigger onReceivedHttpAuthRequest.
Below a complete working example of those two workarounds. If you have to deal with multiple WebViews it might get more complex, because you need to make sure to destroy them all, before recreating them.
public class HttpAuthTestActivity extends Activity {
ViewGroup webViewContainer;
Button logoutButton;
Button reloadButton;
WebView webView;
AuthStoreInterface authStore;
public interface AuthStoreInterface {
public void clear();
public void setHttpAuthUsernamePassword(String host, String realm, String username, String password);
public Pair<String, String> getHttpAuthUsernamePassword(String host, String realm);
}
//if you want to make the auth store persistent, you have implement a persistent version of this interface
public class MemoryAuthStore implements AuthStoreInterface {
Map<Pair<String, String>, Pair<String, String>> credentials;
public MemoryAuthStore() {
credentials = new HashMap<Pair<String, String>, Pair<String, String>>();
}
public void clear() {
credentials.clear();
}
public void setHttpAuthUsernamePassword(String host, String realm, String username, String password) {
credentials.put(new Pair<String, String>(host, realm), new Pair<String, String>(username, password));
}
public Pair<String, String> getHttpAuthUsernamePassword(String host, String realm) {
return credentials.get(new Pair<String, String>(host, realm));
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
authStore = new MemoryAuthStore();
webViewContainer = (ViewGroup)findViewById(R.id.webview_container);
logoutButton = (Button)findViewById(R.id.logout_button);
reloadButton = (Button)findViewById(R.id.reload_button);
createWebView();
logoutButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
authStore.clear();
destroyWebView();
createWebView();
}
});
reloadButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
webView.reload();
}
});
}
#Override
protected void onDestroy() {
webView.destroy();
super.onDestroy();
}
private void destroyWebView() {
webView.destroy();
webViewContainer.removeView(webView);
}
private void createWebView() {
//this is the important line: if you use this ctor with privateBrowsing: true, the internal auth cache will
//acutally be deleted in WebView.destroy, if there is no other privateBrowsing enabled WebView left only
webView = new WebView(this, null, android.R.attr.webViewStyle, true);
webView.setWebViewClient(new WebViewClient() {
#Override
public void onReceivedHttpAuthRequest(final WebView view, final HttpAuthHandler handler, final String host, final String realm) {
Pair<String, String> credentials = authStore.getHttpAuthUsernamePassword(host, realm);
if (credentials != null && handler.useHttpAuthUsernamePassword()) {
handler.proceed(credentials.first, credentials.second);
} else {
LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View form = inflater.inflate(R.layout.http_auth_request, null);
new AlertDialog.Builder(HttpAuthTestActivity.this).setTitle(String.format("HttpAuthRequest (realm: %s, host %s)", realm, host))
.setView(form).setPositiveButton(android.R.string.ok, new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
EditText usernameEdt = (EditText) form.findViewById(R.id.username);
EditText passwordEdt = (EditText) form.findViewById(R.id.password);
String u = usernameEdt.getText().toString();
String p = passwordEdt.getText().toString();
authStore.setHttpAuthUsernamePassword(host, realm, u, p);
handler.proceed(u, p);
}
}).setCancelable(true).setNegativeButton(android.R.string.cancel, new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
}).setOnCancelListener(new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
handler.cancel();
}
}).create().show();
}
}
});
webView.loadUrl("http://httpbin.org/basic-auth/test/test");
webViewContainer.addView(webView);
}
}
I also had this issue. And found the solution, hope this can help you.
First of all, the method onReceivedHttpAuthRequest() is only called one time in an application except use cookies.
I had write the method:
public void syncCookie(Context context, String url) {
HttpClient httpClient = HttpClientContext.getInstance();
Cookie[] cookies = httpClient.getState().getCookies();
Cookie sessionCookie = null;
if (cookies.length > 0) {
sessionCookie = cookies[cookies.length - 1];
}
CookieManager cookieManager = CookieManager.getInstance();
if (sessionCookie != null) {
String cookieString = sessionCookie.getName() + "="
+ sessionCookie.getValue() + ";domain="
+ sessionCookie.getDomain();
CookieSyncManager cookieSyncManager = CookieSyncManager.createInstance(context);
cookieSyncManager.startSync();
cookieManager.setCookie(url, cookieString);
CookieSyncManager.getInstance().sync();
}
}
use like this:
WebView webView = ...;
webView.getSettings().setJavaScriptEnabled(true);
syncCookie(this,url);
webView.loadUri(url);
webView.setWebViewClient();
I used multi-process to solve this problem.
As WebView in your Activity/Fragment need to handle Http Basic Authentication, onRecievedHttpAuthRequest() will be trigger. Creating a dialog for user to input login info.
onRecievedHttpAuthRequest(final WebView view, final HttpAuthHandler handler, final String host, String realm){
final Dialog dialog = new Dialog(context);
dialog.setContentView(R.layout.dialog_layout);
dialog.findViewById(R.id.confirmBtn).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
final String account = ((EditText)dialog.findViewById(R.id.accountET)).getText().toString();
final String pwd = ((EditText)dialog.findViewById(R.id.pwdET)).getText().toString();
serviceIntent = new Intent(context, SSOAuthService.class);
serviceIntent.putExtra("url", authUrl);
serviceIntent.putExtra("account", account);
serviceIntent.putExtra("pwd", pwd);
context.startService(serviceIntent);
dialog.dismiss();
}
});
dialog.show();
}
Launch a service contains a webview to handle http basic auth, and pass account and pwd get from the dialog above.
public class AuthService extends Service {
private String account;
private String pwd;
private String url;
private String webView;
private boolean isProcess = false;
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
url = (String) intent.getExtras().get("url");
account = (String) intent.getExtras().get("account");
pwd = (String) intent.getExtras().get("pwd");
webView = new WebView(this);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
//todo Do whatever u want to do.
closeServiceAndProcess();
}
#Override
public void onReceivedHttpAuthRequest(final WebView view, final HttpAuthHandler handler, final String host, String realm) {
if (!isProcess) {
isProcess = true;
handler.proceed(account, pwd);
} else {
isProcess = false;
closeServiceAndProcess();
}
}
});
webView.loadUrl(url);
return Service.START_REDELIVER_INTENT;
}
private void closeServiceAndProcess() {
stopSelf();
android.os.Process.killProcess(android.os.Process.myPid());
}
}
As complete http basic authentication in the AuthService, kill the process which the AuthService is lived. And http basic authentication could be reset.
I have another solution that seems to work fairly well. Basically you load the URL using WebView.loadUrl(url, additionalHeaders) and pass a blank Authorization header in. This seems to reset the webview properly. The only issue is that you'll the onReceivedHttpAuthRequest will get called in a loop if you don't reload the original URL. So once you get the onReceivedHttpAuthRequest you will need to collect the username/password from the user, and then reload the original URL without passing a blank Authorization header.
Essentially it works like this below (specific code is untested).
MyLoginActivity extends AppCompatActivity {
private boolean clearAuth = true;
private String mUsername, mPassword, mHost, mRealm;
private static final String URL = "https://yourdomain.com";
public void onCreate(bundle ss) {
super.onCreate(ss);
webview = findViewById(R.id.webview);
webview.setWebViewClient(new WebViewClient() {
#Override
public void onReceivedHttpAuthRequest(View webview, HttpAuthHandler authHandler, String host, String realm) {
mAuthHandler = authHandler;
mHost = host;
mRealm = realm;
if (mUsername != null && mPassword != null && authHandler.useHttpAuthUsernamePassword()) {
proceed(mUsername, mPassword);
} else {
showLoginDialog();
}
});
}
public void onDestroy() {
super.onDestroy();
//ensure an auth handler that was displayed but never finished is cancelled
//if you don't do this the app will start hanging and acting up after pressing back when a dialog was visible
if (mAuthHandler != null) {
mAuthHandler.cancel();
}
mAuthHandler = null;
}
public void onCredentialsProvided(String username, String password) {
mUsername = username;
mPassword = password;
if (clearAuth) {
//reload the original URL but this time without adding the blank
//auth header
clearAuth = false;
loadUrl(URL);
} else {
proceed(username, password);
}
}
private void loadUrl(String url) {
if (clearAuth) {
Map<String, String> clearAuthMap = new HashMap();
map.put("Authorization", "");
webView.loadUrl(url, clearAuthMap);
} else {
webView.loadUrl(url);
}
}
private void proceed(String username, String password) {
mAuthHandler.proceed(username, password);
mAuthHandler = null;
}
private void showLoginDialog() {
//show your login dialog here and when user presses submit, call
//activity.onCredentialsProvided(username, password);
}
}