How to bind to AccessibilityService in Mono (Xamarin) - c#

I've tried and got OnServiceConnected event in ServiceConnection class. But casting IBinder to my class (it inherits Binder, that inherits IBinder) returns null if I try to do that as "var myObject = IBinderObject as myBinderClass;" or casting error if I try to do that as "var myObject = (myBinderClass) IBinderObject;".
Please, any working example...
MainActivity code:
namespace MyApp.Droid
{
[Activity(Label = "MyApp", Icon = "#drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity
{
AccessibilityServiceBinder binder;
AccessibilityServiceConnection accessibilityServiceConnection;
bool isBound = false, isBoundAC = false, isConfigurationChange = false;
protected Intent ServiceIntent;
AccessibilityServiceClass ServiceClass;
protected override void OnStart()
{
base.OnStart();
if (!isBoundAC)
{
accessibilityServiceConnection = new AccessibilityServiceConnection(this);
isBoundAC = BindService(ServiceIntent, accessibilityServiceConnection, Bind.AutoCreate);
if (isBoundAC)
{
Toast.MakeText(this, "AccessibilityService is Bound", ToastLength.Long);
}
}
}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
ServiceIntent = new Intent(this, typeof(AccessibilityServiceClass));
ServiceIntent.SetPackage("android.accessibilityservice.AccessibilityService");
global::Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication(new App());
}
public class AccessibilityServiceConnection : Java.Lang.Object, IServiceConnection
{
MainActivity activity;
AccessibilityServiceBinder binder;
public AccessibilityServiceBinder Binder
{
get
{
return (AccessibilityServiceBinder)binder;
}
}
public AccessibilityServiceConnection(MainActivity activity)
{
this.activity = activity;
}
public void OnServiceConnected(ComponentName name, IBinder service)
{
var accessibilityServiceBinder = service as AccessibilityServiceBinder;
//accessibilityServiceBinder is always null here
if (accessibilityServiceBinder != null)
{
activity.binder = accessibilityServiceBinder;
activity.isBound = true;
this.binder = accessibilityServiceBinder;
}
}
public void OnServiceDisconnected(ComponentName name)
{
activity.isBound = false;
}
}
public override Java.Lang.Object OnRetainNonConfigurationInstance()
{
base.OnRetainNonConfigurationInstance();
isConfigurationChange = true;
return accessibilityServiceConnection;
}
}
}
Service code:
namespace MyApp.Droid
{
[Service(Label = "MyApp", Permission = Manifest.Permission.BindAccessibilityService), IntentFilter(new[] { "android.accessibilityservice.AccessibilityService" }), MetaData("android.accessibility-service", Resource = "#xml/accessibilityserviceconfig")]
public class AccessibilityServiceClass : Android.AccessibilityServices.AccessibilityService
{
public override void OnAccessibilityEvent(AccessibilityEvent e)
{
Toast.MakeText(this, "OnAccessibilityEvent", ToastLength.Short);
}
public override void OnInterrupt()
{
Toast.MakeText(this, "OnInterrupt", ToastLength.Short);
}
protected override void OnServiceConnected()
{
Toast.MakeText(this, "OnServiceConnected", ToastLength.Short);
}
}
public class AccessibilityServiceBinder : Binder
{
AccessibilityServiceClass service;
public AccessibilityServiceBinder(AccessibilityServiceClass service)
{
this.service = service;
}
public AccessibilityServiceClass GetAccessibilityService()
{
return service;
}
}
}

You aren't overriding the OnBind event of your service, thus the binder is null as you haven't provided an instance.
Add this to your service:
AccessibilityServiceBinder binder;
public override IBinder OnBind (Intent intent)
{
if(binder == null)
binder = new AccessibilityServiceBinder (this);
return binder;
}
For more info read the Xamarin docs on bound services, they're very well explained: https://developer.xamarin.com/guides/android/application_fundamentals/services/part_2_-_bound_services/
EDIT: Total wrong answer (I will delete it in some minutes). It seems it's not possible to bind to an Accessibility service. More info here: android bind to AccessibilityService

Related

Finish foreground service thread

I have a foreground service with a thread that is continuously checking data from a server.
So the service works well and it doesn't closes when I close the app, but when I start the app again the service creates another thread. Is posible abort the thread of the previous instance?
This is my MainActivity
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
public static Activity ActivityCurrent { get; private set; }
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Window.SetStatusBarColor(Android.Graphics.Color.Rgb(0, 120, 71));
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
System.Diagnostics.Debug.WriteLine("Iniciado");
LoadApplication(new App());
ActivityCurrent = this;
DependencyService.Get<IServicio>().Start();
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
This is my service class
[Service(ForegroundServiceType = Android.Content.PM.ForegroundService.TypeDataSync)]
public class Servicio : Service, IServicio
{
Uri link = new Uri(new Url().Dir + "/estado_alarma.php");
public override IBinder OnBind(Intent intent)
{
throw new NotImplementedException();
}
[return: GeneratedEnum]
Thread refrescar;
public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int starId)
{
if (intent.Action == "START_SERVICE")
{
System.Diagnostics.Debug.WriteLine("Se ha iniciado el servicio");
RegisterNotification();
}
else if (intent.Action == "STOP_SERVICE")
{
StopForeground(true);
StopSelfResult(starId);
}
return StartCommandResult.NotSticky;
}
private void RegisterNotification()
{
NotificationChannel channel = new NotificationChannel("ServicioChannel", "Demo de servicio", NotificationImportance.Max);
NotificationManager manager = (NotificationManager)MainActivity.ActivityCurrent.GetSystemService(Context.NotificationService);
manager.CreateNotificationChannel(channel);
Notification notification = new Notification.Builder(this, "ServicioChannel").SetContentTitle("Servicio Trabajando").SetOngoing(true).Build();
StartForeground(200, notification);
_ = IniciarServicio();
}
public void Start()
{
Intent startService = new Intent(MainActivity.ActivityCurrent, typeof(Servicio));
startService.SetAction("START_SERVICE");
MainActivity.ActivityCurrent.StartService(startService);
}
public void Stop()
{
Intent startService = new Intent(MainActivity.ActivityCurrent, typeof(Servicio));
startService.SetAction("STOP_SERVICE");
MainActivity.ActivityCurrent.StartService(startService);
}
public int IniciarServicio()
{
refrescar = new Thread(EsperaAlarmaAsync);
refrescar.Start();
return 1;
}
public async void EsperaAlarmaAsync()
{
WebClient cliente = new WebClient();
byte[] respuesta;
string resultado;
var a = true;
while (a)
{
respuesta = cliente.DownloadData(link);
resultado = System.Text.Encoding.ASCII.GetString(respuesta);
cliente.Dispose();
if (resultado == "Alerta")
{
setNotificationVolumeToMax();
Device.BeginInvokeOnMainThread(() =>
{
App.Current.MainPage = new Alerta();
var powerManager = (PowerManager)GetSystemService(PowerService);
var wakeLock = powerManager.NewWakeLock(WakeLockFlags.ScreenDim | WakeLockFlags.AcquireCausesWakeup, "Ejemplo");
wakeLock.Acquire();
Intent x = new Intent(MainActivity.ActivityCurrent.Intent);
StartActivity(x);
});
a = false;
}
Thread.Sleep(3000);
}
refrescar.Abort();
}
private void setNotificationVolumeToMax()
{
try
{
var audioManager = (AudioManager)GetSystemService(Context.AudioService);
int max_volume = audioManager.GetStreamMaxVolume(Android.Media.Stream.Music);
audioManager.SetStreamVolume(Android.Media.Stream.Music, max_volume, 0);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
When the server return 'Alerta' it opens the app

How to integrate a rabbitmq Client in a BackgroundService in a Xamarin Forms Android App?

Xamarin Forms App
I have two pages in my App: News and Announcements.
On these pages I want to display messages received from a rabbitmq Server.
I implemented a Service in Android that is executed after the login is completed. The communication between the Shared Code and the Android-Code is realized with Xamarin.MessagingCenter.
The Problem is that my rabbitmq Client currently doesn't receive any messages.
The Server is running on a VM and the App runs in an Emulator.
Code
Here is my Code
DataTransferTaskService in Android
[Service]
class DataTransferTaskService : Service
{
static User user = new User { Groups = new List<string>() { "Test","Test2" } };
CancellationTokenSource _cts;
public override IBinder OnBind(Intent intent)
{
return null;
}
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
// From shared code or in your PCL
_cts = new CancellationTokenSource();
PS();
return StartCommandResult.NotSticky;
}
void PS()
{
var factory = new ConnectionFactory() { HostName = "10.0.0.3", UserName = "test", Password = "test", Port = 5672 };
var connection = factory.CreateConnection();
var channel = connection.CreateModel();
channel.ExchangeDeclare(exchange: "Kastner", type: ExchangeType.Direct);
var queueName = channel.QueueDeclare().QueueName;
foreach (string g in user.Groups)
{
channel.QueueBind(queue: queueName,
exchange: "Kastner",
routingKey: g);
}
var consumer = new EventingBasicConsumer(channel);
System.Diagnostics.Debug.WriteLine("vor event");
consumer.Received += (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
var obj = JObject.Parse(message);
News n;
Announcement a;
System.Diagnostics.Debug.WriteLine("vor if");
if (obj.Properties().Select(p => p.Name).FirstOrDefault() == "NewsId")
{
n = JsonConvert.DeserializeObject<News>(message);
MessagingCenter.Send<object, News>(this, "NewsMessage", n);
}
else
{
a = JsonConvert.DeserializeObject<Announcement>(message);
MessagingCenter.Send<object, Announcement>(this, "AnnouncementMessage", a);
}
};
channel.BasicConsume(queue: queueName,
autoAck: true,
consumer: consumer);
}
}
MainActivity.cs in Android
[Activity(Label = "DA_MessageBrokerApp", Icon = "#mipmap/icon", Theme = "#style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize )]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(savedInstanceState);
FFImageLoading.Forms.Platform.CachedImageRenderer.Init(enableFastRenderer: true);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
CrossMediaManager.Current.Init(this);
LoadApplication(new App());
WireUpDataTransferTask();
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
void WireUpDataTransferTask()
{
MessagingCenter.Subscribe<NewsViewModel>(this, "StartDataTransferMessage", (sender) =>
{
var intent = new Intent(this, typeof(DataTransferTaskService));
StartService(intent);
});
}
}
NewsViewModel in Shared Code
public class NewsViewModel : BaseViewModel
{
static User user = new User { Groups = new List<string>() { "Test","Test2" } };
private News _selectedItem;
public ObservableCollection<News> Items { get; }
public Command LoadItemsCommand { get; }
public Command AddItemCommand { get; }
public Command<News> ItemTapped { get; }
public NewsViewModel()
{
Title = "News";
Items = new ObservableCollection<News>();
MessagingCenter.Subscribe<object, News>(this, "NewsMessage", async (sender, arg) =>
{
await Task.Run(() =>DataStoreNews.AddItemAsync(arg));
await ExecuteLoadItemsCommand();
});
MessagingCenter.Send<NewsViewModel>(this, "StartDataTransferMessage");
LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());
ItemTapped = new Command<News>(OnItemSelected);
}
void AddNewItem(News news)
{
if (!Items.Contains(news))
Items.Add(news);
}
async Task ExecuteLoadItemsCommand()
{
IsBusy = true;
try
{
Items.Clear();
var items = await DataStoreNews.GetItemsAsync(true);
foreach (var item in items)
{
Items.Add(item);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
public void OnAppearing()
{
IsBusy = true;
SelectedItem = null;
}
public News SelectedItem
{
get => _selectedItem;
set
{
SetProperty(ref _selectedItem, value);
OnItemSelected(value);
}
}
async void OnItemSelected(News item)
{
if (item == null)
return;
// This will push the ItemDetailPage onto the navigation stack
await Shell.Current.GoToAsync($"{nameof(NewsDetailPage)}?{nameof(NewsDetailViewModel.NewsId)}={item.NewsId}");
}
}
BaseViewModel in Shared Code
public class BaseViewModel : INotifyPropertyChanged
{
public IDataStore<News> DataStoreNews => DependencyService.Get<IDataStore<News>>();
public IDataStore<Announcement> DataStoreMessage => DependencyService.Get<IDataStore<Announcement>>();
bool isBusy = false;
public bool IsBusy
{
get { return isBusy; }
set { SetProperty(ref isBusy, value); }
}
string title = string.Empty;
public string Title
{
get { return title; }
set { SetProperty(ref title, value); }
}
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName] string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Problem
In the PS() method in the Service Class is the Method where I execute the Code of the rabbitmq Client. But the event that the client receives a message is never raised. I already tried the connection with annother Test Console App and there I received messages. Did I do something wrong with the Service or why is this not working?
Edit: I removed the usings of connection and channel in the DataTransferTaskService class, because I found out that these caused some problems, but it is still not working.

Format FilterAttribute result using Strategy Pattern in ASP.NET Core

I've created an action filter to return custom result in ActionFilterAttribute using context.result:
public class ResultApi : ActionFilterAttribute
{
private readonly ResposeFormat resposeFormat;
public ResultApi(ResposeFormat resposeFormat)
{
this.resposeFormat = resposeFormat;
}
public override void OnResultExecuting(ResultExecutingContext context)
{
resposeFormat.ContextResult(context);
base.OnResultExecuting(context);
}
}
I am using the strategy pattern to format the result:
public abstract class ResposeFormat
{
public abstract void ContextResult(ResultExecutingContext context);
}
And, here are different implementations of my Formatter:
OkResult :
public abstract class OkResultFormatter : ResposeFormat
{
public override void ContextResult(ResultExecutingContext context)
{
if (context.Result is OkResult okResult)
context.Result = new JsonResult(new ReturnResult(true, StatusCode.Success)) { StatusCode = okResult.StatusCode };
}
}
BadRequestResult :
public abstract class BadRquestObjectresultFormatter : ResposeFormat
{
public override void ContextResult(ResultExecutingContext context)
{
if (context.Result is BadRequestObjectResult badRequestObjectResult)
{
var message = badRequestObjectResult.Value.ToString();
if (badRequestObjectResult.Value is SerializableError errors)
{
var errorMessages = errors.SelectMany(p => (string[])p.Value).Distinct();
message = string.Join(" | ", errorMessages);
}
context.Result = new JsonResult(new ReturnResult(false, StatusCode.BadRequest, message)) { StatusCode = badRequestObjectResult.StatusCode };
}
}
}
When I pass the context to the resposeFormat.ContextResult(context);, I want it to find the type of request and use the OkResultFormatter or BadRquestObjectresultFormatter .
How can I do this?
You have to change things a little bit.
First of all, you have to add an abstract get-only property to your ResultTypeToFormat to determine which typeof ObjectResult should each implementation handle:
public abstract class ResponseFormatter
{
public abstract Type ResultTypeToFormat { get; }
public abstract void ContextResult(ResultExecutingContext context);
}
And accordingly, you have to change implementations like below:
public class OkResultFormatter : ResponseFormatter
{
public override Type ResultTypeToFormat => typeof(OkObjectResult);
public override void ContextResult(ResultExecutingContext context)
{
context.Result = new JsonResult(new ReturnResult(HttpStatusCode.OK));
}
}
public class BadRequestResultFormatter : ResponseFormatter
{
public override Type ResultTypeToFormat => typeof(BadRequestObjectResult);
public override void ContextResult(ResultExecutingContext context)
{
// Perform other bad request stuff here ...
context.Result = new JsonResult(new ReturnResult(HttpStatusCode.BadRequest));
}
}
With these changes, now you have to register all result formatters in your DI Container like this:
services.AddScoped<ResponseFormatter, OkResultFormatter>();
services.AddScoped<ResponseFormatter, BadRequestResultFormatter>();
Finally, you can format results dynamically in your Filter Attribute:
public class CustomResultFilterAttribute : ActionFilterAttribute
{
private readonly IEnumerable<ResponseFormatter> _responseFormatters;
public CustomResultFilterAttribute(IEnumerable<ResponseFormatter> responseFormatters)
{
_responseFormatters = responseFormatters;
}
public override void OnResultExecuting(ResultExecutingContext context)
{
Type resultType = context.Result.GetType();
ResponseFormatter appropriateFormatter = _responseFormatters
.Single(formatter => formatter.ResultTypeToFormat == resultType);
appropriateFormatter.ContextResult(context);
}
}
Here's also the sample project in Github: Sample project

The parameter came from broadcast receiver constructor is always nullable

I want to update my main activity UI in my foreground service with broadcast receiver. In BroadcastReceiver.OnReceive method,I passed the instance of main activity,but it is always nullable. How can I fix it? Many thanks in advance!
In my broadcast receiver:
public override void OnReceive(Context context, Intent intent)
{
Core.Music music = intent.GetParcelableExtra("selectedMusic") as Core.Music;
mMainActivity.mTxtSongName.Text = Core.MusicHelper.GetTitleAndAuthor(music.Title);
mMainActivity.mTxtAuthorName.Text = Core.MusicHelper.GetTitleAndAuthor(music.Author);
System.Threading.ThreadPool.QueueUserWorkItem(o =>
{
string imageUrl = music.Url.Replace(#"\", "").Replace("http", "https");
var task = Core.MusicHelper.GetSongPic(imageUrl, 35, 35);
var pic = task.Result;
if (pic != null)
{
mMainActivity.RunOnUiThread(() =>
{
mMainActivity.mImageViewSongPic.SetImageBitmap(pic);
});
}
});
}
In my service:
public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
{
Core.Music music = intent.GetParcelableExtra("selectedMusic") as Core.Music;
BroadcastStarted(music);
//To start the service
return StartCommandResult.NotSticky;
}
In my MainActivity.OnResume:
IntentFilter filter = new IntentFilter();
mReceive = new Service.Boradcast.MusicChangedBroadcastReceiver() { mMainActivity=this};
RegisterReceiver(mReceive,filter);
how do you defined mMainActivity ?
however the simplest example of a reference to MainActivity is to use static
define in your MainActivity OnCreate() method:
public static MainActivity Instance;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
...
Instance = this;
}
then call in your broadcast receiver
public override void OnReceive(Context context, Intent intent)
{
Core.Music music = intent.GetParcelableExtra("selectedMusic") as Core.Music;
MainActivity.Instance.mTxtSongName.Text = Core.MusicHelper.GetTitleAndAuthor(music.Title);
MainActivity.Instance.mTxtAuthorName.Text = Core.MusicHelper.GetTitleAndAuthor(music.Author);
System.Threading.ThreadPool.QueueUserWorkItem(o =>
{
string imageUrl = music.Url.Replace(#"\", "").Replace("http", "https");
var task = Core.MusicHelper.GetSongPic(imageUrl, 35, 35);
var pic = task.Result;
if (pic != null)
{
MainActivity.Instance.RunOnUiThread(() =>
{
MainActivity.Instance.mImageViewSongPic.SetImageBitmap(pic);
});
}
});
}
Or pass MainActivity as a parameter to the constructor :
in your MainActivity.OnResume():
IntentFilter filter = new IntentFilter();
mReceive = new Service.Boradcast.MusicChangedBroadcastReceiver(this);
RegisterReceiver(mReceive,filter);
then in your broadcast receiver:
[BroadcastReceiver]
public class MusicChangedBroadcastReceiver: BroadcastReceiver
{
public MainActivity mMainActivity;
public MusicChangedBroadcastReceiver()
{
}
public MusicChangedBroadcastReceiver(MainActivity activity)
{
this.mMainActivity= activity;
}
public override void OnReceive(Context context, Intent intent)
{
Core.Music music = intent.GetParcelableExtra("selectedMusic") as Core.Music;
mMainActivity.mTxtSongName.Text = Core.MusicHelper.GetTitleAndAuthor(music.Title);
mMainActivity.mTxtAuthorName.Text = Core.MusicHelper.GetTitleAndAuthor(music.Author);
System.Threading.ThreadPool.QueueUserWorkItem(o =>
{
string imageUrl = music.Url.Replace(#"\", "").Replace("http", "https");
var task = Core.MusicHelper.GetSongPic(imageUrl, 35, 35);
var pic = task.Result;
if (pic != null)
{
mMainActivity.RunOnUiThread(() =>
{
mMainActivity.mImageViewSongPic.SetImageBitmap(pic);
});
}
});
}
}

How to use signalr in Android

I am trying to integrate signalR in android app but no luck. I've been looking at various links but none of them provide proper information about implementation.
I've the following questions.
SignalR integration has to be done inside Service/Intent Service?
If we want to receive response via same calling method then how to get?
I've added three libraries i.e signalr android,signalr client and gson but unable to understand how code works, no proper documentation is available to understand the code.
Some of the questions asked but not much information
SignalR in Android Studio
Unable to implement p2p chat using SignalR in Android
If anyone experienced in signal for native apps, it would be very helpful for me.
Update
public class SignalRService extends Service {
private static final String TAG = "Service";
private HubConnection mHubConnection;
private HubProxy mHubProxy;
private Handler mHandler; // to display Toast message
private final IBinder mBinder = new LocalBinder();
private SharedPreferences sp;
#Override
public void onCreate() {
super.onCreate();
Utility.showLog(TAG, "Service Created");
sp = getSharedPreferences(Utility.SHARED_PREFS, MODE_PRIVATE);
mHandler = new Handler(Looper.myLooper());
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
int result = super.onStartCommand(intent, flags, startId);
startSignalR();
return result;
}
#Override
public IBinder onBind(Intent intent) {
startSignalR();
return mBinder;
}
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
public SignalRService getService() {
// Return this instance of SignalRService so clients can call public methods
return SignalRService.this;
}
}
/**
* method for clients (activities)
*/
public void sendMessage() {
String SERVER_METHOD_SEND = "iAmAvailable";
final String string = new String();
mHubProxy.invoke(new String(), SERVER_METHOD_SEND, sp.getString("user_id", null), sp.getString("pass", null), "TransMedic").done(new Action() {
#Override
public void run(Object o) throws Exception {
Utility.showLog(TAG, o.toString());
}
}).onError(new ErrorCallback() {
#Override
public void onError(Throwable throwable) {
}
});
}
private void startSignalR() {
Platform.loadPlatformComponent(new AndroidPlatformComponent());
String serverUrl = "http://transit.alwaysaware.org/signalr";
mHubConnection = new HubConnection(serverUrl);
String SERVER_HUB_CHAT = "ChatHub";
mHubProxy = mHubConnection.createHubProxy(SERVER_HUB_CHAT);
ClientTransport clientTransport = new ServerSentEventsTransport(mHubConnection.getLogger());
SignalRFuture<Void> signalRFuture = mHubConnection.start(clientTransport);
try {
signalRFuture.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
return;
}
sendMessage();
}
#Override
public void onDestroy() {
mHubConnection.stop();
super.onDestroy();
}
}
UPDATE 2018:
If you are using SignalR.net Core use this library otherwise you will get error on connection.
SERVER SIDE:
The following is my sample server-side code, you can pay attention to public void Send(string message) and public void SendChatMessage(string to, string message).
Server-side app: public void SendChatMessage(string to, string message)
Android client app: mHubProxy.invoke("SendChatMessage", receiverName, message);
Server-side app: public void Send(string message)
Android client app: mHubProxy.invoke("Send", message);
namespace SignalRDemo
{
public class ChatHub : Hub
{
private static ConcurrentDictionary<string, string> FromUsers = new ConcurrentDictionary<string, string>(); // <connectionId, userName>
private static ConcurrentDictionary<string, string> ToUsers = new ConcurrentDictionary<string, string>(); // <userName, connectionId>
private string userName = "";
public override Task OnConnected()
{
DoConnect();
Clients.AllExcept(Context.ConnectionId).broadcastMessage(new ChatMessage() { UserName = userName, Message = "I'm Online" });
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
if (stopCalled) // Client explicitly closed the connection
{
string id = Context.ConnectionId;
FromUsers.TryRemove(id, out userName);
ToUsers.TryRemove(userName, out id);
Clients.AllExcept(Context.ConnectionId).broadcastMessage(new ChatMessage() { UserName = userName, Message = "I'm Offline" });
}
else // Client timed out
{
// Do nothing here...
// FromUsers.TryGetValue(Context.ConnectionId, out userName);
// Clients.AllExcept(Context.ConnectionId).broadcastMessage(new ChatMessage() { UserName = userName, Message = "I'm Offline By TimeOut"});
}
return base.OnDisconnected(stopCalled);
}
public override Task OnReconnected()
{
DoConnect();
Clients.AllExcept(Context.ConnectionId).broadcastMessage(new ChatMessage() { UserName = userName, Message = "I'm Online Again" });
return base.OnReconnected();
}
private void DoConnect()
{
userName = Context.Request.Headers["User-Name"];
if (userName == null || userName.Length == 0)
{
userName = Context.QueryString["User-Name"]; // for javascript clients
}
FromUsers.TryAdd(Context.ConnectionId, userName);
String oldId; // for case: disconnected from Client
ToUsers.TryRemove(userName, out oldId);
ToUsers.TryAdd(userName, Context.ConnectionId);
}
public void Send(string message)
{
// Call the broadcastMessage method to update clients.
string fromUser;
FromUsers.TryGetValue(Context.ConnectionId, out fromUser);
Clients.AllExcept(Context.ConnectionId).broadcastMessage(new ChatMessage() { UserName = fromUser, Message = message });
}
public void SendChatMessage(string to, string message)
{
FromUsers.TryGetValue(Context.ConnectionId, out userName);
string receiver_ConnectionId;
ToUsers.TryGetValue(to, out receiver_ConnectionId);
if (receiver_ConnectionId != null && receiver_ConnectionId.Length > 0)
{
Clients.Client(receiver_ConnectionId).broadcastMessage(new ChatMessage() { UserName = userName, Message = message });
}
}
}
public class ChatMessage
{
public string UserName { get; set; }
public string Message { get; set; }
}
}
CLIENT SIDE:
If you have not read my answer at the following question:
SignalR integration in android studio
Then, here is my working basic code:
public class SignalRService extends Service {
private HubConnection mHubConnection;
private HubProxy mHubProxy;
private Handler mHandler; // to display Toast message
private final IBinder mBinder = new LocalBinder(); // Binder given to clients
public SignalRService() {
}
#Override
public void onCreate() {
super.onCreate();
mHandler = new Handler(Looper.getMainLooper());
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
int result = super.onStartCommand(intent, flags, startId);
startSignalR();
return result;
}
#Override
public void onDestroy() {
mHubConnection.stop();
super.onDestroy();
}
#Override
public IBinder onBind(Intent intent) {
// Return the communication channel to the service.
startSignalR();
return mBinder;
}
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
public SignalRService getService() {
// Return this instance of SignalRService so clients can call public methods
return SignalRService.this;
}
}
/**
* method for clients (activities)
*/
public void sendMessage(String message) {
String SERVER_METHOD_SEND = "Send";
mHubProxy.invoke(SERVER_METHOD_SEND, message);
}
private void startSignalR() {
Platform.loadPlatformComponent(new AndroidPlatformComponent());
Credentials credentials = new Credentials() {
#Override
public void prepareRequest(Request request) {
request.addHeader("User-Name", "BNK");
}
};
String serverUrl = "http://192.168.1.100";
mHubConnection = new HubConnection(serverUrl);
mHubConnection.setCredentials(credentials);
String SERVER_HUB_CHAT = "ChatHub";
mHubProxy = mHubConnection.createHubProxy(SERVER_HUB_CHAT);
ClientTransport clientTransport = new ServerSentEventsTransport(mHubConnection.getLogger());
SignalRFuture<Void> signalRFuture = mHubConnection.start(clientTransport);
try {
signalRFuture.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
return;
}
String HELLO_MSG = "Hello from Android!";
sendMessage(HELLO_MSG);
String CLIENT_METHOD_BROADAST_MESSAGE = "broadcastMessage";
mHubProxy.on(CLIENT_METHOD_BROADAST_MESSAGE,
new SubscriptionHandler1<CustomMessage>() {
#Override
public void run(final CustomMessage msg) {
final String finalMsg = msg.UserName + " says " + msg.Message;
// display Toast message
mHandler.post(new Runnable() {
#Override
public void run() {
Toast.makeText(getApplicationContext(), finalMsg, Toast.LENGTH_SHORT).show();
}
});
}
}
, CustomMessage.class);
}
}
Activity:
public class MainActivity extends AppCompatActivity {
private final Context mContext = this;
private SignalRService mService;
private boolean mBound = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent();
intent.setClass(mContext, SignalRService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
super.onStop();
}
public void sendMessage(View view) {
if (mBound) {
// Call a method from the SignalRService.
// However, if this call were something that might hang, then this request should
// occur in a separate thread to avoid slowing down the activity performance.
EditText editText = (EditText) findViewById(R.id.edit_message);
if (editText != null && editText.getText().length() > 0) {
String message = editText.getText().toString();
mService.sendMessage(message);
}
}
}
/**
* Defines callbacks for service binding, passed to bindService()
*/
private final ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to SignalRService, cast the IBinder and get SignalRService instance
SignalRService.LocalBinder binder = (SignalRService.LocalBinder) service;
mService = binder.getService();
mBound = true;
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
CustomMessage Class:
public class CustomMessage {
public String UserName;
public String Message;
}
You can also see my sample client project at this GitHub link
UPDATE FOR RESPONSE FROM INVOKE:
I have just added new sample methods:
Server side:
public string iAmAvailable(string username, string password, string message)
{
return "BNK Response for testing Android INVOKE";
}
Client side:
mHubProxy.invoke(String.class, "iAmAvailable", "username", "password", "TransMedic").done(new Action<String>() {
#Override
public void run(String s) throws Exception {
Log.w("SimpleSignalR", s);
}
}).onError(new ErrorCallback() {
#Override
public void onError(Throwable throwable) {
Log.e("SimpleSignalR", throwable.toString());
}
});
And here is the screenshot:
This work for me : Full source Android (Client) & Server GitHub
Server Slide If one argument must use this interface SubscriptionHandler1 if two argument must use this interfaceSubscriptionHandler2 ,...
Sample for two argument like :
Server slide :
using Microsoft.AspNet.SignalR;
namespace SignalRChat
{
public class ChatHub : Hub
{
public void Send(string name, string message)
{
// Two argument must use this interfaceSubscriptionHandler2 .
Clients.All.broadcastMessage(name, message);
}
}
}
Client slide :
mHubProxy.on(CLIENT_METHOD_BROADAST_MESSAGE,
new SubscriptionHandler2<String, String>() {
#Override
public void run(final String name,final String msg) {
final String finalMsg = msg.toString();
// display Toast message
mHandler.post(new Runnable() {
#Override
public void run() {
Toast.makeText(getApplicationContext(), finalMsg, Toast.LENGTH_SHORT).show();
}
});
}
}
, String.class,String.class);
For catch all message can use this :
mHubConnection.received(new MessageReceivedHandler() {
#Override
public void onMessageReceived(final JsonElement json) {
Log.e("onMessageReceived ", json.toString());
mHandler.post(new Runnable() {
#Override
public void run() {
Toast.makeText(getApplicationContext(), json.toString(), Toast.LENGTH_SHORT).show();
}
});
}
});
The SignalR team recently released a Java client for ASP.NET Core SignalR. Here is a link to getting started docs https://learn.microsoft.com/en-us/aspnet/core/signalr/java-client?view=aspnetcore-2.2
do this tutorial step by step :
https://learn.microsoft.com/en-us/aspnet/core/tutorials/signalr?tabs=visual-studio-mac&view=aspnetcore-5.0
1.According above tutorial publish your chat server to favorite host
2.add this dependency to your android sample:
implementation 'com.microsoft.signalr:signalr:3.0.0'
3.add these permission to manifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
4.below code is MainActivity.class:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
HubConnection hubConnection =
HubConnectionBuilder.create("https://your_chat_server_url/chatHub").build();
TextView textView = (TextView)findViewById(R.id.tvMain);
ListView listView = (ListView)findViewById(R.id.lvMessages);
Button sendButton = (Button)findViewById(R.id.bSend);
EditText editText = (EditText)findViewById(R.id.etMessageText);
List<String> messageList = new ArrayList<String>();
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(MainActivity.this,
android.R.layout.simple_list_item_1, messageList);
listView.setAdapter(arrayAdapter);
hubConnection.on("ReceiveMessage", (user, message)-> {
runOnUiThread(new Runnable() {
#Override
public void run() {
arrayAdapter.add( user + " : " + message);
arrayAdapter.notifyDataSetChanged();
}
});
}, String.class,String.class);
sendButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String message = editText.getText().toString();
String user = "SAEID";
editText.setText("");
try {
hubConnection.send("SendMessage", user,message);
} catch (Exception e) {
e.printStackTrace();
}
}
});
new HubConnectionTask().execute(hubConnection);
}
static class HubConnectionTask extends AsyncTask<HubConnection, Void, Void>{
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected Void doInBackground(HubConnection... hubConnections) {
HubConnection hubConnection = hubConnections[0];
hubConnection.start().blockingAwait();
return null;
}
}
}
5.below code is activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:orientation="vertical" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/tvMain" />
<ListView
android:layout_height="0dp"
android:layout_weight="1"
android:layout_width="fill_parent"
android:id="#+id/lvMessages"
android:transcriptMode="alwaysScroll">
</ListView>
<EditText
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:id="#+id/etMessageText"
android:hint="Enter Message" />
<Button
android:text="Send"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/bSend" />
</LinearLayout>
For those who are implementing signalR client in android and the given answer here doesn't help in receiving the messages can check out this answer by rejnev.
The answer implements a different method connection.received() which is able to receive message callbacks from the server in my case.

Categories