I'm working on an Android app that communicates with a Cast receiver app.
Connecting to the app works (I can see the app appear on the tv), but I'm having difficulties getting the custom channel to work.
In the onCreate of my Activity I get the CastContext and add my SessionManagerLister.
mCastContext = CastContext.getSharedInstance(this);
mCastContext.getSessionManager().addSessionManagerListener(getSessionManagerListener(), CastSession.class);
getSessionManagerListener() returns the listener where I register my MessageReceivedCallback:
private SessionManagerListener<CastSession> getSessionManagerListener()
{
return new SessionManagerListener<CastSession>()
{
#Override
public void onSessionStarted(CastSession castSession, String s)
{
try
{
castSession.setMessageReceivedCallbacks("urn:x-cast:be.myappname.player.cast.v1", new Cast.MessageReceivedCallback()
{
#Override
public void onMessageReceived(CastDevice castDevice, String s, String s1)
{
System.out.println("never reaches this callback");
}
});
}
catch (IOException e)
{
e.printStackTrace();
}
}
... other methods omitted ...
}
}
When I tap the Toolbar cast button I can select a device, which triggers the onSessionStarted in the SessionManagerListener (this also starts the receiver app on the tv). I then add the MessageReceivedCallback, but its callback never gets called.
Inspecting my Cast device in Chrome does show the data I'm expecting to receive, it just never seems to reach my Android code.
cast_receiver.js:67 [667.202s] [cast.receiver.IpcChannel] IPC message
[667.202s] [cast.receiver.IpcChannel] IPC message sent: {"namespace":"urn:x-cast:be.myappname.player.cast.v1","senderId":"7c442884-74e6-a388-243c-58b4ab3a4527.3471:com.google.sample.cast.refplayer.tutorial-512","data":"{\"type\":\"login request\"}"}
A colleague is working on the iOS app and that one does receive the callback.
Try the following in onSessionStarted
CastContext cc = CastContext.getSharedInstance(this);
SessionManager sm = cc.getSessionManager();
if (sm != null) {
CastSession cs = sm.getCurrentCastSession();
if (cs != null) {
try {
MyCastChannel mcc = new MyCastChannel();
cs.setMessageReceivedCallbacks("urn:x-cast:be.myappname.player.cast.v1",mcc);
}
catch (IOException e) {
}
}
}
public class MyCastChannel implements Cast.MessageReceivedCallback
{
#Override
public void onMessageReceived(CastDevice castDevice, String namespace, String message)
{
// do your thing
}
}
I had the same problem, this is how I managed to get the message to be sent:
context.sendCustomMessage(namespace, undefined, JSON.stringify({
"a": "b"
}));
This is the javascript on the receiver side. So you need the "undefined" param and also use JSON.stringify(), otherwise the message gets silently dropped.
The undefined means "send to all", but you should probably specify sender-id there.
This is in the v3 API.
In my case, it was more subtle.
The callback worked absolutely fine when the cast session was initiated for the first time. When the user presses the cast button the receiver is registered for the message callback.
override fun onSessionStarted(castSession: CastSession?, p1: String?) {
liveViewModel.requestPause()
castSession?.let {
setCastChannelReceiver(it, this#myActivity)
loadRemoteMedia(it, buildChromeCastInfo())
}
}
fun setCastChannelReceiver(castSession: CastSession?, receiver: CastMessageReceiver) {
castSession?.let {
castChannel.addReceiver(receiver, castSession)
it.setMessageReceivedCallbacks(castChannel.nameSpace, castChannel)
}
}
Although when the user use to kill the Activity which initiated the cast session and then after traversing other parts of app use to again visit the Activity, the callback failed to work.
Remember, when the user visits the Activity for the second time, the CastSession is already connected. As a result the onSessionStarted(castSession: CastSession, p1: String) method is never called.
I was under the assumption that once the receiver has been registered for the session, it need not be registered again. But still for some reason the callback never worked.
As a final resort, just to be assured I re-registered the receiver in the OnCreate() of the Activity.
override fun onCreate(out:Bundle){
....
setCastChannelReceiver(castSession, receiver)
....
}
fun setCastChannelReceiver(castSession: CastSession?, receiver: CastMessageReceiver) {
castSession?.let {
castChannel.addReceiver(receiver, castSession)
it.setMessageReceivedCallbacks(castChannel.nameSpace, castChannel)
}
}
And it worked!!
NOTE: For me, the communication between the Sender(Android App) and Cast Receiver only occurred when the string messages were in JSON format.
Related
I have created a custom document provider for Android using this code as a base.
https://learn.microsoft.com/en-us/samples/xamarin/monodroid-samples/storageprovider/
This allows for a new drive to be mapped onto the documents folder when browsing/saving documents.
If there is an exception due to a password timeout for example, I would like to pop back up the existing app so the users can entered their credentials again to log in.
Is this possible? As an example of what I am looking for, if the QueryRoots failed with a particular exception, could I run something to pop back up the app interface here?
public override ICursor QueryRoots(string[] projection)
{
Log.Verbose(TAG, "queryRoots");
var result = new MatrixCursor(ResolveRootProjection(projection));
try
{
if (!IsUserLoggedIn())
{
return result;
}
MatrixCursor.RowBuilder row = result.NewRow();
... other init code here
}
catch (Exception ex)
{
if (ex.Message == "NoSessionException")
{
// LOGIC TO BRING BACK APP TO LOG IN AGAIN HERE...
}
}
return result;
}
I make a sample code about how to lauch the app again for your reference. You could put Launch method in catch statement.
In Xamarin.Forms, you could use Dependency service to start the app with package name.
Create a interface:
public interface IDpendencyService
{
Task<bool> Launch(string stringUri);
}
Implemention of Android:
public class DependencyImplementation : Activity, IDpendencyService
{
public Task<bool> Launch(string stringUri)
{
Intent intent = Android.App.Application.Context.PackageManager.GetLaunchIntentForPackage(stringUri);
if (intent != null)
{
intent.AddFlags(ActivityFlags.NewTask);
Forms.Context.StartActivity(intent);
return Task.FromResult(true);
}
else
{
return Task.FromResult(true);
}
}
}
Register in MainActivity:
DependencyService.Register<IDpendencyService, DependencyImplementation>();
I use a Button event to invoke. You could try to invoke in catch.
DependencyService.Get<IDpendencyService>().Launch("com.companyname.xamarindemo");
Screenshot: I have a button on Page21. When i click the button, it would reload the app and pop back up the existing app.
I was trying to add sip incoming calls with linphone sdk, The registration is successful and I can make out going calls and the call status is logging as expected, but I am not able to receive incoming calls. I am using intent service to handle connection.
Here is my code:
protected void onHandleIntent(Intent intent) {
String sipAddress = intent.getStringExtra("address");
String password = intent.getStringExtra("password");
final LinphoneCoreFactory lcFactory = LinphoneCoreFactory.instance();
// First instantiate the core Linphone object given only a listener.
// The listener will react to events in Linphone core.
try {
lc = lcFactory.createLinphoneCore(new LinphoneCoreListenerBase() {
#Override
public void callState(LinphoneCore lc, LinphoneCall call, LinphoneCall.State state, String message) {
super.callState(lc, call, state, message);
Log.i(TAG, "callState: ");
}
}, getApplication());
} catch (LinphoneCoreException e) {
e.printStackTrace();
}
lc.setUserAgent("Test app", "1.0");
try {
LinphoneAddress address = lcFactory.createLinphoneAddress(sipAddress);
String username = address.getUserName();
String domain = address.getDomain();
if (password != null) {
lc.addAuthInfo(lcFactory.createAuthInfo(username, password, null, domain));
}
// create proxy config
LinphoneProxyConfig proxyCfg = lc.createProxyConfig(sipAddress, domain, null, true);
proxyCfg.setExpires(2000);
lc.addProxyConfig(proxyCfg); // add it to linphone
lc.setDefaultProxyConfig(proxyCfg);
running = true;
while (running) {
lc.iterate(); // first iterate initiates registration
sleep(20);
}
} catch (LinphoneCoreException e) {
e.printStackTrace();
}
}
What is wrong with my code?
As the IntentService document (https://developer.android.com/reference/android/app/IntentService) stated:
the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.
I think you should not put the listener in an IntentService. Instead, put it in a long running Service so that the listener can actually keep staying there to receive events.
I'm working on a project that improves Automation Test for Android's App. What I want to do is very "easy": I have this very simple SIP Client with a basic UI and developed just reading the API guides on the android developer website (https://developer.android.com/guide/topics/connectivity/sip.html) that receives and makes SIP calls.
I need to control remotely this app from my PC, connected at the same local network or the same wifi, by sending commands or similar (without interact with the phone) to the app itslef running normally on my phone.For a specific example I posted the method initiateCall() that calls sipAddress(in the app, sipAddress is taken from a Text Box), what I want to do is:
Starting the app on my phone
calling the method initiateCall() from my pc giving a sipAddress as a parameter (I must not use the UI from the app running, that's why I need to give the sipAddress)
check if an outgoing call starts from the app running on my phone
I thought that the solution must be something about web-services,but I don't have any better ideas and i don't know how to start and where to start solving this problem,that's why i need you help.
public void initiateCall() {
try {
SipAudioCall.Listener listener = new SipAudioCall.Listener() {
// set up the listener for outgoing calls
#Override
public void onCallEstablished(SipAudioCall call) {
call.startAudio();
call.setSpeakerMode(true);
updateStatus(call, 2);
}
#Override
public void onCallEnded(SipAudioCall call) {
updateStatus("Call End");
}
};
call = manager.makeAudioCall(me.getUriString(), sipAddress,
listener, 30);
} catch (Exception e) {
Log.i("WalkieTalkieActivity/InitiateCall",
"Error when trying to close manager.", e);
if (me != null) {
try {
manager.close(me.getUriString());
} catch (Exception ee) {
Log.i("WalkieTalkieActivity/InitiateCall",
"Error when trying to close manager.", ee);
ee.printStackTrace();
}
}
if (call != null) {
call.close();
}
}
}
You could do it REST API style. You would need to set up a minimalistic webserver.
If you access for example the url phoneip/ctrl/makecall?number=yournumber a serverside method us called if set up correctly. Then you can call you method and use the GET or POST variables as arguments.
You would have to look into Java Webserver Libraries/Frameworks. You can pick a lightweight one for that purpose. For example this one.
You could then also add security features (authentification to protect it) quite easily.
Example with sparkjava
import static spark.Spark.*;
....
get("/ctrl/makecall", (request, response) -> {
String phonenum = request.queryParams("number"); //may not be accurate; you have to determine the GET variable called "number" in that case; you can rename it; see docs!!!
//call your method with proper arguments
});
I am working on Xamarin Android Application.Before proceed to my next fragment I want to check Internet Connection and inform user about it ? How can i implement that ?And how to refresh whole fragment after user switch-on the internet?
Any advice or suggestion will be appreciated !
To get the network status you could use the following method in your activity:
public bool IsOnline()
{
var cm = (ConnectivityManager)GetSystemService(ConnectivityService);
return cm.ActiveNetworkInfo == null ? false : cm.ActiveNetworkInfo.IsConnected;
}
If I understood you correctly from this sentence: And how to refresh whole fragment after user switch-on the internet, You want to detect, whenever any changes in the connection status happens, Therefore you absolutely need to use broadcast receivers.
First of all you should implement a broadcast receiver with a simple Event named ConnectionStatusChanged as follows:
[BroadcastReceiver()]
public class NetworkStatusBroadcastReceiver : BroadcastReceiver
{
public event EventHandler ConnectionStatusChanged;
public override void OnReceive(Context context, Intent intent)
{
if (ConnectionStatusChanged != null)
ConnectionStatusChanged(this, EventArgs.Empty);
}
}
Then in your activity (in OnCreate() method for example, It doesn't matter) create an instance of that receiver and register it:
var _broadcastReceiver = new NetworkStatusBroadcastReceiver();
_broadcastReceiver.ConnectionStatusChanged += OnNetworkStatusChanged;
Application.Context.RegisterReceiver(_broadcastReceiver,
new IntentFilter(ConnectivityManager.ConnectivityAction));
Here is the body of the event handler:
private void OnNetworkStatusChanged(object sender, EventArgs e)
{
if(IsOnline()){
Toast.MakeText(this, "Network Activated", ToastLength.Short).Show();
// refresh content fragment.
}
}
To cut the long story short, NetworkStatusBroadcastReceiver receives any change in the network status of the device and invokes the ConnectionStatusChanged (When user enables data traffic or WiFi connection), Then you catch that event and check for network status using IsOnline() method. Very simple.
You can use the MVVMCross plugin : Connectivity
It wil expose a boolean
/// <summary>
/// Gets if there is an active internet connection
/// </summary>
bool IsConnected { get; }
and a delegate on change state
/// <summary>
/// Event handler when connection changes
/// </summary>
event ConnectivityChangedEventHandler ConnectivityChanged;
Try this :
NetworkStatus internetStatus = Reachability.InternetConnectionStatus();
if(!Reachability.IsHostReachable("http://google.com")) {
// Put alternative content/message here
}
else
{
// Put Internet Required Code here
}
When a user changes his/her privacy settings through AppOps (e.g. denying an application access to phone contacts), AppOpsManager sends to anyone who listens what the users have changed (i.e. the package name and the operation (e.g. Read contacts)).
So I wrote a listener to do so. However, we the user make only one change, I receive too many duplicate events (e.g. 10 events that the user decided to deny Angry Bird access to his/her location) and then the app crashes.
Here is my code to register listners for each pair of package & operation:
public void startWatchingOperations(AppOpsManager appOps, List<AppOpsManager.PackageOps> opsforapps) {
SharedPreferences myAppListnerPreferences = getSharedPreferences(APP_OPS_PREFERENCES, Activity.MODE_PRIVATE);
for (AppOpsManager.PackageOps o:opsforapps) {
List<OpEntry> opEntry = o.getOps();
//if I already assigned a listener to this pari of package & operation, then skip
if (myAppListnerPreferences.getBoolean(o.getPackageName(), false)==false) {
for (OpEntry entry:opEntry) {
//for each pair of package & operation, assign a new listener
ChangePrivacySettingsListener opsListner = new ChangePrivacySettingsListener(getApplicationContext());
appOps.startWatchingMode(entry.getOp(),o.getPackageName(),opsListner);
}
myAppListnerPreferences.edit().putBoolean(o.getPackageName(), true).apply();
}
}
}
Here is a snippet of the listener
public class ChangePrivacySettingsListener implements AppOpsManager.Callback {
public void opChanged(int op, String packageName) {
AppOpsManager appOps= (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
PackageManager pkg = context.getPackageManager();
try {
//this is an object to store the event: package name,
// the operation that has been changed, & time stamp
PrivacySetting privacySetting = new PrivacySetting();
privacySetting.setPackageName(packageName);
privacySetting.setOperation(OPERATIONS_STRINGS[op]);
privacySetting.setDecisionTime(Calendar.getInstance(TimeZone.getDefault()).getTimeInMillis());
privacySetting.setUserId(userId);
} catch (NameNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Her is the part of AppOpsManager.java that allows me to listen to user's changes.
public class AppOpsManager {
final HashMap<Callback, IAppOpsCallback> mModeWatchers
= new HashMap<Callback, IAppOpsCallback>();
public void startWatchingMode(int op, String packageName, final Callback callback) {
synchronized (mModeWatchers) {
IAppOpsCallback cb = mModeWatchers.get(callback);
if (cb == null) {
cb = new IAppOpsCallback.Stub() {
public void opChanged(int op, String packageName) {
callback.opChanged(op, packageName);
}
};
mModeWatchers.put(callback, cb);
}
try {
mService.startWatchingMode(op, packageName, cb);
} catch (RemoteException e) {
}
}
}
I double checked to ensure that I've never assigned more than one listener to each pair of package & operation.
I would appreciate hints about potential causes.
Here is a link to AppOpsManager.java
Try moving the deceleration of ChangePrivacySettingsListener opsListner to be out side of the for block:
public void startWatchingOperations(AppOpsManager appOps, List<AppOpsManager.PackageOps> opsforapps) {
ChangePrivacySettingsListener opsListner;
SharedPreferences myAppListnerPreferences = getSharedPreferences(APP_OPS_PREFERENCES, Activity.MODE_PRIVATE);
for (AppOpsManager.PackageOps o:opsforapps) {
List<OpEntry> opEntry = o.getOps();
//if I already assigned a listener to this pari of package & operation, then skip
if (myAppListnerPreferences.getBoolean(o.getPackageName(), false)==false) {
for (OpEntry entry:opEntry) {
//for each pair of package & operation, assign a new listener
opsListner = new ChangePrivacySettingsListener(getApplicationContext());
appOps.startWatchingMode(entry.getOp(),o.getPackageName(),opsListner);
}
myAppListnerPreferences.edit().putBoolean(o.getPackageName(), true).apply();
}
}
}
And please let me know what happened?
Just in case this is helpful to someone, up to at least Android Oreo, calling AppOpsManager.startWatchingMode(op, packageName, callback) will cause callback to be invoked when the setting is changed (1) for the op with any package, AND (2) for any AppOps setting changes with packageName. This can be seen from the AppOpsService.java source, particularly AppOpsService.startWatchingMode() which registers the callback, AppOpsService.setMode() which calls the callback when the AppOps setting is changed.
For example, if you register a callback with startWatchingMode(appOps1, package1, callback) and startWatchingMode(appOps2, package1, callback),
when there is a change in the setting for appOps3 for package1, the callback will be called twice since you have registered for package1 two times. If there is a change in appOps1 for package1, the callback will be invoked 3 times, because you have registered once for appOps1, and twice for package1.
The solution is to register either for the set of AppOps you are interested in (without duplications), with the packageName parameter set to null, or register for the set of packages you are interested in, with op parameter set to AppOpsManager.OP_NONE.
Also you need to ensure that all listeners are unregistered (e.g. in onDestroy of your activity) using stopWatchingMode. Otherwise, the callback entries will accumulate across Activity lifecycles (until the app is terminated) and you will start getting duplicates. This also means that you should keep references to all the listeners created.