Commit d591a9cd authored by Jeffrey I. Schiller's avatar Jeffrey I. Schiller

Add the ability of the WebViewer component to put up a dialog box

requesting  the end-users  permission  to use  her  location when  the
Javascript location API is used in the component.

We add two new properties:

  UsesLocation =>  Declares that this WebViewer uses  the location API
  and arranges  for the packaged  app to have the  appropriate Android
  permissions.

  PromptForpermission =>  Causes the Webviewer to  prompt the end-user
  for permission to use their  location. Defaults to "True." If set to
  False,  the  end-user is  not  prompted  (but  presumably has  given
  implicit permission when the app was installed with Android location
  permissions).

Change-Id: Ie6db11f6fba6eb7b016c20f088b37fc2490d9559
parent d9d6f13d
......@@ -7,7 +7,9 @@ package com.google.appinventor.client.editor;
import com.google.appinventor.client.Ode;
import com.google.appinventor.client.explorer.project.Project;
import com.google.appinventor.client.output.OdeLog;
import com.google.appinventor.client.settings.Settings;
import com.google.appinventor.shared.settings.SettingsConstants;
import com.google.appinventor.client.settings.project.ProjectSettings;
import com.google.appinventor.shared.rpc.project.ProjectRootNode;
import com.google.gwt.event.logical.shared.AttachEvent;
......@@ -48,6 +50,8 @@ public abstract class ProjectEditor extends Composite {
private final Map<String, FileEditor> openFileEditors;
protected final List<String> tabNames; // tab names in the same order as the TabBar.
private final HashMap<String,String> locationHashMap = new HashMap<String,String>();
// UI elements
private final TabBar tabBar;
private final ScrollPanel tabBarScrollPanel;
......@@ -279,6 +283,51 @@ public abstract class ProjectEditor extends Composite {
}
}
/**
* Keep track of components that require the
* "android.permission.ACCESS_FINE_LOCATION" (and related
* permissions). This code is in particular for use of the WebViewer
* component. The WebViewer exports the Javascript location
* API. However it cannot be used by an app with location
* permissions. Each WebViewer has a "UsesLocation" property which
* is only available from the designer. Each WebViewer then
* registers its value here. Each time this hashtable is updated we
* recompute whether or not location permission is needed based on a
* logical OR of all of the WebViwer components registered. Note:
* Even if no WebViewer component requires location permisson, other
* components, such as the LocationSensor may require it. That is
* handled via the @UsesPermissions mechanism and is independent of
* this code.
*
* @param componentName The name of the component registering location permission
* @param newVlue either "True" or "False" indicating whether permission is need.
*/
public final void recordLocationSetting(String componentName, String newValue) {
OdeLog.log("ProjectEditor: recordLocationSetting(" + componentName + "," + newValue + ")");
locationHashMap.put(componentName, newValue);
recomputeLocationPermission();
}
private final void recomputeLocationPermission() {
String usesLocation = "False";
for (String c : locationHashMap.values()) {
OdeLog.log("ProjectEditor:recomputeLocationPermission: " + c);
if (c.equals("True")) {
usesLocation = "True";
break;
}
}
changeProjectSettingsProperty(SettingsConstants.PROJECT_YOUNG_ANDROID_SETTINGS, SettingsConstants.YOUNG_ANDROID_SETTINGS_USES_LOCATION,
usesLocation);
}
public void clearLocation(String componentName) {
OdeLog.log("ProjectEditor:clearLocation: clearing " + componentName);
locationHashMap.remove(componentName);
recomputeLocationPermission();
}
/**
* Notification that the file with the given file ID has been saved.
*
......
......@@ -206,7 +206,7 @@ public abstract class MockComponent extends Composite implements PropertyChangeL
protected boolean expanded;
// Properties of the component
// Expose these to individual component subclasses, which might need to
// Expose these to individual component subclasses, which might need to
// check properties fpr UI manipulation. One example is MockHorizontalArrangement
protected final EditableProperties properties;
......@@ -263,6 +263,7 @@ public abstract class MockComponent extends Composite implements PropertyChangeL
public void delete() {
if (!isForm()) {
if (Window.confirm(MESSAGES.reallyDeleteComponent())) {
MockComponent.this.editor.getProjectEditor().clearLocation(MockComponent.this.getName());
getForm().select();
// Pass true to indicate that the component is being permanently deleted.
getContainer().removeComponent(MockComponent.this, true);
......
......@@ -6,9 +6,15 @@
package com.google.appinventor.client.editor.simple.components;
import com.google.appinventor.client.editor.simple.SimpleEditor;
import com.google.appinventor.shared.settings.SettingsConstants;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.SimplePanel;
import java.util.ArrayList;
import com.google.appinventor.client.output.OdeLog;
/**
* Mock WebViewer component.
*
......@@ -21,6 +27,9 @@ public final class MockWebViewer extends MockVisibleComponent {
*/
public static final String TYPE = "WebViewer";
// Property names that we need to treat specially
private static final String PROPERTY_NAME_USESLOCATION = "UsesLocation";
// Large icon image for use in designer. Smaller version is in the palette.
private final Image largeImage = new Image(images.webviewerbig());
......@@ -72,5 +81,13 @@ public final class MockWebViewer extends MockVisibleComponent {
return heightHint;
}
@Override
public void onPropertyChange(String propertyName, String newValue) {
super.onPropertyChange(propertyName, newValue);
if (propertyName.equals(PROPERTY_NAME_USESLOCATION)) {
editor.getProjectEditor().recordLocationSetting(this.getName(), newValue);
}
}
}
......@@ -35,5 +35,8 @@ public final class YoungAndroidSettings extends Settings {
addProperty(new EditableProperty(this,
SettingsConstants.YOUNG_ANDROID_SETTINGS_VERSION_NAME, "1.0",
EditableProperty.TYPE_INVISIBLE));
addProperty(new EditableProperty(this,
SettingsConstants.YOUNG_ANDROID_SETTINGS_USES_LOCATION, "false",
EditableProperty.TYPE_INVISIBLE));
}
}
......@@ -281,7 +281,7 @@ public final class YoungAndroidFormUpgrader {
} else if (componentType.equals("Texting")) {
srcCompVersion = upgradeTextingProperties(componentProperties, srcCompVersion);
} else if (componentType.equals("Notifier")) {
srcCompVersion = upgradeNotifierProperties(componentProperties, srcCompVersion);
......@@ -506,7 +506,7 @@ public final class YoungAndroidFormUpgrader {
if (srcCompVersion < 7) {
// The callback parameters speed and heading were added to Flung.
srcCompVersion = 7;
}
}
return srcCompVersion;
}
......@@ -662,7 +662,7 @@ public final class YoungAndroidFormUpgrader {
// The Shape property was added.
// No properties need to be modified to upgrade to version 4.
srcCompVersion = 4;
}
}
if (srcCompVersion < 5) {
// The ImagePath property was renamed to Selection.
handlePropertyRename(componentProperties, "ImagePath", "Selection");
......@@ -913,7 +913,7 @@ public final class YoungAndroidFormUpgrader {
return srcCompVersion;
}
private static int upgradeTextBoxProperties(Map<String, JSONValue> componentProperties,
int srcCompVersion) {
if (srcCompVersion < 2) {
......@@ -955,10 +955,12 @@ public final class YoungAndroidFormUpgrader {
private static int upgradeWebViewerProperties(Map<String, JSONValue> componentProperties,
int srcCompVersion) {
if (srcCompVersion < 2) {
if (srcCompVersion < 3) {
// The CanGoForward and CanGoBack methods were added.
// No properties need to be modified to upgrade to version 2.
srcCompVersion = 2;
// UsesLocation property added.
// No properties need to be modified to upgrade to version 3.
srcCompVersion = 3;
}
return srcCompVersion;
}
......
......@@ -96,11 +96,11 @@ public final class FileImporterImpl implements FileImporter {
// so that it contains the correct entries for "main" and "name", which are dependent on
// the projectName and qualifiedFormName.
String content = YoungAndroidProjectService.getProjectPropertiesFileContents(
projectName, qualifiedFormName, null, null, null);
projectName, qualifiedFormName, null, null, null, null);
project.addTextFile(new TextFile(fileName, content));
isProjectArchive = true;
} else if (fileName.equals(FileExporter.REMIX_INFORMATION_FILE_PATH) ||
} else if (fileName.equals(FileExporter.REMIX_INFORMATION_FILE_PATH) ||
fileName.equals(StorageUtil.ANDROID_KEYSTORE_FILENAME)) {
// If the remix information file is present, we ignore it. In the past, a remix
// information file was saved in the zip when project source was downloaded and
......@@ -149,7 +149,7 @@ public final class FileImporterImpl implements FileImporter {
if (projectHistory != null) {
project.setProjectHistory(projectHistory);
}
String settings = YoungAndroidProjectService.getProjectSettings(null, null, null);
String settings = YoungAndroidProjectService.getProjectSettings(null, null, null, null);
long projectId = storageIo.createProject(userId, project, settings);
return new UserProject(projectId, storageIo.getProjectName(userId, projectId),
storageIo.getProjectType(userId, projectId),
......
......@@ -119,15 +119,17 @@ public final class YoungAndroidProjectService extends CommonProjectService {
/**
* Returns project settings that can be used when creating a new project.
*/
public static String getProjectSettings(String icon, String vCode, String vName) {
public static String getProjectSettings(String icon, String vCode, String vName, String useslocation) {
icon = Strings.nullToEmpty(icon);
vCode = Strings.nullToEmpty(vCode);
vName = Strings.nullToEmpty(vName);
useslocation = Strings.nullToEmpty(useslocation);
return "{\"" + SettingsConstants.PROJECT_YOUNG_ANDROID_SETTINGS + "\":{" +
"\"" + SettingsConstants.YOUNG_ANDROID_SETTINGS_ICON + "\":\"" +
icon + "\",\"" + SettingsConstants.YOUNG_ANDROID_SETTINGS_VERSION_CODE +
"\":\"" + vCode +"\",\"" + SettingsConstants.YOUNG_ANDROID_SETTINGS_VERSION_NAME +
"\":\"" + vName + "\"}}";
"\":\"" + vName + "\",\"" + SettingsConstants.YOUNG_ANDROID_SETTINGS_USES_LOCATION +
"\":\"" + useslocation + "\"}}";
}
/**
......@@ -141,7 +143,7 @@ public final class YoungAndroidProjectService extends CommonProjectService {
* @param vname the version name
*/
public static String getProjectPropertiesFileContents(String projectName, String qualifiedName,
String icon, String vcode, String vname) {
String icon, String vcode, String vname, String useslocation) {
String contents = "main=" + qualifiedName + "\n" +
"name=" + projectName + '\n' +
"assets=../" + ASSETS_FOLDER + "\n" +
......@@ -156,6 +158,9 @@ public final class YoungAndroidProjectService extends CommonProjectService {
if (vname != null && !vname.isEmpty()) {
contents += "versionname=" + vname + "\n";
}
if (useslocation != null && !useslocation.isEmpty()) {
contents += "useslocation=" + useslocation + "\n";
}
return contents;
}
......@@ -225,6 +230,9 @@ public final class YoungAndroidProjectService extends CommonProjectService {
String newVName = Strings.nullToEmpty(settings.getSetting(
SettingsConstants.PROJECT_YOUNG_ANDROID_SETTINGS,
SettingsConstants.YOUNG_ANDROID_SETTINGS_VERSION_NAME));
String newUsesLocation = Strings.nullToEmpty(settings.getSetting(
SettingsConstants.PROJECT_YOUNG_ANDROID_SETTINGS,
SettingsConstants.YOUNG_ANDROID_SETTINGS_USES_LOCATION));
// Extract the old icon from the project.properties file from storageIo.
String projectProperties = storageIo.downloadFile(userId, projectId,
......@@ -240,12 +248,14 @@ public final class YoungAndroidProjectService extends CommonProjectService {
String oldIcon = Strings.nullToEmpty(properties.getProperty("icon"));
String oldVCode = Strings.nullToEmpty(properties.getProperty("versioncode"));
String oldVName = Strings.nullToEmpty(properties.getProperty("versionname"));
String oldUsesLocation = Strings.nullToEmpty(properties.getProperty("useslocation"));
if (!newIcon.equals(oldIcon) || !newVCode.equals(oldVCode) || !newVName.equals(oldVName)) {
if (!newIcon.equals(oldIcon) || !newVCode.equals(oldVCode) || !newVName.equals(oldVName)
|| !newUsesLocation.equals(oldUsesLocation)) {
// Recreate the project.properties and upload it to storageIo.
String projectName = properties.getProperty("name");
String qualifiedName = properties.getProperty("main");
String newContent = getProjectPropertiesFileContents(projectName, qualifiedName, newIcon, newVCode, newVName);
String newContent = getProjectPropertiesFileContents(projectName, qualifiedName, newIcon, newVCode, newVName, newUsesLocation);
storageIo.uploadFile(projectId, PROJECT_PROPERTIES_FILE_NAME, userId,
newContent, StorageUtil.DEFAULT_CHARSET);
}
......@@ -264,7 +274,7 @@ public final class YoungAndroidProjectService extends CommonProjectService {
String propertiesFileName = PROJECT_PROPERTIES_FILE_NAME;
String propertiesFileContents = getProjectPropertiesFileContents(projectName,
qualifiedFormName, null, null, null);
qualifiedFormName, null, null, null, null);
String formFileName = getFormPropertiesFileName(qualifiedFormName);
String formFileContents = getInitialFormPropertiesFileContents(qualifiedFormName);
......@@ -280,7 +290,7 @@ public final class YoungAndroidProjectService extends CommonProjectService {
project.addTextFile(new TextFile(codeblocksFileName, codeblocksFileContents));
// Create new project
return storageIo.createProject(userId, project, getProjectSettings("", "1", "1.0"));
return storageIo.createProject(userId, project, getProjectSettings("", "1", "1.0", "false"));
}
@Override
......@@ -298,6 +308,9 @@ public final class YoungAndroidProjectService extends CommonProjectService {
String vname = oldSettings.getSetting(
SettingsConstants.PROJECT_YOUNG_ANDROID_SETTINGS,
SettingsConstants.YOUNG_ANDROID_SETTINGS_VERSION_NAME);
String useslocation = oldSettings.getSetting(
SettingsConstants.PROJECT_YOUNG_ANDROID_SETTINGS,
SettingsConstants.YOUNG_ANDROID_SETTINGS_USES_LOCATION);
Project newProject = new Project(newName);
newProject.setProjectType(YoungAndroidProjectNode.YOUNG_ANDROID_PROJECT_TYPE);
......@@ -316,7 +329,7 @@ public final class YoungAndroidProjectService extends CommonProjectService {
// name and qualified name.
String qualifiedFormName = StringUtils.getQualifiedFormName(
storageIo.getUser(userId).getUserEmail(), newName);
newContents = getProjectPropertiesFileContents(newName, qualifiedFormName, icon, vcode, vname);
newContents = getProjectPropertiesFileContents(newName, qualifiedFormName, icon, vcode, vname, useslocation);
} else {
// This is some file other than the project properties file.
// oldSourceFileName may contain the old project name as a path segment, surrounded by /.
......@@ -339,7 +352,7 @@ public final class YoungAndroidProjectService extends CommonProjectService {
}
// Create the new project and return the new project's id.
return storageIo.createProject(userId, newProject, getProjectSettings(icon, vcode, vname));
return storageIo.createProject(userId, newProject, getProjectSettings(icon, vcode, vname, useslocation));
}
@Override
......
......@@ -32,4 +32,5 @@ public class SettingsConstants {
public static final String YOUNG_ANDROID_SETTINGS_SHOW_HIDDEN_COMPONENTS = "ShowHiddenComponents";
public static final String YOUNG_ANDROID_SETTINGS_VERSION_CODE = "VersionCode";
public static final String YOUNG_ANDROID_SETTINGS_VERSION_NAME = "VersionName";
public static final String YOUNG_ANDROID_SETTINGS_USES_LOCATION = "UsesLocation";
}
......@@ -325,7 +325,8 @@ public class ProjectServiceTest {
"source=../src\n" +
"build=../build\n" +
"versioncode=1\n" +
"versionname=1.0\n");
"versionname=1.0\n" +
"useslocation=false\n");
expectedYaFiles2.put("src/com/domain/noname/Project2/Screen1.scm",
YOUNG_ANDROID_PROJECT_SCM_SOURCE);
assertEquals(expectedYaFiles2, getTextFiles(USER_ID_ONE, yaProject2));
......@@ -465,7 +466,8 @@ public class ProjectServiceTest {
"{\"" + SettingsConstants.PROJECT_YOUNG_ANDROID_SETTINGS + "\":" +
"{\"" + SettingsConstants.YOUNG_ANDROID_SETTINGS_ICON + "\":\"\",\"" +
SettingsConstants.YOUNG_ANDROID_SETTINGS_VERSION_CODE + "\":\"1\",\"" +
SettingsConstants.YOUNG_ANDROID_SETTINGS_VERSION_NAME + "\":\"1.0\"}}",
SettingsConstants.YOUNG_ANDROID_SETTINGS_VERSION_NAME + "\":\"1.0\",\"" +
SettingsConstants.YOUNG_ANDROID_SETTINGS_USES_LOCATION + "\":\"false\"}}",
loadedSettings);
String storedSettings =
......
......@@ -981,6 +981,17 @@ public final class Compiler {
componentPermissions.put(name, permissionsForThisComponent);
}
}
if (project != null) { // Only do this if we have a project (testing doesn't provide one :-( ).
LOG.log(Level.INFO, "usesLocation = " + project.getUsesLocation());
if (project.getUsesLocation().equals("True")) { // Add location permissions if any WebViewer requests it
Set<String> locationPermissions = Sets.newHashSet(); // via a Property.
// See ProjectEditor.recordLocationSettings()
locationPermissions.add("android.permission.ACCESS_FINE_LOCATION");
locationPermissions.add("android.permission.ACCESS_COARSE_LOCATION");
locationPermissions.add("android.permission.ACCESS_MOCK_LOCATION");
componentPermissions.put("WebViewer", locationPermissions);
}
}
}
}
......
......@@ -78,6 +78,7 @@ public final class Project {
private static final String VNAMETAG = "versionname";
private static final String ASSETSTAG = "assets";
private static final String BUILDTAG = "build";
private static final String USESLOCATIONTAG = "useslocation";
// Table containing project properties
private Properties properties;
......@@ -90,7 +91,7 @@ public final class Project {
// List of source files
private List<SourceDescriptor> sources;
// Logging support
private static final Logger LOG = Logger.getLogger(Project.class.getName());
......@@ -190,7 +191,7 @@ public final class Project {
public void setIcon(String icon) {
properties.setProperty(ICONTAG, icon);
}
/**
* Returns the version code.
*
......@@ -208,7 +209,7 @@ public final class Project {
public void setVCode(String vcode) {
properties.setProperty(VCODETAG, vcode);
}
/**
* Returns the version name.
*
......@@ -227,6 +228,18 @@ public final class Project {
properties.setProperty(VNAMETAG, vname);
}
/**
* gets the useslocation property
*
* @return useslocation property
*/
public String getUsesLocation() {
String retval = properties.getProperty(USESLOCATIONTAG);
if (retval == null) // Older Projects won't have this
retval = "False";
return retval;
}
/**
* Returns the project directory. This directory contains the project.properties file.
*
......
......@@ -211,10 +211,12 @@ public class YaVersion {
// - IMAGEPICKER_COMPONENT_VERSION was incremented to 5.
// For YOUNG_ANDROID_VERSION 70:
// - TEXTING_COMPONENT_VERSION was incremented to 3.
// For YOUNG_ANDROID_VERSION 71:
// - NOTIFIER_COMPONENT_VERSION was incremented to 2.
// For YOUNG_ANDROID_VERSION 71:
// - NOTIFIER_COMPONENT_VERSION was incremented to 2.
// For YOUNG_ANDROID_VERSION 72:
// - WEBVIEWER_COMPONENT_VERSION was incremented to 3
public static final int YOUNG_ANDROID_VERSION = 71;
public static final int YOUNG_ANDROID_VERSION = 72;
// ............................... Blocks Language Version Number ...............................
......@@ -425,7 +427,7 @@ public class YaVersion {
// For IMAGEPICKER_COMPONENT_VERSION 5:
// - The ImagePath property was changed to Selection, and now returns a file path to
// external storage
public static final int IMAGEPICKER_COMPONENT_VERSION = 5;
// For IMAGESPRITE_COMPONENT_VERSION 2:
......@@ -600,6 +602,8 @@ public class YaVersion {
// For WEBVIEWER_COMPONENT_VERSION 2:
// - The CanGoForward and CanGoBack methods were added
public static final int WEBVIEWER_COMPONENT_VERSION = 2;
// For WEBVIEWER_COMPONENT_VERSION 3:
// - Add UsesLocation property to set location permissions
public static final int WEBVIEWER_COMPONENT_VERSION = 3;
}
......@@ -16,6 +16,13 @@ import com.google.appinventor.components.common.ComponentCategory;
import com.google.appinventor.components.common.PropertyTypeConstants;
import com.google.appinventor.components.common.YaVersion;
import com.google.appinventor.components.runtime.util.EclairUtil;
import com.google.appinventor.components.runtime.util.SdkLevel;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.view.MotionEvent;
import android.view.View;
import android.webkit.WebView;
......@@ -55,6 +62,9 @@ public final class WebViewer extends AndroidViewComponent {
// whether or not to follow links when they are tapped
private boolean followLinks;
// Whether or not to prompt for permission in the WebViewer
private boolean prompt = true;
/**
* Creates a new WebViewer component.
*
......@@ -69,6 +79,10 @@ public final class WebViewer extends AndroidViewComponent {
webview.setFocusable(true);
// enable pinch zooming and zoom controls
webview.getSettings().setBuiltInZoomControls(true);
if (SdkLevel.getLevel() >= SdkLevel.LEVEL_ECLAIR)
EclairUtil.setupWebViewGeoLoc(this, webview, container.$context());
container.$add(this);
webview.setOnTouchListener(new View.OnTouchListener() {
......@@ -275,4 +289,60 @@ public final class WebViewer extends AndroidViewComponent {
webview.loadUrl(url);
}
/**
* Specifies whether or not this WebViewer can access the JavaScript
* Location API.
*
* @param uses -- Whether or not the API is available
*/
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN,
defaultValue = "False")
@SimpleProperty(userVisible = false,
description = "Whether or not to give the application permission to use the Javascript geolocation API.")
public void UsesLocation(boolean uses) {
// We don't actually do anything here (the work is in the MockWebViewer)
}
/**
* Determine if the user should be prompted for permission to use the geolocation API while in
* the webviewer.
*
* @return true if prompting is required. False assumes permission is granted.
*/
@SimpleProperty(description = "If True, then prompt the user of the WebView to give permission to access the geolocation API. " +
"If False, then assume permission is granted.")
public boolean PromptforPermission() {
return prompt;
}
/**
* Determine if the user should be prompted for permission to use the geolocation API while in
* the webviewer.
*
* @param prompt set to true to require prompting. False assumes permission is granted.
*/
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN,
defaultValue = "True")
@SimpleProperty(userVisible = true)
public void PromptforPermission(boolean prompt) {
this.prompt = prompt;
}
/**
* Clear Stored Location permissions. When the geolocation API is used in
* the WebViewer, the end user is prompted on a per URL basis for whether
* or not permission should be granted to access their location. This
* function clears this information for all locations.
*
* As the permissions interface is not available on phones older then
* Eclair, this function is a no-op on older phones.
*/
@SimpleFunction(description = "Clear stored location permissions.")
public void ClearLocations() {
if (SdkLevel.getLevel() >= SdkLevel.LEVEL_ECLAIR)
EclairUtil.clearWebViewGeoLoc();
}
}
......@@ -4,10 +4,23 @@
// Released under the MIT License https://raw.github.com/mit-cml/app-inventor/master/mitlicense.txt
package com.google.appinventor.components.runtime.util;
import com.google.appinventor.components.runtime.WebViewer;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import com.bugsense.trace.BugSenseHandler;
import android.view.MotionEvent;
import android.view.View;
import android.webkit.GeolocationPermissions;
import android.webkit.GeolocationPermissions.Callback;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.webkit.WebChromeClient;
/**
* Helper methods for calling methods added in Eclair (2.0, API level 5)
*
......@@ -39,4 +52,61 @@ public class EclairUtil {
BugSenseHandler.sendException(ex);
}
/**
* Setup Dialog Box to request location permission from end-user for the Javascript
* location (navigator.geolocation.getCurrentLocation()) API.
*
* @param webview - The WebView component running the Javascript engine that needs permission
* @param activity - Its containing activity used for placing the dialog box
*/
public static void setupWebViewGeoLoc(final WebViewer caller, WebView webview, final Activity activity) {
webview.getSettings().setGeolocationDatabasePath(activity.getFilesDir().getAbsolutePath());
webview.getSettings().setDatabaseEnabled(true);
webview.setWebChromeClient(new WebChromeClient() {
@Override
public void onGeolocationPermissionsShowPrompt(String origin,
Callback callback) {
final Callback theCallback = callback;
final String theOrigin = origin;
if (!caller.PromptforPermission()) { // Don't prompt, assume permission
callback.invoke(origin, true, true);
return;
}
AlertDialog alertDialog = new AlertDialog.Builder(activity).create();
alertDialog.setTitle("Permission Request");
if (origin.equals("file://"))
origin = "This Application";
alertDialog.setMessage(origin + " would like to access your location.");
alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, "Allow",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
theCallback.invoke(theOrigin, true, true);
}
});
alertDialog.setButton(AlertDialog.BUTTON_NEGATIVE, "Refuse",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
theCallback.invoke(theOrigin, false, true);
}
});
alertDialog.show();
}
});
}
/**
* Clear Stored Location permissions. When the geolocation API is used in
* the WebViewer, the end user is prompted on a per URL basis for whether
* or not permission should be granted to access their location. This
* function clears this information for all locations.
*
* As the permissions interface is not available on phones older then
* Eclair, this function is a no-op on older phones.
*/
public static void clearWebViewGeoLoc() {
GeolocationPermissions permissions = GeolocationPermissions.getInstance();
permissions.clearAll();
}
}
......@@ -86,8 +86,8 @@
Note that you do not need to worry about UTF-encoding the
query. But you do need to make sure the query follows the
syntax described in the
<a href="https://developers.google.com/fusiontables/docs/v1/getting_started" target="_blank">reference manual</a>,
which means that things like capitalization for names of columns matters, and that single quotes must
<a href="https://developers.google.com/fusiontables/docs/v1/getting_started" target="_blank">reference manual</a>,
which means that things like capitalization for names of columns matters, and that single quotes must
be used around column names if there are spaces in them.
</p>
<p>
......@@ -102,7 +102,7 @@
</h3>
<h4>Getting an API Key</h4>
<p>In order to use the FusiontablesControl Component you need to acquire a Google
<p>In order to use the FusiontablesControl Component you need to acquire a Google
a Google Applications Programming Interface (API) key, an <i>API Key</i>. To get an API key,
follow these instructions:</p>
<ol>
......@@ -115,7 +115,7 @@
your key as the value to the <i>ApiKey</i> property in all your Fusiontables apps.</p>
<h4>Creating Fusion Tables</h4>
<p>You will probably want to create your own Fusion Tables to experiment with as you are developing your apps.
<p>You will probably want to create your own Fusion Tables to experiment with as you are developing your apps.
This is as easy as creating a Google document, if you are familiar with that process. Here are the steps:</p>
<ol>
<li>On the web, login to your Gmail account or any other Google service (e.g., Drive, YouTube).</li>
......@@ -131,7 +131,7 @@
<h4>Creating a Fusiontables App</h4>
<p>When you drag the <i>FusiontablesControl</i> component onto the Designer, don't forget to set its <i>ApiKey</i>
property, which is initially blank. You should copy this from your <a href="https://code.google.com/apis/console/" target="_blank">Google APIs Console</a>
and paste it into the property field.
and paste it into the property field.
<h3>
Properties
</h3>
......@@ -148,13 +148,13 @@
<p>In order to develop apps that use Fusiontables, you must
obtain a Google API Key. To get a key follow these
instructions.</p>
<ol>
<li>Go to your <a href="https://code.google.com/apis/console/" target="_blank">Google APIs Console</a> and login if necessary.</li>
<li>Select the <i>Services</i> item from the menu on the left.</li>
<li>Choose the <i>Fusiontables</i> service from the list provided and turn it on.</li>
<li>Go back to the main menu and select the <i>API Access</i> item. </li>
</ol>
Your API Key will be near the bottom of that pane in the section called "Simple API Access".
<ol>
<li>Go to your <a href="https://code.google.com/apis/console/" target="_blank">Google APIs Console</a> and login if necessary.</li>
<li>Select the <i>Services</i> item from the menu on the left.</li>
<li>Choose the <i>Fusiontables</i> service from the list provided and turn it on.</li>
<li>Go back to the main menu and select the <i>API Access</i> item. </li>
</ol>
Your API Key will be near the bottom of that pane in the section called "Simple API Access".
</p>
</dd>
</dl>
......@@ -902,6 +902,25 @@
URL of the page the WebViewer should initially open to. Setting this will load
the page.
</dd>
<dt>
<code>
PromptforPermission
</code>
</dt>
<dd>
If True, then prompt the user of the WebView to give
permission to access the geolocation API. If False, then
assume permission is granted.
</dd>
<dt>
<code>
UsesLocation
</code>
</dt>
<dd>
Whether or not to give the application permission to use the
Javascript geolocation API.
</dd>
<dt>
<code>
Visible
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment