Android Cast - Media Router callback not returning - android

I'm trying to auto discover Cast devices. This is basically verbatim what I've seen for how to do this, however I never get the callback for either onRouteAdded or onRouteSelected.
I've tried changing which flags are used, but didn't get different results. Sometimes the route will be added, but never selected.
private void startSearchForDevicesAndCast() {
MediaRouter router = MediaRouter.getInstance(this);
int count = router.getRoutes().size();
List<MediaRouter.RouteInfo> j = router.getRoutes();
MediaRouteSelector selector = new MediaRouteSelector.Builder().addControlCategory(
CastMediaControlIntent.categoryForCast(getString(R.string.app_id))).build();
router.addCallback(selector, new MediaRouter.Callback() {
#Override
public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo route) {
super.onRouteAdded(router, route);
Log.i(TAG, "onRouteAdded: ");
router.selectRoute(route);
}
#Override
public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) {
super.onRouteChanged(router, route);
Log.i(TAG, "onRouteChanged: ");
}
#Override
public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) {
Log.i(TAG, "onRouteSelected: ");
super.onRouteSelected(router, route);
}
}, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
}

I believe in your case, you would need to do a little bit more. Depending on the setup and structure of your app and some other factors, the route you are expecting may have already been discovered by the MediaRouter. In that case, you will not get an onRouteAdded() since it is already there. To accommodate this, one approach is after calling router.addCallback(..) (as you are already doing), get the list of all present routes by calling mMediaRouter.getRoutes() and then filter the list for the app id that you are interested in (by using the same selector that you have defined and using route.matchesSelector(selector)) and consider that as your initial set of discovered devices. From that point on, the onRouteAdded() and onRouteRemoved() can be used to update the list. As for onRouteChanged(), that sometimes has valuable information; for example when a cast device is rebooted, and when it comes up, at the very beginning it may not have the right configuration but soon after it gets its configuration and updates certain fields and then onRouteChanged() is called but in most cases, you may ignore that and just focus on the initial set and the ones that get added or removed. If you want to see how this can be put together, note that what I outlined above is very close to what the MediaRouterChooserDialog does internally and since the source to that is openly available in Media Router v7 support library, you can take a look and see how it can be done.

Related

Android how to choose between nearby connections

Im developing an app which uses androids nearby connections to connect two mobile units.
When I test the scenario with 3 mobile units, where two are advertising and one is discovering, it chooses to make a connection to one of them.
How do I generate a list of found endpoints instead so that the user can choose between them and select the right one to connect to?
My code is the following when an endpoint is found:
private final EndpointDiscoveryCallback mEndpointDiscoveryCallback = new EndpointDiscoveryCallback() {
#Override
public void onEndpointFound(String endpointId, DiscoveredEndpointInfo info) {
Toast.makeText(MainActivity.this,"onEndpointFound",Toast.LENGTH_SHORT).show();
connect(endpointId);
}
#Override
public void onEndpointLost(String endpointId) {
Toast.makeText(MainActivity.this,"onEndpointLost",Toast.LENGTH_SHORT).show();
}
};
Following the documentation https://developers.google.com/nearby/connections/android/manage-connections it states "Depending on your use case, you may wish to instead display a list of discovered devices to the user, allowing them to choose which devices to connect to." But it does not state how to do this.

How do I make a list of nearby places in a static location, not current location?

I'm not sure if this has been answered. This feels like such a basic question but I've been looking for a way to do this and all the examples I came across showed a list of places near the device. What I'm trying to do is to specify a location via button click, then show the places near it.
Specifically, if the user clicks button 1, this means the user wants to see the places near train station A, probably in another activity with a RecyclerView.
I was using http://www.zoftino.com/current-location-and-nearby-places-android-example for reference. I followed it all the way to adding the API key in the manifest file but I got stuck trying to analyze how I'm supposed to define a specific location instead of getting the device's location.
I'm not sure if I'm approaching this correctly but so far, I've made a Java bean that can get which station was selected...
public class Station {
String station;
public Station (String station) {
this.station = station;
}
public Station () {
}
public String getStation() {
return station;
}
public void setStation(String station) {
this.station = station;
}
I also have an activity with an onClick method to check which button was clicked...
public void onClick (View v) {
switch (v.getId()) {
case R.id.img1: station = getString(R.string.StationName1);
Log.d("project", "selected station is " + station);
break;
//other cases deleted to make this more concise
}
Station selected = new Station (station);
Log.d("project", "content of station bean is " + selected.getStation());
}
I'm testing this using a phone running Android 7.1.1. From the logcat, I know that the Station bean is able to get the selected station correctly. I honestly don't know why I decided to use getString(R.string.StationName1) but considering I'm doing this for 4 train lines and I'm already past the project's deadline. I'd rather not have to change them, if possible.
I was thinking of adding some sort of "coordinates" attribute to the Java bean and passing the exact coordinates using the switch case above but even if I did that, I still wouldn't know how to use it so I can show the nearby locations.
Edit: I'm using Android Studio 3.2.1
The linked tutorial has the following code block where places are being detected and displayed:
#SuppressLint("MissingPermission")
private void getCurrentPlaceData() {
Task<PlaceLikelihoodBufferResponse> placeResult = placeDetectionClient.
getCurrentPlace(null);
placeResult.addOnCompleteListener(new OnCompleteListener<PlaceLikelihoodBufferResponse>() {
#Override
public void onComplete(#NonNull Task<PlaceLikelihoodBufferResponse> task) {
Log.d(TAG, "current location places info");
List<Place> placesList = new ArrayList<Place>();
PlaceLikelihoodBufferResponse likelyPlaces = task.getResult();
for (PlaceLikelihood placeLikelihood : likelyPlaces) {
placesList.add(placeLikelihood.getPlace().freeze());
}
likelyPlaces.release();
PlacesRecyclerViewAdapter recyclerViewAdapter = new
PlacesRecyclerViewAdapter(placesList,
CurrentLocationNearByPlacesActivity.this);
recyclerView.setAdapter(recyclerViewAdapter);
}
});
}
Here, the getCurrentPlace method uses the current device location, which is the likely cause of your issue. In fact, the Place Detection Client documentation itself notes that: (emphasis mine)
The Place Detection API provides quick access to the device's current place, and offers the opportunity to report the location of the device at a particular place (like a check in).
Like you said, there doesn't seem to be any API in the Android Places SDK that offers a 'Nearby Places' functionality for a location. Even the above is attempting to associate the current user location with a known place (as opposed to a set of coordinates with no extra info), and doesn't seem to be meant to search for nearby places by design (though it can clearly be done).
However, the Place Search API can be used for your requirements (I have used it in an app that does something similar to what you are looking to do). It includes an endpoint for Nearby Search Requests, where a static location can be passed. The only downside is that you will have to handle the HTTP calls through Retrofit or something similar, since the SDK doesn't have an existing framework for this API.

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

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)).

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.

How do I keep a ChromeCast route alive when my app is in the background on battery power?

I'm working on an Android app that supports sending music to a ChromeCast. We'd like users to be able to cast entire music playlists while the app runs in the background.
When my Nexus 7 is not connected to USB power and I turn the screen inactivity timeout to 15 seconds in the settings, the app will disconnect from the ChromeCast about 90 seconds after the device powers off its screen.
I've identified that I'm getting a MediaRouter.Callback call to onRouteUnselected, and since that's the callback I get when a user disconnects from a route, I'm handling it by tearing down the ApplicationSession.
When I plug back in and check the logcat, I see this message around the same time:
I/MediaRouter(19970): Choosing a new selected route because the current one is no longer selectable: MediaRouter.RouteInfo{ uniqueId=... }
Can I do anything to avoid the route being unselected when the app is in the background, or is there something else I can do to get the behavior I want?
I eventually got around this by refusing to disconnect the message streams and tear down the session when the route was disconnected under these conditions, and silently re-select the route when it became available again. The route gets deselected, but it does not affect my casting session.
To do this, I check to see if the route exists when it's unselected.
public void onRouteUnselected(final MediaRouter router, final RouteInfo route) {
if (!onUiThread()) {
new Handler(Looper.getMainLooper()).post((new Runnable() {
#Override
public void run() {
onRouteUnselected(router, route);
}
}));
return;
}
boolean isThisRouteAvailable = doesRouterContainRoute(router, route);
mRouteToReconnectTo = null;
if (isThisRouteAvailable) {
// Perform code to close the message streams and tear down the session.
} else {
// The route was unselected because it's no longer available from the router,
// so try to just keep playing until the message streams get disconnected.
mRouteToReconnectTo = route;
// Short-circuited a disconnect.
}
}
Later, when the route comes back, we can immediately re-select it.
#Override
public void onRouteAdded(MediaRouter router, RouteInfo route) {
super.onRouteAdded(router, route);
// if mRouteToReconnectTo is not null, check to see if this route
// matches it, and reconnect if it does with router.selectRoute(route)
}
#Override
public void onRouteSelected(final MediaRouter router, final RouteInfo route) {
if (!onUiThread()) {
new Handler(Looper.getMainLooper()).post((new Runnable() {
#Override
public void run() {
onRouteSelected(router, route);
}
}));
return;
}
if (areRoutesEqual(mRouteToReconnectTo, route)) {
// Short-circuited a reconnect.
mRouteToReconnectTo = null;
return;
}
mRouteToReconnectTo = null;
// Standard post-selection stuff goes here
}
There's no good way to compare two RouteInfo's, so I ended up writing a helper function that compared their description strings.
Rooster's answer is perfectly feasible and actually provides good insight as to how to re-connect to a route once it comes back online....
but....just to give further insight on what's going on....
You're getting...
I/MediaRouter(19970): Choosing a new selected route because the current one is no longer selectable: MediaRouter.RouteInfo{ uniqueId=... }
because when the device goes to sleep and is NOT plugged into a power source, the WIFI hardware is going into a low-power profile mode (and possibly shutting down entirely). This results in packet loss and subsequently causes the MedaRouter to fire the onRouteUnselected callback.
To prevent the Wifi from turning off you could set a WakeLock on the Wifi in the following manner:
WifiLock wifiLock;
WifiManager wm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF , "MyWifiLock");
wifiLock.acquire();
Using the flag WifiManager.WIFI_MODE_FULL_HIGH_PERF will keep the WIFI hardware alive and active when the device goes to sleep. Caution, this flag is only available to API 12 and above.
I tried using the WifiManager.WIFI_MODE_FULL flag when creating the WifiLock, but that didn't seem to do the trick.
Obviously anyone using any type of WifiLock or WakeLock should take considerable care in making sure locks released when no longer needed. Also, beware this will cause battery drain when the device screen is off.
If you used the sample code (Android in this case), you're probably doing this...
mSession.setStopApplicationWhenEnding(true);
mSession.endSession();
...when the route is unselected. If you instead do this...
mSession.setStopApplicationWhenEnding(false);
mSession.endSession();
...then you can clean up the session, but the Chromecast will keep the application alive. When the route becomes available again (or possibly when the user picks the device again) you can build a new session. I have yet to explore how to determine if the new session is talking to a "brand new" instance of the application or to the application left running from another session, but I'll update this answer when I do.

Categories

Resources