I have to retrieve a user's location in a WebView
. I do this with the following Javascript:
function getLocation() {
navigator.geolocation.getCurrentPosition(displayLocation, handleError);
}
But the permission request popup never opens.
I've set these settings:
ws.setJavaScriptEnabled(true);
ws.setGeolocationEnabled(true);
ws.setJavaScript开发者_开发问答CanOpenWindowsAutomatically(true);
What is the correct way to access a user's location from within a WebView
?
- JavaScript must be enabled in the
WebView
, usingWebSettings.setJavaScriptEnabled(true);
- The app needs permission
ACCESS_FINE_LOCATION
The
WebView
must use a customWebChromeClient
which implementsWebChromeClient.onGeolocationPermissionsShowPrompt()
. This method is called by theWebView
to obtain permission to disclose the user's location to JavaScript. (In the case of the browser, we show a prompt to the user.) The default implementation does nothing, so permission is never obtained and the location is never passed to JavaScript. A simple implementation which always grants permission is ...webView.setWebChromeClient(new WebChromeClient() { public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) { callback.invoke(origin, true, false); } });
Geolocation uses databases to persist cached positions and permissions between sessions. The location of the database is set using WebSettings.setGeolocationDatabasePath(...)
. If the location of the database is not set, the persistent storage will not be available, but Geolocation will continue to function correctly otherwise. To set the location of the databases, use ...
webView.getSettings().setGeolocationDatabasePath( context.getFilesDir().getPath() );
Dialog to accept or decline user location is design by programmer :D. As Chris Cashwell said, you just use a callback like this:
webview.setWebChromeClient(new WebChromeClient(){
public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
// callback.invoke(String origin, boolean allow, boolean remember);
callback.invoke(origin, true, false);
}
});
In some cases, HTML5 requires the use of storage, therefore you must enable some properties so that webview has full access to run normal.
// HTML5 API flags
webView.getSettings().setAppCacheEnabled(true);
webView.getSettings().setDatabaseEnabled(true);
webView.getSettings().setDomStorageEnabled(true);
Sharing my Working activity class, this is a complete solution which can demonstrate
- Showing loading dialog while the web page is loading
- Ask for permission in marshmallow and above
- Handle webpage error
- Check for the internet connection and open setting page
- Handling Geolocation permission with and without dialog
Hope, it saves someone's time
/**
* Created by Hitesh.Sahu on 3/24/2017.
*/
public class WebViewActivity extends AppCompatActivity {
final private int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124;
private int webViewPreviousState;
private final int PAGE_STARTED = 0x1;
private final int PAGE_REDIRECTED = 0x2;
private CoordinatorLayout rootView;
private WebView webView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_webview);
webView = (WebView) findViewById(R.id.webView);
rootView = (CoordinatorLayout) findViewById(R.id.root_view);
if (Build.VERSION.SDK_INT >= 23) {
// Marshmallow+ Permission APIs
askRuntimePermission();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (0 != (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE)) {
WebView.setWebContentsDebuggingEnabled(true);
}
}
webView.setInitialScale(1);
webView.getSettings().setLoadWithOverviewMode(true);
webView.getSettings().setUseWideViewPort(true);
webView.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);
webView.setScrollbarFadingEnabled(false);
webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
webView.getSettings().setBuiltInZoomControls(true);
webView.setWebViewClient(new GeoWebViewClient());
// Below required for geolocation
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setGeolocationEnabled(true);
webView.setWebChromeClient(new GeoWebChromeClient());
webView.getSettings().setAppCacheEnabled(true);
webView.getSettings().setDatabaseEnabled(true);
webView.getSettings().setDomStorageEnabled(true);
webView.getSettings().setGeolocationDatabasePath(getFilesDir().getPath());
webView.loadUrl("file:///android_asset/index.html");
}
/**
* WebChromeClient subclass handles UI-related calls
* Note: think chrome as in decoration, not the Chrome browser
*/
public class GeoWebChromeClient extends android.webkit.WebChromeClient {
@Override
public void onGeolocationPermissionsShowPrompt(final String origin,
final GeolocationPermissions.Callback callback) {
// Always grant permission since the app itself requires location
// permission and the user has therefore already granted it
callback.invoke(origin, true, false);
// final boolean remember = false;
// AlertDialog.Builder builder = new AlertDialog.Builder(WebViewActivity.this);
// builder.setTitle("Locations");
// builder.setMessage("Would like to use your Current Location ")
// .setCancelable(true).setPositiveButton("Allow", new DialogInterface.OnClickListener() {
// public void onClick(DialogInterface dialog, int id) {
// // origin, allow, remember
// callback.invoke(origin, true, remember);
// }
// }).setNegativeButton("Don't Allow", new DialogInterface.OnClickListener() {
// public void onClick(DialogInterface dialog, int id) {
// // origin, allow, remember
// callback.invoke(origin, false, remember);
// }
// });
// AlertDialog alert = builder.create();
// alert.show();
}
}
/**
* WebViewClient subclass loads all hyperlinks in the existing WebView
*/
public class GeoWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// When user clicks a hyperlink, load in the existing WebView
view.loadUrl(url);
return true;
}
Dialog loadingDialog = new Dialog(WebViewActivity.this);
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
webViewPreviousState = PAGE_STARTED;
if (loadingDialog == null || !loadingDialog.isShowing())
loadingDialog = ProgressDialog.show(WebViewActivity.this, "",
"Loading Please Wait", true, true,
new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
// do something
}
});
loadingDialog.setCancelable(false);
}
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void onReceivedError(WebView view, WebResourceRequest request,
WebResourceError error) {
if (isConnected()) {
final Snackbar snackBar = Snackbar.make(rootView, "onReceivedError : " + error.getDescription(), Snackbar.LENGTH_INDEFINITE);
snackBar.setAction("Reload", new View.OnClickListener() {
@Override
public void onClick(View view) {
webView.loadUrl("javascript:window.location.reload( true )");
}
});
snackBar.show();
} else {
final Snackbar snackBar = Snackbar.make(rootView, "No Internet Connection ", Snackbar.LENGTH_INDEFINITE);
snackBar.setAction("Enable Data", new View.OnClickListener() {
@Override
public void onClick(View view) {
startActivityForResult(new Intent(Settings.ACTION_WIRELESS_SETTINGS), 0);
webView.loadUrl("javascript:window.location.reload( true )");
snackBar.dismiss();
}
});
snackBar.show();
}
super.onReceivedError(view, request, error);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void onReceivedHttpError(WebView view,
WebResourceRequest request, WebResourceResponse errorResponse) {
if (isConnected()) {
final Snackbar snackBar = Snackbar.make(rootView, "HttpError : " + errorResponse.getReasonPhrase(), Snackbar.LENGTH_INDEFINITE);
snackBar.setAction("Reload", new View.OnClickListener() {
@Override
public void onClick(View view) {
webView.loadUrl("javascript:window.location.reload( true )");
}
});
snackBar.show();
} else {
final Snackbar snackBar = Snackbar.make(rootView, "No Internet Connection ", Snackbar.LENGTH_INDEFINITE);
snackBar.setAction("Enable Data", new View.OnClickListener() {
@Override
public void onClick(View view) {
startActivityForResult(new Intent(Settings.ACTION_WIRELESS_SETTINGS), 0);
webView.loadUrl("javascript:window.location.reload( true )");
snackBar.dismiss();
}
});
snackBar.show();
}
super.onReceivedHttpError(view, request, errorResponse);
}
@Override
public void onPageFinished(WebView view, String url) {
if (webViewPreviousState == PAGE_STARTED) {
if (null != loadingDialog) {
loadingDialog.dismiss();
loadingDialog = null;
}
}
}
}
/**
* Check if there is any connectivity
*
* @return is Device Connected
*/
public boolean isConnected() {
ConnectivityManager cm = (ConnectivityManager)
this.getSystemService(Context.CONNECTIVITY_SERVICE);
if (null != cm) {
NetworkInfo info = cm.getActiveNetworkInfo();
return (info != null && info.isConnected());
}
return false;
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS: {
Map<String, Integer> perms = new HashMap<String, Integer>();
// Initial
perms.put(Manifest.permission.ACCESS_FINE_LOCATION, PackageManager.PERMISSION_GRANTED);
// Fill with results
for (int i = 0; i < permissions.length; i++)
perms.put(permissions[i], grantResults[i]);
// Check for ACCESS_FINE_LOCATION
if (perms.get(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
) {
// All Permissions Granted
// Permission Denied
Toast.makeText(WebViewActivity.this, "All Permission GRANTED !! Thank You :)", Toast.LENGTH_SHORT)
.show();
} else {
// Permission Denied
Toast.makeText(WebViewActivity.this, "One or More Permissions are DENIED Exiting App :(", Toast.LENGTH_SHORT)
.show();
finish();
}
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
@TargetApi(Build.VERSION_CODES.M)
private void askRuntimePermission() {
List<String> permissionsNeeded = new ArrayList<String>();
final List<String> permissionsList = new ArrayList<String>();
if (!addPermission(permissionsList, Manifest.permission.ACCESS_FINE_LOCATION))
permissionsNeeded.add("Show Location");
if (permissionsList.size() > 0) {
if (permissionsNeeded.size() > 0) {
// Need Rationale
String message = "App need access to " + permissionsNeeded.get(0);
for (int i = 1; i < permissionsNeeded.size(); i++)
message = message + ", " + permissionsNeeded.get(i);
showMessageOKCancel(message,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
}
});
return;
}
requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
return;
}
Toast.makeText(WebViewActivity.this, "No new Permission Required- Launching App .You are Awesome!!", Toast.LENGTH_SHORT)
.show();
}
private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(WebViewActivity.this)
.setMessage(message)
.setPositiveButton("OK", okListener)
.setNegativeButton("Cancel", null)
.create()
.show();
}
@TargetApi(Build.VERSION_CODES.M)
private boolean addPermission(List<String> permissionsList, String permission) {
if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
permissionsList.add(permission);
// Check for Rationale Option
if (!shouldShowRequestPermissionRationale(permission))
return false;
}
return true;
}
}
Are you declaring that permission in your manifest?
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
You may also need to declare other location permissions, like these:
<uses-permission android:name="android.permission.ACCESS_GPS" />
<uses-permission android:name="android.permission.ACCESS_ASSISTED_GPS" />
<uses-permission android:name="android.permission.ACCESS_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
I have recently come across this type of situation and have done below steps to achieve this:
Step 1:
Add permissions in your AndroidManifest.xml
file
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
Step 2: Create an activity that would contain a WebView
and ProgressBar
(in my case)
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="0dp"
android:minHeight="4dp"
android:padding="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<WebView
android:id="@+id/webView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/progressBar" />
</androidx.constraintlayout.widget.ConstraintLayout>
Step 3: In your activity class, add below code and make changes according to your need
class WebActivity : AppCompatActivity() {
var pageUrl: String = "https://couponia.co/"
var mGeoLocationRequestOrigin: String? = null
var mGeoLocationCallback: GeolocationPermissions.Callback? = null
val MAX_PROGRESS = 100
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_web)
initWebView()
setWebClient()
loadUrl(pageUrl)
}
@SuppressLint("SetJavaScriptEnabled")
private fun initWebView() {
webView.settings.javaScriptEnabled = true
webView.settings.loadWithOverviewMode = true
webView.settings.useWideViewPort = true
webView.settings.domStorageEnabled = true
webView.settings.databaseEnabled = true
webView.settings.setAppCacheEnabled(true)
webView.webViewClient = object : WebViewClient() {
override
fun onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?) {
handler?.proceed()
}
}
}
private fun setWebClient() {
webView.webChromeClient = object : WebChromeClient() {
override fun onGeolocationPermissionsShowPrompt(
origin: String?,
callback: GeolocationPermissions.Callback?
) {
if (ContextCompat.checkSelfPermission(
this@WebActivity,
Manifest.permission.ACCESS_FINE_LOCATION
)
!= PackageManager.PERMISSION_GRANTED
) {
if (ActivityCompat.shouldShowRequestPermissionRationale(
this@WebActivity,
Manifest.permission.ACCESS_FINE_LOCATION
)
) {
AlertDialog.Builder(this@WebActivity)
.setMessage("Please turn ON the GPS to make app work smoothly")
.setNeutralButton(
android.R.string.ok,
DialogInterface.OnClickListener { dialogInterface, i ->
mGeoLocationCallback = callback
mGeoLocationRequestOrigin = origin
ActivityCompat.requestPermissions(
this@WebActivity,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 1001
)
})
.show()
} else {
//no explanation need we can request the locatio
mGeoLocationCallback = callback
mGeoLocationRequestOrigin = origin
ActivityCompat.requestPermissions(
this@WebActivity,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 1001
)
}
} else {
//tell the webview that permission has granted
callback!!.invoke(origin, true, true)
}
}
override fun onProgressChanged(view: WebView?, newProgress: Int) {
super.onProgressChanged(view, newProgress)
progressBar.progress = newProgress
if (newProgress < MAX_PROGRESS && progressBar.visibility == ProgressBar.GONE) {
progressBar.visibility = ProgressBar.VISIBLE
}
if (newProgress == MAX_PROGRESS) {
progressBar.visibility = ProgressBar.GONE
}
}
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
// Check if the key event was the Back button and if there's history
if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {
webView.goBack()
return true
}
// If it wasn't the Back key or there's no web page history, exit the activity)
return super.onKeyDown(keyCode, event)
}
private fun loadUrl(pageUrl: String) {
webView.loadUrl(pageUrl)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
1001 -> {
//if permission is cancel result array would be empty
if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//permission was granted
if (mGeoLocationCallback != null) {
mGeoLocationCallback!!.invoke(mGeoLocationRequestOrigin, true, true)
}
} else {
//permission denied
if (mGeoLocationCallback != null) {
mGeoLocationCallback!!.invoke(mGeoLocationRequestOrigin, false, false)
}
}
}
}
}
}
This is an example of showing alert dialog to promote for user permission to use his/her location:
final Context context = this;
@Override
public void onGeolocationPermissionsShowPrompt(final String origin, final GeolocationPermissions.Callback callback) {
Log.i(TAG, "onGeolocationPermissionsShowPrompt()");
final boolean remember = false;
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("Locations");
builder.setMessage("Would like to use your Current Location ")
.setCancelable(true).setPositiveButton("Allow", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// origin, allow, remember
callback.invoke(origin, true, remember);
}
}).setNegativeButton("Don't Allow", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// origin, allow, remember
callback.invoke(origin, false, remember);
}
});
AlertDialog alert = builder.create();
alert.show();
}
Posting as a new answer for updated Android with everything in one post, because you no longer need to use setWebChromeClient
.
With android 6+ you just to ask for the GPS permissions at runtime using ActivityCompat.requestPermissions(context, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 123);
.
you need to dynamically request for Permission when location is requested from Webview
Make sure You you have added ACCESS_FINE_LOCATION to Manifest
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Define Your callback and origin at top level Class ,to be able to assign them to the callbacks and origin provided by ChromeClient
public class MainActivity extends AppCompatActivity {
private android.webkit.WebView myWebView;
String mGeoLocationRequestOrigin = null;
GeolocationPermissions.Callback mGeoLocationCallback = null;
...................................................
Handle geoLocation Request and Assign value to callbacks to be able to use them once permission is granted
myWebView.setWebChromeClient(new WebChromeClient(){
@Override
public void onGeolocationPermissionsShowPrompt(final String origin,
final GeolocationPermissions.Callback callback) {
int permissionCheckFineLocation = ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION);
if (permissionCheckFineLocation!= PackageManager.PERMISSION_GRANTED) {
mGeoLocationCallback=callback;
mGeoLocationRequestOrigin=origin;
//requesting permission
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 123);
}
else {// permission and the user has therefore already granted it
callback.invoke(origin, true, false);
}
}
});
once permission is received invoke calbacks using origin
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(grantResults[0]== PackageManager.PERMISSION_GRANTED){
//you have the permission now.
if(requestCode==123) {
mGeoLocationCallback.invoke(mGeoLocationRequestOrigin, true, false);
}
}
精彩评论