How do I get LocationManager.addNmeaListener() to work? - android-gps

Here are the relevant parts of my code:-
public class MainActivity extends Activity
implements SensorEventListener, OnNmeaMessageListener {
private LocationManager m_locationManager;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ac = this;
m_locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
}
...
protected void onResume() {
...
if (m_locationManager != null) {
m_gpsSensor = new SensorView(this);
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
m_locationManager.addNmeaListener(getMainExecutor(), this);
} else {
// no-op in later versions
m_locationManager.addNmeaListener(this);
}
m_gpsSensor.lineBreak("gps: ", "no messages yet");
} catch (Exception ignore) {
m_gpsSensor.lineBreak("gps: ", getString(R.string.permissiondenied));
}
topLayout.addView(m_gpsSensor);
}
}
#Override
public void onNmeaMessage(String message, long timestamp) {
long nanos = timestamp * 1000000;
if (nanos > m_gpsSensor.lastNanos + UPDATE_NANOS) {
m_gpsSensor.lastNanos = nanos;
m_gpsSensor.lineBreak("gps: ", message);
}
}
}
onNmeaMessage is never called (lastNanos is initialised to zero) and m_gpsSensor (a subclass of View) displays "no messages yet". My device does have GPS, and the app has permission to access it (otherwise it would display "Permission denied"), and GPS does work, because it can see satellites and get a fix with Satstat
I tried
addNmeaListener (OnNmeaMessageListener listener, Handler handler)
which doesn't work either. m_locationManager isn't null, because in that case it wouldn't display anything at all.
The device is a Samsung Galaxy S21 Ultra 5G running Android 12.
What am I doing wrong?

Apparently addNmeaListener doesn't start the GPS, and there doesn't seem to be an explicit call to do so. You have to call one of the requestLocationUpdates variants.

Related

OnResume() and OnSleep() called but not working properly

I'm a new Xamarin developper and I'm trying to build an app with Xamarin.Forms. Everything works properly on the app but I noticed a strange behaviour : be it on device or with the emulator, when I turn the power off one time then on again, OnSleep() and OnResume() methods work just fine.
But the problem is, when I repeat the same operation a second time, the application freezes and to unlock it I have to either close the app and open it again or go to menu where you can select other apps working in background (I don't know how it's called) and return on the app. I checked with breakpoints and those two methods are called on the first and second time. I tried removing everything from those methods but the problem persists. Does anyone know why it behaves like that ? Thank you.
In my App.xaml.cs :
protected override void OnStart()
{
if (isLoggedIn())
{
MainPage = new NavigationPage(new MDPage());
GeneralViewModel general = new GeneralViewModel();
general.checkUser();
}
else
{
MainPage = new NavigationPage(new LoginPage());
}
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
if (isLoggedIn()) //With a breakpoint, this is executed
{
MainPage.Navigation.PushAsync(new MDPage());
/*this is also executed but the second time,
*even if it is executed the application freezes*/
GeneralViewModel general = new GeneralViewModel();
general.checkUser();
}
else
{
MainPage = new NavigationPage(new LoginPage());
}
}
public bool isLoggedIn()
{
if (Settings.GeneralSettings != null && Settings.GeneralSettings != "")
{
return true;
}
else
{
return false;
}
}
The GeneralViewModel method I call (btw even without it the problem persists)
public void DoSomething()
{
ThreadPool.QueueUserWorkItem(o => checkUser());
}
public Task checkUser()
{
if (Settings.GeneralSettings != null && Settings.GeneralSettings != "")
{
DoSomething();
}
else
{
try
{
var data = Application.Current.MainPage;
Device.BeginInvokeOnMainThread(async () =>
{
await data.DisplayAlert("Information(s)", "Vous avez été déconnecté", "OK");
});
}
catch (Exception e)
{
Console.WriteLine("exception");
}
}
return default;
}
edit : I checked the console and it seems even though nothing appears on the screen, when I press a button on my frozen page the action the button should do happens but the screen doesn't change.

Turning Bluetooth Tethering On in Xamarin.Android

I'm currently trying to add some Bluetooth functionality to my app. I want to be able to change the Bluetooth Tethering on or off, as well as check its status.
I found the Java code on StackOverflow: How to check Bluetooth tethering status programmatically in Android
I have translated it into C#, but I don't seem to be able to get any result.
Regardless of the tethering setting, it always shows the toast with "Tethering:false", and the setBluetoothTethering doesn't change anything.
Any idea what I'm missing?
Here's my code:
[...]
try
{
Class classBluetoothPan = Class.ForName("android.bluetooth.BluetoothPan");
Method mBTPanConnect = classBluetoothPan.GetDeclaredMethod("connect", Class.FromType(typeof(BluetoothDevice)));
Constructor BTPanCtor = classBluetoothPan.GetDeclaredConstructor(Class.FromType(typeof(Context)), Class.FromType(typeof(IBluetoothProfileServiceListener)));
BTPanCtor.Accessible = true;
Java.Lang.Object BTSrvInstance = BTPanCtor.NewInstance(Activity, new BTPanServiceListener(Activity));
Method isTetheringOnMethod = classBluetoothPan.GetDeclaredMethod("isTetheringOn", null);
var isTetheringOn = isTetheringOnMethod.Invoke(BTSrvInstance);
Toast.MakeText(Activity, "Tethering:" + isTetheringOn, ToastLength.Short).Show();
Method setBluetoothTetheringMethod = classBluetoothPan.GetDeclaredMethod("setBluetoothTethering", new Class[1] { Class.FromType(typeof(bool)) });
setBluetoothTetheringMethod.Invoke(BTSrvInstance, true);
// tether = !tether;
}
catch (ClassNotFoundException e)
{
e.PrintStackTrace();
}
catch (Java.Lang.Exception e)
{
e.PrintStackTrace();
}
[...]
public class BTPanServiceListener : Java.Lang.Object, IBluetoothProfileServiceListener
{
private Activity _activity;
public BTPanServiceListener(Activity activity)
{
_activity = activity;
}
public void OnServiceConnected([GeneratedEnum] ProfileType profile, IBluetoothProfile proxy)
{
// throw new NotImplementedException();
}
public void OnServiceDisconnected([GeneratedEnum] ProfileType profile)
{
// throw new NotImplementedException();
}
}
I figured out how to enable Bluetooth tethering via setBluetoothTethering.
I wrote an entire blog about this
You can find the final code here
I assume that isTetheringOn works in the same way

Audio performance with Javafx for Android (MediaPlayer and NativeAudioService)

I have created, with JavaFX, a game on desktop that works fine (20000 Java lines. As it is a game, the Real Time constraint is important (response time of player's actions).
The final aim is to run this application with Android. I have almost finished to "transfer the Java code" from PC to Android, even if I have encountered some real time trouble. I think almost all of them are solved now.
For instance, I have minimized the CPU time (consumption) of Shape or Rectangle.intersect(node1, node2) calls that are used for detecting impacts between two mobiles. Thus, the real time has been divided by 3. Great!
For testing this Android version, I use Eclipse + Neon2, JavaFX, JavaFXports + gluon and my phone (Archos Diamond S).
But, for Android phones, I had a real time problem related to the sounds that are generated with MediaPlayer and NativeAudioSrvice.
Yet, I have followed this advice that suggests the synchronous mode:
javafxports how to call android native Media Player
1st question:
Does it exist an asynchronous mode with this Mediaplayer class?I think that would solve this latency problem?
In practice, I have tried the asynchronous solution ... without success: the real time problem due to the audio generation with MediaPlayer stays: an audio generation costs from 50 ms to 80 ms whereas the main cyclic processing runs each 110 ms. Each audio generation can interfer with the main processing execution.
And, in each periodic task (rate: 110 ms), I can play several sounds like that. And, in a trace, there was up to six sound activations that take (together) about 300 ms (against the 110 ms of the main cyclic task )
QUESTION:
How to improve the performance of NativeAudio class (especially, the method play() with its calls that create the real time problem: setDataSource(...), prepare() and start() )?
THE SOLUTION
The main processing must be a "synchronized" method to be sure that this complete processing will be run, without any audio interruption.
More, each complete processing for generating a sound is under a dedicated thread, defined with a Thread.MIN_PRIORITY priority.
Now, the main processing is run each 110 ms and, when it begins, it cannot be disturbed by any audio generation. The display is very "soft" (no more jerky moving).
There is just a minor problem: when an audio seDataSource(), a start() or a prepare() method has begun, it seems to be that the next main processing shall wait the end of the method before beginning (TBC)
I hope this solution could help another people. It is applicable in any case of audio generations with MediaPlayer.
JAVA code of the solution
The main processing is defined like that:
public static ***synchronized*** void mainProcessing() {
// the method handles the impacts, explosions, sounds, movings, ... , in other words almost the entiere game .. in a CRITICAL SECTION
}
/****************************************************/
In the NativeAudio class that implements "NativeAudioService":
#Override
public void play() {
if (bSon) {
Task<Void> taskSound = new Task<Void>() {
#Override
protected Void call() throws Exception {
generateSound();
return null;
}};
Thread threadSound = new Thread(taskSound);
threadSound.setPriority(Thread.MIN_PRIORITY);
threadSound.start();
}
}
/****************************************************/
private void generateSound() {
currentPosition = 0;
nbTask++;
noTask = nbTask;
try {
if (mediaPlayer != null) {
stop();
}
mediaPlayer = new MediaPlayer();
AssetFileDescriptor afd = FXActivity.getInstance().getAssets().openFd(audioFileName);
mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
mediaPlayer.setAudioStreamType(AudioManager.STREAM_RING);
float floatLevel = (float) audioLevel;
mediaPlayer.setVolume(floatLevel, floatLevel);
mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mediaPlayer) {
if (nbCyclesAudio >= 1) {
mediaPlayer.start();
nbCyclesAudio--;
} else {
mediaPlayer.stop();
mediaPlayer.release(); // for freeing the resource - useful for the phone codec
mediaPlayer = null;
}
}
});
mediaPlayer.prepare();
mediaPlayer.start();
nbCyclesAudio--;
} catch (IOException e) {
}
}
I've changed a little bit the implementation you mentioned, given that you have a bunch of short audio files to play, and that you want a very short time to play them on demand. Basically I'll create the AssetFileDescriptor for all the files once, and also I'll use the same single MediaPlayer instance all the time.
The design follows the pattern of the Charm Down library, so you need to keep the package names below.
EDIT
After the OP's feedback, I've changed the implementation to have one MediaPlayer for each audio file, so you can play any of them at any time.
Source Packages/Java:
package: com.gluonhq.charm.down.plugins
AudioService interface
public interface AudioService {
void addAudioName(String audioName);
void play(String audioName, double volume);
void stop(String audioName);
void pause(String audioName);
void resume(String audioName);
void release();
}
AudioServiceFactory class
public class AudioServiceFactory extends DefaultServiceFactory<AudioService> {
public AudioServiceFactory() {
super(AudioService.class);
}
}
Android/Java Packages
package: com.gluonhq.charm.down.plugins.android
AndroidAudioService class
public class AndroidAudioService implements AudioService {
private final Map<String, MediaPlayer> playList;
private final Map<String, Integer> positionList;
public AndroidAudioService() {
playList = new HashMap<>();
positionList = new HashMap<>();
}
#Override
public void addAudioName(String audioName) {
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setOnCompletionListener(m -> pause(audioName)); // don't call stop, allows reuse
try {
mediaPlayer.setDataSource(FXActivity.getInstance().getAssets().openFd(audioName));
mediaPlayer.setOnPreparedListener(mp -> {
System.out.println("Adding audio resource " + audioName);
playList.put(audioName, mp);
positionList.put(audioName, 0);
});
mediaPlayer.prepareAsync();
} catch (IOException ex) {
System.out.println("Error retrieving audio resource " + audioName + " " + ex);
}
}
#Override
public void play(String audioName, double volume) {
MediaPlayer mp = playList.get(audioName);
if (mp != null) {
if (positionList.get(audioName) > 0) {
positionList.put(audioName, 0);
mp.pause();
mp.seekTo(0);
}
mp.start();
}
}
#Override
public void stop(String audioName) {
MediaPlayer mp = playList.get(audioName);
if (mp != null) {
mp.stop();
}
}
#Override
public void pause(String audioName) {
MediaPlayer mp = playList.get(audioName);
if (mp != null) {
mp.pause();
positionList.put(audioName, mp.getCurrentPosition());
}
}
#Override
public void resume(String audioName) {
MediaPlayer mp = playList.get(audioName);
if (mp != null) {
mp.start();
mp.seekTo(positionList.get(audioName));
}
}
#Override
public void release() {
for (MediaPlayer mp : playList.values()) {
if (mp != null) {
mp.stop();
mp.release();
}
}
}
}
Sample
I've added five short audio files (from here), and added five buttons to my main view:
#Override
public void start(Stage primaryStage) throws Exception {
Button play1 = new Button("p1");
Button play2 = new Button("p2");
Button play3 = new Button("p3");
Button play4 = new Button("p4");
Button play5 = new Button("p5");
HBox hBox = new HBox(10, play1, play2, play3, play4, play5);
hBox.setAlignment(Pos.CENTER);
Services.get(AudioService.class).ifPresent(audio -> {
audio.addAudioName("beep28.mp3");
audio.addAudioName("beep36.mp3");
audio.addAudioName("beep37.mp3");
audio.addAudioName("beep39.mp3");
audio.addAudioName("beep50.mp3");
play1.setOnAction(e -> audio.play("beep28.mp3", 5));
play2.setOnAction(e -> audio.play("beep36.mp3", 5));
play3.setOnAction(e -> audio.play("beep37.mp3", 5));
play4.setOnAction(e -> audio.play("beep39.mp3", 5));
play5.setOnAction(e -> audio.play("beep50.mp3", 5));
});
Scene scene = new Scene(new StackPane(hBox), Screen.getPrimary().getVisualBounds().getWidth(),
Screen.getPrimary().getVisualBounds().getHeight());
primaryStage.setScene(scene);
primaryStage.show();
}
#Override
public void stop() throws Exception {
Services.get(AudioService.class).ifPresent(AudioService::release);
}
The prepare step takes place when the app is launched and the service is instanced, so when playing later on any of the audio files, there won't be any delay.
I haven't checked if there could be any memory issues when adding several media players with big audio files, as that wasn't the initial scenario. Maybe a cache strategy will help in this case (see CacheService in Gluon Charm Down).

Google Drive API implementation Xamarin Android

Our application should have the functionality to save Application files to Google Drive. Of course, using the local configured account.
From Android API i tried to figure out some clue. But android API with Xamarin implementation seems very tough for me.
I have installed Google Play Services- Drive from Xamarin Components but there are no examples listed from which we can refer the flow and functionality.
The basic steps (see the link below for full details):
Create GoogleApiClient with the Drive API and Scope
Try to connect (login) the GoogleApiClient
The first time you try to connect it will fail as the user has not selected a Google Account that should be used
Use StartResolutionForResult to handle this condition
When GoogleApiClient is connected
Request a Drive content (DriveContentsResult) to write the file contents to.
When the result is obtained, write data into the Drive content.
Set the metadata for the file
Create the Drive-based file with the Drive content
Note: This example assumes that you have Google Drive installed on your device/emulator and you have registered your app in Google's Developer API Console with the Google Drive API Enabled.
C# Example:
[Activity(Label = "DriveOpen", MainLauncher = true, Icon = "#mipmap/icon")]
public class MainActivity : Activity, GoogleApiClient.IConnectionCallbacks, IResultCallback, IDriveApiDriveContentsResult
{
const string TAG = "GDriveExample";
const int REQUEST_CODE_RESOLUTION = 3;
GoogleApiClient _googleApiClient;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.Main);
Button button = FindViewById<Button>(Resource.Id.myButton);
button.Click += delegate
{
if (_googleApiClient == null)
{
_googleApiClient = new GoogleApiClient.Builder(this)
.AddApi(DriveClass.API)
.AddScope(DriveClass.ScopeFile)
.AddConnectionCallbacks(this)
.AddOnConnectionFailedListener(onConnectionFailed)
.Build();
}
if (!_googleApiClient.IsConnected)
_googleApiClient.Connect();
};
}
protected void onConnectionFailed(ConnectionResult result)
{
Log.Info(TAG, "GoogleApiClient connection failed: " + result);
if (!result.HasResolution)
{
GoogleApiAvailability.Instance.GetErrorDialog(this, result.ErrorCode, 0).Show();
return;
}
try
{
result.StartResolutionForResult(this, REQUEST_CODE_RESOLUTION);
}
catch (IntentSender.SendIntentException e)
{
Log.Error(TAG, "Exception while starting resolution activity", e);
}
}
public void OnConnected(Bundle connectionHint)
{
Log.Info(TAG, "Client connected.");
DriveClass.DriveApi.NewDriveContents(_googleApiClient).SetResultCallback(this);
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_RESOLUTION)
{
switch (resultCode)
{
case Result.Ok:
_googleApiClient.Connect();
break;
case Result.Canceled:
Log.Error(TAG, "Unable to sign in, is app registered for Drive access in Google Dev Console?");
break;
case Result.FirstUser:
Log.Error(TAG, "Unable to sign in: RESULT_FIRST_USER");
break;
default:
Log.Error(TAG, "Should never be here: " + resultCode);
return;
}
}
}
void IResultCallback.OnResult(Java.Lang.Object result)
{
var contentResults = (result).JavaCast<IDriveApiDriveContentsResult>();
if (!contentResults.Status.IsSuccess) // handle the error
return;
Task.Run(() =>
{
var writer = new OutputStreamWriter(contentResults.DriveContents.OutputStream);
writer.Write("Stack Overflow");
writer.Close();
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.SetTitle("New Text File")
.SetMimeType("text/plain")
.Build();
DriveClass.DriveApi
.GetRootFolder(_googleApiClient)
.CreateFile(_googleApiClient, changeSet, contentResults.DriveContents);
});
}
public void OnConnectionSuspended(int cause)
{
throw new NotImplementedException();
}
public IDriveContents DriveContents
{
get
{
throw new NotImplementedException();
}
}
public Statuses Status
{
get
{
throw new NotImplementedException();
}
}
}
Ref: https://developers.google.com/drive/android/create-file

Xamarin Form and Bluetooth disconnect

Is there an event of a Android.Bluetooth class that is raised when the device is disconnected?
You have to set add a BluetoothGattCallback
public class MyGattCallback : BluetoothGattCallback
{
public override void OnConnectionStateChange(BluetoothGatt gatt, GattStatus status, ProfileState newState)
{
base.OnConnectionStateChange(gatt, status, newState);
if(newState == ProfileState.Disconnected)
{
// disconnected
}
}
}
And when you connect your device, you pass it:
BluetoothDevice device = ...;
var callback = new MyGattCallback();
device.ConnectGatt(Application.Context, false, callback);

Resources