How to handle GoogleApiClient instance upon Activity reconnection in Chromecast App? - android

I'm writing a simple app to send public photos from Dropbox public folder to Chromecast.
Instead of CastCompanion library I decided to write my own stuff to understand better the API.
According to Google Guidelines:
if the sender application becomes disconnected from the media route, such as when the user or the operating system kills the application without the user first disconnecting from the Cast device, then the application must restore the session with the receiver when the sender application starts again.
It seems to me that the same solution should apply to Activity recreation upon orientation change since it recreates the Activity from scratch.
My first question: Is my assumption correct? Both scenarios, orientation change and system kill, may use the same solution?
Given this assumption I wrote some code to restore session upon Activity restoration.
I'm considering the orientation change scenario, when Activity is recreated from scratch and I am supposed to restore route Id, Session Id and try to reconnect (I'm storing and retrieving both values from shared preferences).
I've been testing with and it's working fine.
That's what I do (based on Google Sender Guidelines code):
After discovering the ongoing Route Id and find the cast device I call this method:
private void connectToDevice(CastDevice castDevice) {
Log.d(TAG, "connecting to " + castDevice);
Cast.CastOptions.Builder apiOptionsBuilder = Cast.CastOptions
.builder(castDevice, new CastListener());
Log.d(TAG, "apiClient is null ? " + (apiClient == null));
apiClient = new GoogleApiClient.Builder(this)
.addApi(Cast.API, apiOptionsBuilder.build())
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
Log.d(TAG, "apiClient connected? " + apiClient.isConnected());
Log.d(TAG, "apiClient connecting? " + apiClient.isConnecting());
apiClient.connect();
}
private class CastListener extends Cast.Listener {
#Override
public void onApplicationStatusChanged() {
if (apiClient != null) {
Log.d(TAG, "callback => " + this);
}
}
#Override
public void onVolumeChanged() {
}
#Override
public void onApplicationDisconnected(int errorCode) {
teardown();
}
}
After this method I call Cast.CastApi.joinApplication if I recognize a reconnection.
But once reconnected to Chromecast the log of onApplicationStatusChanged prints one different instance for every phone's rotation. E.g: if I rotate phone 3 times the log prints 3 times with 3 different pointer addresses. That makes me believe it is internally holding all callbacks instances.
How am I supposed to handle this situation since the Activity is being recreated and I need to create another instance of GoogleApiClient keeping the session?
Full source:
https://github.com/ivan-aguirre/chromecast_samples/blob/master/DropboxCast/app/src/main/java/com/dropboxcast/dropboxcast/MainActivity.java

IMHO, I believe the proper way (or at least a better way) to approach this is one of the following:
if you have only one activity and that is all you care about, then use a fragment that persists across configuration changes and put the stuff that you want to persist seamlessly, there. This way, rotation of the phone is not going to cause any disruption in your cast related stuff.
if you have more than a single activity, think about creating an object that lasts across all your activities and put the cast stuff there and then ask that object for the instance of CastApi whenever needed, etc.
In your case, do you really get disconnected when you rotate the phone? Since you are setting up a whole new connection, you might want to disconnect yourself first when configuration changes (assuming you don't want to go with my earlier proposed (1) or (2)).

Related

How to change database value if user's internet is switched off

For the past few days i've been trying to show the online/offline status of a user.. For this i have a register activity where they register and their info gets saved in firebase and if they exit an activity i have overriden its onstop method and made the value to set to offline... but if the user suddenly loses internet connection it still shows online.. i cant change it to offline because internet is needed to make a change in the database and the use doesn't have internet... SO how do i set the database value to offline... i googled quite some stuff about this but didnt find anything... Can anyone please help me out please
My code
#Override
protected void onStart() {
super.onStart();
fetchData();
// mDatabaseReference.child("UserData").child(UID).child("Online").setValue("True");
}
#Override
protected void onStop() {
super.onStop();
fetchData();
// mDatabaseReference.child("UserData").child(UID).child("Online").setValue(false);
}
What you're trying to do is known as a presence system. The Firebase Database has a special API to allow this: onDisconnect(). When you attach a handler to onDisconnect(), the write operation you specify will be executed on the server when that server detects that the client has disconnected.
From the documentation on managing presence:
Here is a simple example of writing data upon disconnection by using the onDisconnect primitive:
DatabaseRef presenceRef = FirebaseDatabase.getInstance().getReference("disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnect().setValue("I disconnected!");
In your case this could be as simple as:
protected void onStart() {
super.onStart();
fetchData();
DatabaseReference onlineRef = mDatabaseReference.child("UserData").child(UID).child("Online");
onlineRef.setValue("True");
onlineRef.onDisconnect().setValue("False");
}
Note that this will work in simple cases, but will start to have problems for example when your connection toggles rapidly. In that case it may take the server longer to detect that the client disappears (since this may depends on the socket timing out) than it takes the client to reconnect, resulting in an invalid False.
To handle these situations better, check out the sample presence system in the documentation, which has more elaborate handling of edge cases.

How many mayLaunchUrl we can run at a time?

I am trying to utilize ChromeCustomTabs into our project. I ran into several issue when I used mayLaunchUrl. I checked the code Google has on the github. I simply set up an button to test the mayLaunchURL (prerender feature), when I looked up the traffic using chrome dev tool. I did the the traffic and tab got trigger and the url got loaded ( it is simply a GET call with params). However, when I click it multiple times, (after 8-10times, with different params everytime), it STOP working. I stop seeing the requests sent out. (Not seen on chrome dev tool, nor the Proxy I set up).
I wonder if there is a limit times ( restriction) for mayLaunchURL feature, in other words, how many pages we can pre-render in this case? Is there a way to manually cancel the pre-render page and free the resource?
is there a restriction in terms of times to bindCustomTabsService? The way I did to call mayLaunchURL is to have an activity and kill the activity once I finish the tab. Can I bind the service each time even I “kill (finish)” the activtiy every time?
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
customTabActivityHelper = new CustomTabActivityHelper();
customTabActivityHelper.setConnectionCallback(this);
}
#Override
protected void onStart() {
super.onStart();
customTabActivityHelper.bindCustomTabsService(this);
}
#Override
public void onCustomTabsConnected() {
Boolean mayLaunchUrlAccepted = customTabActivityHelper.mayLaunchUrl(Uri.parse(“the URL?f=“+params), null, null);
// the mayLaunchUrlAccepted always return true in my case. Even when there is no request sent.
}
Yes, mayLaunchURL() are very expensive in terms of battery/RAM/network, so it is throttled on app UID level. But limits get dropped after some time.
Best strategy is to use mayLaunchURL() if the confidence that the user will navigate to the URL is very high.
There is the "low confidence" mayLaunchURL() which is not throttled, but performs a more limited set of actions (currently preconnect, not specified which, may change). The low confidence mayLaunchURL is triggered by providing null as the uri and a list of URLs in otherLikelyBundles.

Static objects are not always there when a GCM message is received

I'm implementing GCM into my Android app. So far so good, the messaging works, but when a message is received, the app may or may not crash with NullPointerException. The reason this happens is because a static reference is sometimes null and sometimes not, but the reason why this happens is unknown to me.
The object that may or may not be null is a MessageController located via a simple service locator pattern, which looks like this:
public class ControllerLocator
{
private static MessageController controller;
public static MessageController getMessageController()
{
return controller;
}
public static void provide(MessageController mc)
{
controller = mc;
}
}
The controller is set up in the application's onCreate() method:
#Override
public void onCreate()
{
super.onCreate();
ControllerLocator.provide(new MessageController());
}
// Later on in the program after backend authentication:
ControllerLocator.getMessageController().setCredentials(...);
The GCM message handling is like this:
#Override
protected void onHandleIntent(Intent intent)
{
Bundle extras = intent.getExtras();
String msgType = GoogleCloudMessaging.getInstance(this).getMessageType(intent);
if(msgType.equals(GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE))
{
handleMessage(extras.getString("message"));
}
GcmBroadcastReceiver.completeWakefulIntent(intent);
}
private void handleMessage(String msg)
{
// 'controller' may or may not be null
MessageController controller = ControllerLocator.getMessageController();
}
Now, the actual scenario: Everything works fine when the app is running in the foreground. However, if I press "back" to exit close the main activity and a GCM message is received, the crash may or may not occur.
Why does the static reference get removed? How would I fix this to ensure that the controller is always there?
Its because your app process is killed and all the memory it held was released while you were waiting for that GCM message.
When your GCM message was incoming the system creates your app process and Application object over again hence you got null.
Simply check if the instance is null on your get instance method and if its null recreate your object.
EDIT:
There is no way to make sure the same object is alive always, Android system is running many apps at once and cant allow any app to fill up memory, therefore it may kill your process whenever other apps need memory and that would automatically release your reserved memory including static varaiables. Its your job as an Android developer to deal with it :)
To avoid reauth and heavy computations on recreation, you may have to rethink your design such that the state of your object is persistent, using SharedPreferences maybe.
You cannot ensure that your static Controller Object will be alive (not null),, Because an app in the background is an ideal candidate for GC.
You could however save your GCM Message data in SQLite at the point where it is received. And then check the table for data, when you open your app.
Make your MessageController variable static.

Get list of available chromecasts on demand

Ok, so I am trying to figure at how to get an up-to-date list of available chromecast devices, I'm doing this so that my app can check when the chromecast is not in use and then open my receiver app.
I am having some unexpected behaviour from the code below:
public class MainActivity extends Activity {
...
#Override
public void onCreate(Bundle savedInstanceState) {
...
mMediaRouterCallback = new MyMediaRouterCallback();
mMediaRouter = MediaRouter.getInstance(context);
mMediaRouteSelector = new MediaRouteSelector.Builder()
.addControlCategory(CastMediaControlIntent.categoryForCast(context.getString(R.string.app_id)))
.build();
mMediaRouter.addCallback(mMediaRouteSelector, mMediaRouterCallback,
MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
}
This adds a MediaRouter callback to the MediaRouter. I have chosen to use the active scan flag.
private class MyMediaRouterCallback extends MediaRouter.Callback {
...
#Override
public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
Log.d(TAG, "Description 1 " + info.getDescription());
mSelectedDevice = CastDevice.getFromBundle(info.getExtras());
Log.d(TAG, "Description 2 " + mSelectedDevice.toString());
if(info.getDescription().equals("Chromecast")) {
// code to launch chromecast receiver app here.
}
}
}
My implementation of the MediaRouter.Callback overrides onRouteAdded, it simply prints some information about the devices it has found Description 1 describes the receiver app the device is using, description 2 gives its name.
However when this code is run initially the same device is discovered twice printing:
07-05 21:01:12.270: D/MainActivity(9730): Description 1 Casting HelloText
07-05 21:01:12.280: D/MainActivity(9730): Description 2 "Downstairs"
07-05 21:01:12.280: D/MainActivity(9730): Description 1 Casting HelloText
07-05 21:01:12.280: D/MainActivity(9730): Description 2 "Downstairs"
Then periodically the onRouteAdded callback is called sometimes only listing the device once, other times listing the device twice. My understanding however is that this callback should only be called when a new route is added.
I want to find all the available devices on command, not at random intervals that I can't control, what do I need to be doing? I can't find a callback that seems to be appropriate for this situation (such as whenever devices update/change), nor can I find a way to list them without using callbacks, so I'm a bit stuck.
(I have been basing these tests of the HelloText-Android example found here https://github.com/googlecast/CastHelloText-android, also I started this (my first android project) only a couple of days ago, so I apologise if I am missing something horrendously obvious)
Thanks in advance.
Call getRoutes() to get the list of known routes at the point in time that you desire. Iterate over them. Call matchesSelector() on each to filter out those that match your desired control category.
If you are listening for "onRouteAdded()", you would also need to listen to "onRouteRemoved()" to do a correct bookkeeping; if a device is added, it can be removed and added again so if you just listen to onRouteAdded(), it may seem it is being added multiple times. Getting the list from MedaiaRoute.getRoutes() might be easier if you don't want to be notified immediately and only want to know the list at certain points on demand.

Android detecting when lines have been connected during an outgoing call

Just a quick background I'm Running CM7 on a rooted Nexus one.
I am trying to detect when an outgoing call is actually connected: has stopped ringing and the person you are calling has answered. Looking through the forums this seems to be a tough and perhaps unanswered question. I'd really appreciate any insight into this.
In my searching the best I could find was in:
Android : How to get a state that the outgoing call has been answered?
#PattabiRaman said: "instead of detecting the outgoing call connection state, it is easy to get the duration of the last dialed call."
Does he mean that one should get the duration of the last dialed call as the call is in progress? And when that duration goes over 0 then you know?
The class com.android.internal.telephony.CallManager should have information about when the call actually is answered. It has a public static method getInstance() which returns the CallManager instance, and a public method getActiveFgCallState() which returns the current call state as a Call.State enum.
So in theory something like this might work:
Method getFgState = null;
Object cm = null;
try {
Class cmDesc = Class.forName("com.android.internal.telephony.CallManager");
Method getCM = cmDesc.getMethod("getInstance");
getFgState = cmDesc.getMethod("getActiveFgCallState");
cm = getCM.invoke(null);
} catch (Exception e) {
e.printStackTrace();
}
And then repeatedly poll the state:
Object state = getFgState.invoke(cm);
if (state.toString().equals("IDLE")) {
...
} else if (state.toString().equals("ACTIVE")) {
// If the previous state wasn't "ACTIVE" then the
// call has been established.
}
I haven't verified that this actually works. And even if it does you'll have to keep in mind that the API could change, since this isn't something that app developers are supposed to rely on.
I have looked into the code.
It will always give null unless you instantiate a Phone object and set it as default Phone.
But instantiating it needs some System permissions allowed only to system aps.
By using this method:
com.android.internal.telephony.PhoneFactory# public static void makeDefaultPhones(Context context) {
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.4_r1.2/com/android/internal/telephony/PhoneFactory.java

Categories

Resources