Inside Android webview I consistent get a PermissionDenied error back from a call to navigator.mediaDevices.getUserMedia despite asking for permission first.
My process starts with a call to StartRecordAudio function below with the CheckPermissionFirst parameter set to true if working in Android.
var AudioRecorder ; // recorder
var RawAudioDataArray ; // raw recorded data
var AudioSaveFormat = "audio/wav" ; // waveform audio - alternatives "audio/mpeg-3" and "audio/webm" ;
var AudioStartCallback ; // function to be called at start of input
var AudioErrorCallback ; // function to be called if audio cannot be recorded (no capability orno permission)
var AudioDoneCallback ; // function to be called at end of input
function StartRecordAudio (CheckPermissionFirst,StartCallback,ErrorCallback,DoneCallback)
{
AudioStartCallback = StartCallback ;
AudioErrorCallback = ErrorCallback ;
AudioDoneCallback = DoneCallback ;
RawAudioDataArray = [] ;
if (AudioRecorder)
{
StartRecording ();
}
else if (CheckPermissionFirst)
{
CheckAppMicrophonePermission ()
}
else
{
navigator.mediaDevices.getUserMedia ({audio:true}).then (AudioDeviceFound).catch (AudioErrorCallback);
};
};
CheckAppMicrophonePermission is as follows
function CheckAppMicrophonePermission ()
{
AndroidBridge.call ("CheckMicrophonePermission" , []);
};
Skipping over the details of the AndroidBridge, this call triggers the following java function in the Android wrapper code
if ("CheckMicrophonePermission".equals(command))
{
if (ContextCompat.checkSelfPermission (MainActivity.this, android.Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED)
{
ActivityCompat.requestPermissions (MainActivity.this, new String[]{android.Manifest.permission.RECORD_AUDIO}, requestMicrophoneId );
}
else
{
MicrophonePermissionsOK ();
}
}
which when finished interrogating the user will call
public void onRequestPermissionsResult (int requestCode, String[] permissions, int[] grantResults)
{
switch (requestCode)
{
case requestMicrophoneId:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
{
MicrophonePermissionsOK();
break;
}
else if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE))
{
MicrophonePermissionsNOTOK(false);
}
else
{
MicrophonePermissionsNOTOK(true);
};
break;
}
}
Function MicrophonePermissionsOK is as follows :
public void MicrophonePermissionsOK ()
{
String jsString = "AppMicrophonePermissionGranted()";
mWebView.evaluateJavascript (jsString);
}
Back on the original side of the Android Bridge
function AppMicrophonePermissionGranted ()
{
AndroidMicrophonePermission = true ;
ShowAlert ("Please select OK to activate the app microphone link");
AlertCallback = function () {HideAlert (); AudioPermissionGranted ()};
};
Finally when the user ok's my custom alert dialog (details of this function irrelevant here I think), we get to:
function AudioPermissionGranted ()
{
navigator.mediaDevices.getUserMedia ({audio:true}).then (AudioDeviceFound).catch (AudioErrorCallback);
};
For anyone interested the promise success route is to
function AudioDeviceFound (Stream)
{
AudioRecorder = new MediaRecorder (Stream);
AudioRecorder.ondataavailable = function (e)
{
RawAudioDataArray.push (e.data);
if (AudioRecorder.state == "inactive") // recorder has been stopped either by user or after max seconds
{
var blob = new Blob (RawAudioDataArray, {type:AudioSaveFormat});
var reader = new FileReader ();
reader.onload = function () {AudioDoneCallback (reader.result.split (',')[1])};
reader.readAsDataURL (blob);
};
};
StartRecording ();
};
function StartRecording ()
{
AudioStartCallback ();
AudioRecorder.start (1000); // record in 1 second chunks
SetAudioTimeout ();
};
function SetAudioTimeout ()
{
AudioTimeout = window.setTimeout (function () {AudioRecorder.stop();
AudioTimeout = null}, MaxAudioSeconds * 1000);
};
All of this code works fine on Chrome and inside a Chrome Packaged App.
However I have traced the code inside an Android webview and I get the correct sequence of actions right through all the code up until invoking the getUserMedia promise.
This is the case for a new loaded app or an existing loaded app. Also when the permission has not been requested before, when the permission has been requested denied and when the permission is granted, both through the permission dialog shown above directly in the settings app.
However (in Android webview), invoking the getUserMedia promise always triggers the catch condition with error parameter name set to PermissionDenied and the message set to empty string.
Please can someone help me?
Related
I'm developing an Android App (Android 6.0 and above) and I have performance issues with large XML-Files. (about 17 mB)
When the app is started the required XML-document is loaded from the private storage and a List is returned (takes about 2-3 sek.) and filled in an custom adapter -> this works perfectly fine.
But the user can start a synchronization manually inside the app (e.g.: data
was updated on server)
Therefor I've implemented a background download-service so that the UI stays responsive during the download.
The downloaded data is stored inside the private folder again.
problem:
The background download works perfectly and my UI stays responive until I start reading the information from the new XML-File.
I don't get any result back -> even after 3 min there is no return value from the function although I use the same function for reading the XML like I do at the beginning -> GetKontaktliste()
public class MainActivity : AppCompatActivity
{
alert.SetPositiveButton("Synchronisieren", async (senderAlert, args) =>
{ new DownloadTask(this).Execute("");});
}
public class DownloadTask : AsyncTask
{
protected override Java.Lang.Object DoInBackground(params Java.Lang.Object[] #params)
{
App_Tools lAppTools = new App_Tools();
ThreadPool.QueueUserWorkItem(async state =>
{
//Download Function -> received XML-Data is saved in local storage
bool lKontakte = await lAppTools.DownloadKontakte(_context);
bool lVorgaenge = await lAppTools.DownloadVorgaenge(_context);
bool lVorgaengeImport = await lAppTools.ImportVorgaenge(_context);
if (lKontakte == true)
{
//Problem is that i dont get any results back here
List<KontaktItem> lResult = new List<KontaktItem>();
LinqAbfragen lLinq = new LinqAbfragen();
lLinq.GetKontaktliste();
}
});
return true;
}
}
public class LinqAbfragen
{
//Read Contacts
public List<KontaktItem> GetKontaktliste()
{
List<KontaktItem> lResult = new List<KontaktItem>();
//Read from IEnumerable<XElement>
var KontakteAsXElement = ReadXmlAsXElement("Kontakte.xml", "Kontakt");
lResult = (from kontakt in KontakteAsXElement
select new KontaktItem
{
AdressNr = kontakt.Element("AdressNr").Value,
Vorname = kontakt.Element("Vorname1").Value,
Nachname = kontakt.Element("Name1").Value,
xmlData = (string)kontakt.ToString()
}
).ToList();
return lResult;
}
}
I have an odd issue I can't explain the reason for it - maybe someone here can shed some light on it
I have a ticket scanning app in Xamarin Forms currently testing it on android
the interface allows you to:
type an order number and click the check order Button
use the camera scanner to scan which automatically triggers check order
use the barcode scanner to scan which automatically triggers check order
after the check order validation, user has to select the number of tickets from a drop down list and press confrim entry button
what I'm trying to do, is if the seats available on that ticket is just 1 - then automatically trigger confirm entry button functionality
problem that I have is that - some of my logic depends on setting the drop down index in code - for some reason it doesn't update - as seen in the debugger shot here
and this is the second tme I've noticed this today, earlier it was a var I was trying to assign a string and it kept coming up as null - eventually I replaced that code
is this a bug in xamarin ?
code has been simplified:
async void OnCheckOrderButtonClicked(object sender, EventArgs e)
{
await ValidateOrderEntry();
}
private async void scanCameraButton_Clicked(object sender, EventArgs e)
{
messageLabel.Text = string.Empty;
var options = new ZXing.Mobile.MobileBarcodeScanningOptions();
options.PossibleFormats = new List<ZXing.BarcodeFormat>() {
ZXing.BarcodeFormat.QR_CODE,ZXing.BarcodeFormat.EAN_8, ZXing.BarcodeFormat.EAN_13
};
var scanPage = new ZXingScannerPage(options);
scanPage.OnScanResult += (result) =>
{
//stop scan
scanPage.IsScanning = false;
Device.BeginInvokeOnMainThread(async () =>
{
//pop the page and get the result
await Navigation.PopAsync();
orderNoEntry.Text = result.Text;
//automatically trigger update
await ValidateOrderEntry();
});
};
await Navigation.PushAsync(scanPage);
}
private async Task ValidateOrderEntry()
{
//...other code....
checkInPicker.Items.Clear();
if (availablTickets == 1)
{
checkInPickerStack.IsVisible = true;
checkInPicker.SelectedIndex = 0;
messageLabel.Text = "Ticket OK! - " + orderNoEntry.Text;
messageLabel.TextColor = Color.Green;
//select the only element
checkInPicker.SelectedIndex = 0;
await PostDoorEntry();
}
//...other code....
}
private async Task PostDoorEntry()
{
int entryCount = checkInPicker.SelectedIndex + 1;
//... more code...
//...post api code..
}
Maybe I'm overlooking something, but you clear all the items a few lines above the one you are pointing out. That means there are no items in your Picker and thus you can't set the SelectedIndex to anything other than -1, simply because there are no items.
I downloaded the sample Location services project from https://mobilefirstplatform.ibmcloud.com/tutorials/en/foundation/7.1/advanced-client-side-development/location-services-hybrid-applications/. From the sample I kept posChange trigger policy and i deleted the other two. And I am getting the location when app is opened. But not in the app is closed. Below the snippet
function getFirstPositionAndTrack() {
var geoPolicy = WL.Device.Geo.Profiles.RoughTracking();
geoPolicy.timeout = 60000;
geoPolicy.maximumAge = 10000;
geoPolicy.minChangeDistance = 3;
WL.Device.Geo.acquirePosition(
function(pos) {
var triggers = {
Geo: {
posChange: { // display all movement
type: "PositionChange",
minChangeDistance: 5,
callback: function(deviceContext) {
WL.Client.transmitEvent({ event: 'deviceLocation'}, true);
}
},
}
};
WL.Device.startAcquisition({ Geo: geoPolicy }, triggers, { Geo: alertOnGeoAcquisitionErr } );
},
function(geoErr) {
console.log(geoErr);
// try again:
getFirstPositionAndTrack();
},
geoPolicy
);
}
And in adapter to check background
WL.Server.setEventHandlers([
WL.Server.createEventHandler({ event: 'deviceLocation'}, deviceEvent)
]);
Procedure
function deviceEvent(event) {
WL.Logger.error(JSON.stringify(event));
}
And in onconnectSuccess I am calling the function and setting the keepAlive Property
getFirstPositionAndTrack()
WL.App.setKeepAliveInBackground(true);
And is there any other way to debug when app is in background or closed?
I'm making an app capable of take photos and upload them to a server. The phone will save the id generated.
I made a class abstractApp that creates an App object with a couple of helpers and variables. I'm using Framework7.
var App;
document.addEventListener("deviceready", function() {
App = new abstractApp();
var i;
App.f7Ref = new Framework7({init: false});
for (i = 0; i < App.constants.views.length; i++)
{
if (i==0)
App.mainView = App.f7Ref.addView (
App.constants.views[i].selector,
App.constants.views[i].settings );
else
App.f7Ref.addView (
App.constants.views[i].selector,
App.constants.views[i].settings );
}
// Checks if there exists register of remote photos
if ( App.local.get('remotephotos', false) == null || App.local.get('remotephotos', false) == '' )
{
App.remotephotos = [];
App.local.set('remotephotos', []);
}
else
{
App.remotephotos = App.local.get('remotephotos');
}
// Checks if there exists register of local photos
if ( App.local.get('localphotos', false) == null || App.local.get('localphotos', false) == '' )
{
App.localphotos = [];
App.local.set('localphotos', []);
}
else
{
App.localphotos = App.local.get('localphotos');
}
for (i = 0; i < appControllers.length; i++)
{
appControllers[i].apply(App);
}
console.log(App);
}, false);
In appControllers I'm saving functions related to each page (so it is a bit more organized). With only index and new-photo controllers I have no problem, I can attach events to elements and navigate between views. The problem is calling the camera object.
window.appControllers.push(function()
{
var $$ = Dom7;
var Ref = this.f7Ref;
var server = new serverInterface();
var photos = this.remotephotos;
var App = this;
Ref.onPageInit('new', function (page) {
Ref.alert('entra', 'entra');
$$('.capture').on('click', function () {
Ref.alert('Click', 'Click detected');
navigator.camera.getPicture(onSuccess, onFail,
{
quality: 20,
destinationType: destinationType.FILE_URI
});
function onSuccess(imageURI) {
Ref.alert('Photo captured.', 'Bien');
}
function onFail(message) {
Ref.alert('There was a problem.', 'Ups');
}
});
});
});
So, I enter the new page and I click the button with cass capture and the alert (click detected) appears, but it doesn't show the camera to take the photo.
I'm using Phonegap Build and an android phone, do you have any idea of what's happening?
Thank you very much in advance
The problem wasn't the javascript code I quoted.
I was loading the camera plugin in the config.xml like this
<gap:plugin name="org.apache.cordova.camera" />
But it should be loaded this way:
<plugin name="cordova-plugin-camera" />
If the plugin is not loaded correctly, in Phonegap Build appears: version n/a installed. In this case, when it is correctly loaded, appears: version 2.1.0 installed.
More information about this thread can be found here: http://phonegap.com/blog/2015/11/19/config_xml_changes_part_two/
Hope it helps someone else too
Regards
I have to develop an Android application using phongap that retrieves the sensors data from the device.
One of the sensors I have to listen to is the ambient light sensor. This sensor has no implementation in phoneGap, so I have to add it as a plugin to PhoneGap.
I know how to add plugin and I know how to access the ALS data from Java - but in order to be sure that I am implementing it well I want to implement it as PhoneGap implements other sensors like Accelerometer. Therefor I wrote a ALSManager class in java that I implemented as I found the Accelerometer was implemented here:
https://github.com/apache/cordova-android/blob/master/framework/src/org/apache/cordova/AccelListener.java
and added lightSensor and lightValues modules like the acceleromter and acceleration modules.
But when I run this application I got following error message:
TypeError: Object # has no method 'getCurrentLight'
(and in the lightSensor module I have getCurrentLight method).
does any one can please suggest me what I am missing? or what do I have to do?
Thanks in advance,
The code I added in the cordova-2.5.0.js. Let me know if it's not enough:
// file: lib/common/plugin/LightValues.js
define("cordova/plugin/LightValues", function(require, exports, module) {
var Acceleration = function(lux, timestamp) {
this.lux = lux;
this.timestamp = timestamp || (new Date()).getTime();
};
module.exports = LightValues;
});
// file: lib/common/plugin/lightSensor.js
define("cordova/plugin/lightSensor", function(require, exports, module) {
/**
* This class provides access to device accelerometer data.
* #constructor
*/
var argscheck = require('cordova/argscheck'),
utils = require("cordova/utils"),
exec = require("cordova/exec"),
LightValues = require('cordova/plugin/LightValues');
// Is the accel sensor running?
var running = false;
// Keeps reference to watchAcceleration calls.
var timers = {};
// Array of listeners; used to keep track of when we should call start and stop.
var listeners = [];
// Last returned acceleration object from native
var light = null;
// Tells native to start.
function start() {
exec(function(a) {
var tempListeners = listeners.slice(0);
light = new LightValues(a.lux, a.timestamp);
for (var i = 0, l = tempListeners.length; i < l; i++) {
tempListeners[i].win(light);
}
}, function(e) {
var tempListeners = listeners.slice(0);
for (var i = 0, l = tempListeners.length; i < l; i++) {
tempListeners[i].fail(e);
}
}, "Light", "start", []);
running = true;
}
// Tells native to stop.
function stop() {
exec(null, null, "Light", "stop", []);
running = false;
}
// Adds a callback pair to the listeners array
function createCallbackPair(win, fail) {
return {win:win, fail:fail};
}
// Removes a win/fail listener pair from the listeners array
function removeListeners(l) {
var idx = listeners.indexOf(l);
if (idx > -1) {
listeners.splice(idx, 1);
if (listeners.length === 0) {
stop();
}
}
}
var lightSensor = {
/**
* Asynchronously acquires the current acceleration.
*
* #param {Function} successCallback The function to call when the acceleration data is available
* #param {Function} errorCallback The function to call when there is an error getting the acceleration data. (OPTIONAL)
* #param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. (OPTIONAL)
*/
getCurrentLight: function(successCallback, errorCallback, options) {
//argscheck.checkArgs('fFO', 'lightSensor.getCurrentLight', arguments);
var p;
var win = function(a) {
removeListeners(p);
successCallback(a);
};
var fail = function(e) {
removeListeners(p);
errorCallback && errorCallback(e);
};
p = createCallbackPair(win, fail);
listeners.push(p);
if (!running) {
start();
}
},
/**
* Asynchronously acquires the acceleration repeatedly at a given interval.
*
* #param {Function} successCallback The function to call each time the acceleration data is available
* #param {Function} errorCallback The function to call when there is an error getting the acceleration data. (OPTIONAL)
* #param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. (OPTIONAL)
* #return String The watch id that must be passed to #clearWatch to stop watching.
*/
watchLight: function(successCallback, errorCallback, options) {
//argscheck.checkArgs('fFO', 'lightSensor.watchLight', arguments);
// Default interval (10 sec)
var frequency = (options && options.frequency && typeof options.frequency == 'number') ? options.frequency : 10000;
// Keep reference to watch id, and report accel readings as often as defined in frequency
var id = utils.createUUID();
var p = createCallbackPair(function(){}, function(e) {
removeListeners(p);
errorCallback && errorCallback(e);
});
listeners.push(p);
timers[id] = {
timer:window.setInterval(function() {
if (light) {
successCallback(light);
}
}, frequency),
listeners:p
};
if (running) {
// If we're already running then immediately invoke the success callback
// but only if we have retrieved a value, sample code does not check for null ...
if (light) {
successCallback(light);
}
} else {
start();
}
return id;
},
/**
* Clears the specified accelerometer watch.
*
* #param {String} id The id of the watch returned from #watchAcceleration.
*/
clearWatch: function(id) {
// Stop javascript timer & remove from timer list
if (id && timers[id]) {
window.clearInterval(timers[id].timer);
removeListeners(timers[id].listeners);
delete timers[id];
}
}
};
module.exports = lightSensor;
});
I think maybe the problem is that you are adding your plugin code to cordova-2.5.0.js file. Instead what you should do is create a standalone JS file for each of your JavaScript files and then cordova.require() those files in the HTML page where you want to use that functionality.
So, create LightValues.js and LightSensor.js as separate files somewhere in your www folder. Then in your HTML file, make sure to include the JS file: <script type="text/javascript" src="path-to-lightSensor.JS-file"> (You'd only need to include this one file since it require()s the second one.)
Next, in deviceReady() function, you can call the light sensor with var lightSensor = cordova.require("cordova/plugin/lightSensor"). Notice that the cordova/plugin/lightSensor is not the path to the JS file but the name of it module that you declared in the define() section when you wrote the plugin.
After this you should be able to call lightSensor.getCurrentLight(). If you console.log(lightSensor) you would expect to see all of the available methods that you wrote.
Note that I'm not positive that cordova.require and cordova.define work in cordova-2.5. I'd hope that they do but this page sort of suggests it may not be supported until 2.6. If you are having problems after splitting the files out, maybe it is because of this.
I have just developed a light sensor plugin and fortunately succeed. I have just read the code above and found some small mistakes about identifier e.g "var Acceleration = function(lux, timestamp)" the variable should be LightValues instead of Acceleration. So please check the code first to void some essential mistakes. Then, I first use "lux" as the name of the variable but I got an undefined value of light when debugging the program. So I change "lux" into "x" and it did work!
There are 6 files should be paid attention to: the "LightListener.java", "LightValues.js", "LightSensor.js","cordova_plugin.js", "index.html" and "config.xml". If you configure all the 6 files, the program should work.