I'm building a voice call app for android using WebView. Everything works fine except in android 12, I can not use the earpiece (or even wired headphones in some phones) for audio out. The audio is always playing through loudspeaker. And some of the users reported that they can hear the internal ring(Which is a MediaPlayer object) in earpiece, but once the call answered it switches to loudspeaker. And the worst part is, I'm not able to reproduce this issue as this works perfectly on all my test phones (android 12).
My code,
private fun setAudioOutDevice(target: AudioDeviceInfo?) {
//need some delay for bluetooth sco
var audioChangeDelay: Long = 15
if (System.currentTimeMillis() - lastAudioChangeTime <= 300) audioChangeDelay = 300
Handler(Looper.getMainLooper()).postDelayed({
var targetDevice = target
//no device selected. scan all output devices and select one automatically
if (targetDevice == null) {
var wiredHeadsetDevice: AudioDeviceInfo? =
getAudioDevice(AudioDeviceInfo.TYPE_WIRED_HEADSET)
if (wiredHeadsetDevice == null) wiredHeadsetDevice =
getAudioDevice(AudioDeviceInfo.TYPE_WIRED_HEADPHONES)
val bluetoothDevice: AudioDeviceInfo? =
getAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO)
val speakerDevice: AudioDeviceInfo? =
getAudioDevice(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER)
val earpieceDevice: AudioDeviceInfo? =
getAudioDevice(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE)
//update global variables
isBluetoothAvailable = bluetoothDevice != null
//disable BT if wired headset connected
if (wiredHeadsetDevice != null) isBluetoothAvailable = false
//choose an output device
targetDevice =
if (callType == videoCall && bluetoothDevice == null && wiredHeadsetDevice == null) speakerDevice
else if (callType == videoCall && wiredHeadsetDevice == null && !isBluetoothAudioEnabled) speakerDevice
else wiredHeadsetDevice
?: if (isBluetoothAvailable && isBluetoothAudioEnabled && bluetoothDevice != null) bluetoothDevice
else earpieceDevice ?: speakerDevice
//no earpiece
if (earpieceDevice == null) {
setBigStatus("Unable to access Earpiece", 0)
}
}
//set output device
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
var success = false
try {
// am.clearCommunicationDevice()
if (targetDevice != null) success = am.setCommunicationDevice(targetDevice)
} catch (e: Exception) {
debugMsg(e.message.toString())
}
if (!success) setBigStatus(getString(R.string.unable_to_set_audio), 0)
}
//older devices, android 11 or lower
else {
am.mode = AudioManager.MODE_IN_COMMUNICATION
if (targetDevice?.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
changeSco(false)
am.isSpeakerphoneOn = true
} else {
if (targetDevice?.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO) changeSco(true)
else changeSco(false)
am.isSpeakerphoneOn = false
}
}
}, audioChangeDelay)
}
private fun changeSco(enable: Boolean) {
if (enable) {
am.startBluetoothSco()
am.isBluetoothScoOn = true
} else {
am.stopBluetoothSco()
am.isBluetoothScoOn = false
}
}
private fun getAudioDevice(type: Int): AudioDeviceInfo? {
val audioDevices = am.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
for (deviceInfo in audioDevices) {
if (type == deviceInfo.type) return deviceInfo
}
return null
}
The above code is working fine on android 11 and lower and even on some android 12 phones. How can I make this work on all devices?
There is an issue tracker here. But it seems that nobody is working on it.
UPDATE Today I tried it on a pixel 3a and it gives mixed behaviors. Sometimes it plays on the earpiece or wired headset but for most of the calls, it plays on the loudspeaker only. And even if I don't set any mode, it automatically switches to MODE_IN_COMMUNICATION right after the stream starts. I think, android is trying to figure out the proper mode by itself and that messing with my app logic.
Related
How to check the “Allow Full Access” is enabled in iOS 11?
I have tried multiple methods which do not seem to be working in iOS 10 or iOS 11.
Here is one that I tried:
func hasFullAccess() -> Bool
{
var hasFullAccess = false
if #available(iOSApplicationExtension 10.0, *) {
let pasty = UIPasteboard.general
if pasty.hasURLs || pasty.hasColors || pasty.hasStrings || pasty.hasImages {
hasFullAccess = true
} else {
pasty.string = "TEST"
if pasty.hasStrings {
hasFullAccess = true
pasty.string = ""
}
}
} else {
// Fallback on earlier versions
var clippy : UIPasteboard?
clippy = UIPasteboard.general
if clippy != nil {
hasFullAccess = true
}
}
return hasFullAccess
}
Every time it returns true and I am running this on a device not on the simulator.
First of all, it feels like you're checking the iOS version wrong. It usually looks like this if #available(iOS 11.0, *) where iOS version you pass is the latest one you use. In your declaration iOS 11 is not even there, it checks for iOS 10 and below.
Second, you're using || which is OR operator. So if any of those statements is true, the whole thing will return true. You need && the AND operator to check whether everything is matching.
It's my first time asking a question on stackoverflow, so i'll try to be as specific as I can. Thanks for your help.
Currently I'm developing an application for Androïd and IOS with Angular/IONIC solution.
Actually I've developed a service using cordova-plugin-screen-orientation 1.4.2 version plugin to change the orientation of my screen for some views. In fact, that work perfectly on both OS. But I also need when it's done, to catch the width and height of my device.
So there is my problem. On Android everything works, but on IOS, it looks like when I change the orientation I catch always the old values. (if I change for a landscape, I catch the previous portrait orientation values).
I tried different algorythm like the more simple : change the orientation and when it's done catch the values.
Currently I'm trying with a watcher, for the same result.
Here is my code :
class WindowService extends Service {
constructor($resource,$config, $window, Watcher) {
'ngInject';
super();
this.orientations = {
1:"portrait",
2:"landscape"
}
//imported library
this.md = new MobileDetect($window.navigator.userAgent);
// if it's a tablet Android, md.tablet() != null && md.os() == 'AndroidOS'
// if it's an iPad md.tablet() == 'iPad'
// if it's an iPhone md.mobile() == 'iPhone'
// if it's an Android phone, md.phone() != null && md.os() == 'AndroidOS'
console.log("md > ", this.md.mobile());
this.maxHeight = "100vh";
this.maxWidth = "100vw";
if (this.isAndroid()){
this.maxHeight = $window.innerHeight + "px";
this.maxWidth = $window.innerWidth + "px";
this.maxHeightWatcher = Watcher.watch(() => $window.innerHeight, innerHeight => {
this.maxHeight = $window.innerHeight + "px";
this.maxWidth = $window.innerWidth + "px";
});
}
}
setOrientation(){
this.invoke(($window) => {
if ($window.screen.unlockOrientation){
$window.screen.unlockOrientation();
if ((this.md.phone() != null && this.md.os() == 'AndroidOS') || this.md.mobile() == 'iPhone'){
$window.screen.lockOrientation('portrait');
}else if ((this.md.tablet() != null && this.md.os() == 'AndroidOS') || this.md.tablet() == 'iPad'){
$window.screen.lockOrientation('landscape');
}else {
$window.screen.lockOrientation('landscape');
}
}
});
}
//THIS S THE FUNCTION I CALL
changeOrientation(orientationId){
this.invoke(($window) => {
this.lockScreenOrientation($window, orientationId).then(res => {
console.log("changeOrientation > ", res);
}).catch(err => {
console.log("changeOrientation > ", err);
});
});
}
lockScreenOrientation($window, orientationId){
if (this.orientations[orientationId] != undefined){
if (this.orientations[orientationId] != $window.screen.orientation){
if ($window.screen.unlockOrientation){
$window.screen.unlockOrientation();
console.log("lockOrientation > ", $window.screen.lockOrientation(this.orientations[orientationId]));
return Promise.resolve(this.orientations[orientationId]);
}else{
return Promise.reject("[ERROR] cordova-plugin-screen-orientation hadn't been loaded.")
}
}else{
return Promise.resolve(this.orientations[orientationId]);
}
}else{
return Promise.reject("[ERROR] The orientation specified is Unknown.")
}
}
getDevice(){
if ((this.md.phone() != null && this.md.os() == 'AndroidOS')){
return "Android Smartphone";
}else if (this.md.mobile() == 'iPhone'){
return "iPhone";
}else if ((this.md.tablet() != null && this.md.os() == 'AndroidOS')){
return "Android Tablet";
}else if (this.md.tablet() == 'iPad'){
return "iPad";
}else
return "Unknown";
}
isSmartphone(){
if ((this.md.phone() != null && this.md.os() == 'AndroidOS') || this.md.mobile() == 'iPhone'){
return true;
}
return false;
}
isTablet(){
if ((this.md.tablet() != null && this.md.os() == 'AndroidOS') || this.md.tablet() == 'iPad')
return true;
return false;
}
isIOS() {
if (this.md.mobile() == 'iPhone' || this.md.tablet() == 'iPad')
return true;
return false;
}
isAndroid() {
if ((this.md.phone() != null && this.md.os() == 'AndroidOS')
|| (this.md.tablet() != null && this.md.os() == 'AndroidOS'))
return true;
return false;
}
getMaxHeight(){
return this.maxHeight;
}
getMaxWidth(){
return this.maxWidth;
}
}
Service.register(WindowService);
<ion-side-menu-content drag-content="false" ng-controller="HeaderController" style="height:{{view.maxHeight}};width: {{view.maxWidth}};">
....
</ion-side-menu-content>
The class Watcher is just an little extension of the basic watcher of angular. It works the same way.
[edit] Sorry I forgot to specify something : as you can see in the js code, in the constructor method, if it's IOS I just put 100vh fr the height and 100vw for the width. And I have the same result : the change from portrait to landscape keep the portrait size values.
I recently received a BLE device for Bluetooth to Serial. It uses TruConnect and I'm trying to get it to communicate with my serial device. The serial device receives communication over a serial cable and echoes back anything that is sent to it as well as any results from a command that is sent.
Right now I'm simply trying to send TruConnect commands to the BLE device to check the current baud rate that the BLE device is set for.
I wrote some code based on this TruConnect guide that I found:
https://truconnect.ack.me/1.5/apps/communicating_via_ble#reading_from_a_truconnect_device_serial_interface.
The problem seems to be that whenever I try to read anything from the tx characteristic when there should be data, the data is not right.
Setting up CBPeripheral events:
private void setupPerif(CBPeripheral perf)
{
selectedPeripheral = perf;
selectedPeripheral.UpdatedCharacterteristicValue += (sender, e) =>
{
var c = e.Characteristic;
if (c != null)
{
var uuid = c.UUID.ToString(true).ToLower();
if (uuid == UUID_RX)
{
//
}
else if (uuid == UUID_TX)
{
// expecting bytes to contain valid response data
// it almost always contains twenty 0s.
byte[] bytes = c.Value.Where(i => i != 13).ToArray();
var invalidBytes = c.Value.Where(i => i > 127).ToArray();
var nonZeros = c.Value.Where(i => i != 0).ToArray();
if (nonZeros.Length < 1)
{
return;
}
else
{
foreach (byte b in bytes)
handler.handleByteReceived((char)b);
}
}
else if (uuid == UUID_MODE)
{
//
}
}
};
selectedPeripheral.DiscoveredService += (sender, e) =>
{
var services = selectedPeripheral.Services;
if (services != null)
{
foreach (CBService service in services)
{
if (service.UUID.ToString(true).ToLower() == UUID_TRUCONNECT)
{
truConnect = service;
selectedPeripheral.DiscoverCharacteristics(truConnect);
}
}
}
};
selectedPeripheral.DiscoveredCharacteristic += (sender, e) =>
{
if (truConnect != null && truConnect.Characteristics != null)
{
foreach (CBCharacteristic c in truConnect.Characteristics)
{
var uuidString = c.UUID.ToString(true).ToLower();
if (uuidString == UUID_RX)
{
rx = c;
}
else if (uuidString == UUID_TX)
{
tx = c;
}
else if (uuidString == UUID_MODE)
{
mode = c;
// set to stream mode
selectedPeripheral.WriteValue(NSData.FromArray(new byte[] { MODE_COMMAND }), mode, CBCharacteristicWriteType.WithResponse);
}
}
}
};
selectedPeripheral.WroteCharacteristicValue += (sender, e) =>
{
// if UUID is for RX, we just wrote to RX. Drill down to
// TX characteristic and read it. This will trigger
// the UpdatedCharacteristicValue event.
string uuid = e.Characteristic.UUID.ToString(true).ToLower();
if (uuid == UUID_RX)
{
var services = selectedPeripheral.Services;
if (services != null)
{
foreach (CBService s in services)
{
if (s.UUID.ToString(true).ToLower() == UUID_TRUCONNECT)
{
var charachteristics = s.Characteristics;
if (charachteristics != null && charachteristics.Length > 0)
{
foreach (CBCharacteristic c in charachteristics)
{
if (c.UUID.ToString(true).ToLower() == UUID_TX)
{
Timer t = new Timer(new TimerCallback(delegate(object o)
{
selectedPeripheral.ReadValue(c);
}), null, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(-1));
}
}
}
}
}
}
}
};
manager.ConnectPeripheral(selectedPeripheral);
}
Writes to rx. This is what should be used to actually send commands.
public void sendCommand(string command)
{
command += endString + "\n";
if (rx != null)
{
NSData d = NSData.FromString(command);
foreach (CBService s in selectedPeripheral.Services)
{
if (s.UUID.ToString(true).ToLower() == UUID_TRUCONNECT)
foreach (CBCharacteristic c in s.Characteristics)
{
if (c.UUID.ToString(true).ToLower() == UUID_RX)
selectedPeripheral.WriteValue(NSData.FromString(command), c, CBCharacteristicWriteType.WithResponse);
}
}
}
}
So my question is, why am I not getting the expected data when the CBPeripheral.UpdatedCharacteristicValue event is called? Occasionally I will get the expected data, but it is quite rare, and I can't seem to find any logical reason or pattern that would explain why this is happening.
AHA! I figured it out!
The problem was that I need to set the notify value for the appropriate characteristics. After doing that, I didn't need to call CBPeripheral.ReadValue(CBCharacteristic).
selectedPeripheral.DiscoveredCharacteristic += (sender, e) =>
{
if (truConnect != null && truConnect.Characteristics != null)
{
foreach (CBCharacteristic c in truConnect.Characteristics)
{
var uuidString = c.UUID.ToString(true).ToLower();
if (uuidString == UUID_RX)
{
rx = c;
}
else if (uuidString == UUID_TX)
{
tx = c;
// set the notify value to true and poof!
// now CBPeripheral.UpdatedCharacteristicValue
// event will be triggered at the appropriate time.
selectedPeripheral.SetNotifyValue(true, tx);
}
else if (uuidString == UUID_MODE)
{
mode = c;
// set to remote command mode
selectedPeripheral.WriteValue(NSData.FromArray(new byte[] { MODE_COMMAND }), mode, CBCharacteristicWriteType.WithResponse);
}
}
}
};
At present i am using the below code for detecting mobiles, tablets. But i need to restrict this code to Mobile. Your suggestions can be appreciated if provided the solution.
DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("Mobile")
{
ContextCondition = (context =>
{
var manager = (context.Cache[WurflManagerCacheKey] as IWURFLManager);
var cabablities = manager.GetDeviceForRequest(context.Request.UserAgent);
return cabablities.GetCapability("is_wireless_device") == "true";
//return cabablities.GetCapability("mobile_browser").Contains("Opera");
//return cabablities.UserAgent.Contains("Opera");
})
});
I would use the is_wireless_device and is_tablet capabilities to isolate only mobile phones:
if (is_tablet == false && is_wireless_device == true) {
//Must be a mobile phone
}
I am working on a Scrollable Image field.I am handling TouchEvent.DOWN mTouchEvent.MOVE,TouchEvent.UP.
Somehow control never goes to TouchEvent.UP section.How to capture the UP event.
I have to findout the start and end points of the drag.
My Code looks like this..
if (event == TouchEvent.DOWN && touchEvent.isValid())
{
_xTouch = touchEvent.getX(1);
_yTouch = touchEvent.getY(1);
}
else if(event == TouchEvent.UP && touchEvent.isValid())
{
int x = touchEvent.getX(1);
int y = touchEvent.getY(1);
}
else if (event == TouchEvent.MOVE && touchEvent.isValid())
{
boolean result = scrollImage((touchEvent.getX(1) - _xTouch), (touchEvent.getY(1) - _yTouch));
_xTouch = touchEvent.getX(1);
_yTouch = touchEvent.getY(1);
//If scrolling occurred, consume the touch event.
if (result)
{
return true;
}
else
{
return false;
}
}
Thanks in advance.
:)
it was a misunderstanding.I was handling the touch event in multiple layers..like Field level,layout manager level and screen level.
So in particular case..it was being cosumed by manager.And i need the event to be cosumed by field.
It was a mistake in return value.