How can I get the cached leaderboard score, when offline? - google-play-games

After the initial sign in, it seems that GPGS can keep the user in the "signed in" state, even when offline. So, I can play as if I'm connected, and then, when I have actual connectivity, my scores/achievements are synced.
This is great, but it doesn't seem to work when trying to retrieve a score from the leaderboard, when offline:
Games.Leaderboards.loadCurrentPlayerLeaderboardScore(
gameHelper.getApiClient(),
LD_ID,
LeaderboardVariant.TIME_SPAN_ALL_TIME,
LeaderboardVariant.COLLECTION_PUBLIC).setResultCallback(new ResultCallback<LoadPlayerScoreResult>(){
#Override
public void onResult(LoadPlayerScoreResult arg0) {
LeaderboardScore c = arg0.getScore();
Player.bestScore = (int)c.getRawScore();
}
});
When online, c.getRawScore() works as expected, but when offline, c is null (to my surprise, because I reasoned that it would just fall back to the cached score, if offline).
Is there any way to get the cached leaderboard score?

Games.Leaderboards.loadCurrentPlayerLeaderboardScore(
gameHelper.getApiClient(),
LD_ID,
LeaderboardVariant.TIME_SPAN_ALL_TIME,
LeaderboardVariant.COLLECTION_PUBLIC).setResultCallback(new ResultCallback<Leaderboards.LoadPlayerScoreResult>(){
#Override
public void onResult(LoadPlayerScoreResult res) {
Status status = res.getStatus();
int statusCode = status.getStatusCode();
if (statusCode == GamesStatusCodes.STATUS_NETWORK_ERROR_NO_DATA) {
System.out.println("A network error occurred while attempting to retrieve fresh data, and no data was available locally.");
} else if (statusCode == GamesStatusCodes.STATUS_NETWORK_ERROR_STALE_DATA) {
System.out.println("A network error occurred while attempting to retrieve fresh data, but some locally cached data was available. The data returned may be stale and/or incomplete.");
} else {
System.out.println(status.getStatusMessage());
}
}
});
I havn't tested this code but it should work. It's some debugging code to help with your problem. Run this while online and offline and see what you get in each case.

Related

Android FusedLocationClient does not request new samples

How to force FusedLocationProviderClient explicitly ask for a new location?
A few months ago, I upgraded the FusedLocationProviderClient. Ever since, requesting to sample a new location won't provide a new one.
// Request a single location.
// Note: FusedLocationProviderClient.lastLocation
// is no better
val request = LocationRequest()
.setNumUpdates(1)
.setExpirationDuration(0)
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
.setMaxWaitTime(10*DateUtils.SECOND_IN_MILLIS)
.setFastestInterval(0)
fusedLocationClient?.requestLocationUpdates(request, object:LocationCallback() {
override fun onLocationResult(result: LocationResult?) {
super.onLocationResult(result)
Log.v(LOG_TAG, "Single fused location request yields ${result?.lastLocation} ")
callOnDone(result?.lastLocation)
}
}, Looper.getMainLooper())
When I get request locations, even seconds apart, it gets me the same location with the same time stamp.
I use emulator with a GPX recording of my track, so that GPS readouts are always available.
However, when I tried to request a location from the location manager (and do nothing with the result), it works much better.
(context?.getSystemService(Context.LOCATION_SERVICE) as? LocationManager)?
.requestSingleUpdate(
LocationManager.GPS_PROVIDER,
object :LocationListener{...dummy, just log the callback...},
Looper.getMainLooper())
Conclusion: FusedLocationProviderClient won't request new locations from the OS, even on High Priority mode. However, it does accept new locations if one is available from outside.
The question is: How do I force FusedLocationProviderClient to proactively request a new location when I need a new one here and now?
This question is quite old, but I came here with a similar question, and I see about 65 others have over the last couple months. The following code works. It delivers a Location
to the onLocationResult callback, at locationResult.getLastLocation():
public void getNewLocation() {
LocationRequest locationRequest = new LocationRequest()
.setNumUpdates(1)
.setFastestInterval(0)
.setSmallestDisplacement(0)
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
if (ActivityCompat.checkSelfPermission(context,
Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(context,
Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
fusedLocationProviderClient.requestLocationUpdates(locationRequest,
new com.google.android.gms.location.LocationCallback() {
#Override
public void onLocationResult(LocationResult locationResult) {
locationListener.onLocationChanged(locationResult.getLastLocation());
}
},
Looper.getMainLooper());
}
The difference here is that I've removed the setMaxWaitTime and setExpirationDuration. Evidently they break the call. If I interpret
setExpirationDuration correctly, it means this request should expire in
x millis. Setting it zero means the request expires immediately, so when a Location does become available, the request has already expired.
Hence no callback to onLocationChanged.
I hope this helps somebody.

Google Play Warning: How to fix incorrect implementation of HostnameVerifier

Today I just received this email from Google:
Your app(s) listed at the end of this email have an unsafe
implementation of the HostnameVerifier interface, which accepts all
hostnames when establishing an HTTPS connection to a remote host with
the setDefaultHostnameVerifier API, thereby making your app vulnerable
to man-in-the-middle attacks. An attacker could read transmitted data
(such as login credentials), and even change the data transmitted on
the HTTPS connection.
Sadly, I searched all my code and found no use of HostnameVerifier, nor setDefaultHostnameVerifier or even any HTTPS connections!
I'm using Google's compatibility libraries in its latest version: 25.0.1, and in some of my apps the Google Ads 9.8.0. Will upgrade Ads to 10.0.1, as I can only assume the culprit is in there?!
Did anyone received this alert and if so how did you solve it?
Same here - Insecure Hostname Verifier Detected in APK
Your app is using an unsafe implementation of HostnameVerifier. Please
see this Google Help Center article for details, including the
deadline for fixing the vulnerability. Im not using HostnameVerifier
and not calling setDefaultHostnameVerifier. Moreover - Im using OKHTTP
lib for http-requests. I hope that defining TrustManager will solve
this issue.
Since I'm not subclassing HostnameVerifier or calling setDefaultHostnameVerifier() I assume it relies to some 3rd party lib. Since I can't detect such lib I think I will try to add a class with following code
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
public boolean verify(final String hostname, final SSLSession session) {
if (check if SSL is really valid)
return true;
else
return false;
}
});
to my project and will see if it fixes the issue.
So I did it and additionally to every webView I've added overridden method
#Override
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
// the main thing is to show dialog informing user
// that SSL cert is invalid and prompt him to continue without
// protection: handler.proceed();
// or cancel: handler.cancel();
String message;
switch(error.getPrimaryError()) {
case SslError.SSL_DATE_INVALID:
message = ResHelper.getString(R.string.ssl_cert_error_date_invalid);
break;
case SslError.SSL_EXPIRED:
message = ResHelper.getString(R.string.ssl_cert_error_expired);
break;
case SslError.SSL_IDMISMATCH:
message = ResHelper.getString(R.string.ssl_cert_error_idmismatch);
break;
case SslError.SSL_INVALID:
message = ResHelper.getString(R.string.ssl_cert_error_invalid);
break;
case SslError.SSL_NOTYETVALID:
message = ResHelper.getString(R.string.ssl_cert_error_not_yet_valid);
break;
case SslError.SSL_UNTRUSTED:
message = ResHelper.getString(R.string.ssl_cert_error_untrusted);
break;
default:
message = ResHelper.getString(R.string.ssl_cert_error_cert_invalid);
}
mSSLConnectionDialog = new MaterialDialog.Builder(getParentActivity())
.title(R.string.ssl_cert_error_title)
.content(message)
.positiveText(R.string.continue_button)
.negativeText(R.string.cancel_button)
.titleColorRes(R.color.black)
.positiveColorRes(R.color.main_red)
.contentColorRes(R.color.comment_grey)
.backgroundColorRes(R.color.sides_menu_gray)
.onPositive(new MaterialDialog.SingleButtonCallback() {
#Override
public void onClick(MaterialDialog materialDialog, DialogAction dialogAction) {
mSSLConnectionDialog.dismiss();
handler.proceed();
}
})
.onNegative(new MaterialDialog.SingleButtonCallback() {
#Override
public void onClick(MaterialDialog materialDialog, DialogAction dialogAction) {
handler.cancel();
}
})
.build();
mSSLConnectionDialog.show();
}
to the
mWebView.setWebViewClient(new WebViewClient() {
... // other corresponding overridden methods
}
And finally Google says:
SECURITY SCAN COMPLETE
No known vulnerabilities were detected for APK 158.
However I'm not sure what code made it, HostNameVerifier or onReceivedSslError() of mWebView.setWebViewClient.
As per the mail received from Google, there can be two possibilities for this issue:
Primarily you have to check your package name is not using any keywords restricted by Google. For example "com.companyname.android", .android is not allowed. Secondary is to check for HostNameVerifier
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
public boolean verify(final String hostname, final SSLSession session) {
if (/* check if SSL is really valid */)
return true;
else
return false;
}
});

Cannot make XBAP cookies work

I am trying to make a XBAP application communicating with a webservice with login.
But I want the user to skip the login step if they already logged in the last seven days.
I got it to work using html/aspx.
But it fails continuously with XBAP.
While debugging, the application is given full trust.
This is the code I have so far to write the cookie:
protected static void WriteToCookie(
string pName,
Dictionary<string, string> pData,
int pExiresInDays)
{
// Set the cookie value.
string data = "";
foreach (string key in pData.Keys)
{
data += String.Format("{0}={1};", key, pData[key]);
}
string expires = "expires=" + DateTime.Now.AddDays(pExiresInDays).ToUniversalTime().ToString("r");
data += expires;
try
{
Application.SetCookie(new Uri(pName), data);
}
catch (Exception ex)
{
}
}
And this is what I have to read the cookie:
protected static Dictionary<string, string> ReadFromCookie(
string pName)
{
Dictionary<string, string> data = new Dictionary<string, string>();
try
{
string myCookie = Application.GetCookie(new Uri(pName));
// Returns the cookie information.
if (String.IsNullOrEmpty(myCookie) == false)
{
string[] splitted = myCookie.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
string[] sub;
foreach(string split in splitted)
{
sub = split.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
if (sub[0] == "expires")
{
continue;
}
data.Add(sub[0], sub[1]);
}
}
}
catch(Exception ex)
{
}
return data;
}
The pName is set with:
string uri = "http://MyWebSiteName.com";
When the user authenticate the first time, I call the WriteToCookie function and set it with 7 days to expire.
It looks like everything is fine as I get no exception of error messages. (I have a break point in the catch)
After that, I close the session and start it again.
The first thing I do is a ReadFromCookie.
Then I get an exception with the following message: No more data is available
So my application is sending the user automatically back to the login screen.
I also tried to do a ReadFromCookie right after the WriteToCookie in the same session, and I get the same error.
Application.SetCookie(new Uri("http://MyWebSiteName.com/WpfBrowserApplication1.xbap"), "Hellllo");
string myCookie2 = Application.GetCookie(new Uri("http://MyWebSiteName.com/WpfBrowserApplication1.xbap"));
It seems to me that the cookie is not even written in the first place.
So I am guessing I am doing something wrong.
Maybe the uri I am using is wrong. Is there a specific format needed for it?
Just like you need a very specific format for the expire date.
I have been searching quite a lot of internet for a good sample/tutorial about using cookies with XBAP, and I could not find anything really well documented or tested.
A lot of people say that it works, but no real sample to try.
A lot of people also handle the authentication in html, then go to the XBAP after successfully reading/writing the cookies.
I would prefer a full XBAP solution if possible.
To answer some questions before they are asked, here are the project settings:
Debug:
Command line arguments: -debug -debugSecurityZoneURL http://MyWebSiteName.com "C:\Work\MyWebSiteName\MyWebSiteNameXBAP\bin\Debug\MyWebSiteNameXBAP.xbap"
Security:
Enable ClickOnce security settings (Checked)
This is a full trust application (selected)
I also created a certificate, and added it the 3 stores like explained in "publisher cannot be verified" message displayed
So I do not have the warning popup anymore. I just wanted to make sure that it was not a permission issue.
Finally found the answer to this problem.
Thanks for this CodeProject I was finally able to write/read cookies from the XBAP code.
As I had guessed, the URI needs to be very specific and you cannot pass everything you want in it.
What did the trick was using: BrowserInteropHelper.Source
In the end the read/write code looks like:
Application.SetCookie(BrowserInteropHelper.Source, data);
string myCookie = Application.GetCookie(BrowserInteropHelper.Source);
It looks like you cannot use ';' to separate your own data.
If you do so, you will only get the first entry in your data.
Use a different separator (ex: ':') and then you can get everything back
The data look like this:
n=something:k=somethingElse;expires=Tue, 12 May 2015 14:18:56 GMT ;
The only thing I do not get back from Application.GetCookie is the expire date.
Not sure if it is normal or not. Maybe it is flushed out automatically for some reason. If someone knows why, I would appreciate a comment to enlighten me.
At least now I can read/write data to the cookie in XBAP. Yeah!

RestSharp - when a test runs for the first time, it fails. When I debug, it passes. what's going on?

Pretty basic test:
[TestClass]
public class ApiClientTest
{
private RestClient _client;
[TestInitialize()]
public virtual void TestInitialize()
{
_client = new RestClient("http://localhost:24144");
_client.CookieContainer = new System.Net.CookieContainer();
}
[TestMethod]
public void ApiClientTestCRUD()
{
// 1. Log out twice. Verify Unauthorized.
var response = LogOut();
response = LogOut();
Assert.AreEqual(response.StatusCode, HttpStatusCode.Unauthorized);
// Error here:
Result Message: Assert.AreEqual failed. Expected:<0>.
Actual:< Unauthorized >.
I get <0>, which isn't even something that my WebAPI returns.
I think the issue is with my use of RestSharp, because if I debug one time it passes, and then subsequent runs pass. Any clue what's going on?
To be clear - this occurs when I open up my solution and attempt to run the test for the first time. I can fix it by debugging once, watching it pass, and then running without debugging as much as I want. I can reproduce this by closing VS and opening up the solution again - and running the test without debugging first.
Here's the LogOut method in my WebAPI:
[Authorize]
public HttpResponseMessage LogOut()
{
try
{
if (User.Identity.IsAuthenticated)
{
WebSecurity.Logout();
return Request.CreateResponse(HttpStatusCode.OK, "logged out successfully.");
}
return Request.CreateResponse(HttpStatusCode.Conflict, "already done.");
}
catch (Exception e)
{
return Request.CreateResponse(HttpStatusCode.InternalServerError, e);
}
}
UPDATE:
I ended up running the tests with Trace.WriteLine:
// 1. Log out twice. Verify Unauthorized.
Trace.WriteLine("ENTERING FIRST LOGOUT");
var response = LogOut();
Trace.WriteLine("Content: " + response.Content);
Trace.WriteLine("ErrorMessage: " + response.ErrorMessage);
Trace.WriteLine("ResponseStatus: " + response.ResponseStatus);
Trace.WriteLine("StatusCode: " + response.StatusCode);
Trace.WriteLine("StatusDescription: " + response.StatusDescription);
response = LogOut();
Trace.WriteLine("COMPLETED LOGOUTS");
Assert.AreEqual(response.StatusCode, HttpStatusCode.Unauthorized);
And I found the following:
ENTERING FIRST LOGOUT
Content:
ErrorMessage: Unable to connect to the remote server
ResponseStatus: Error
StatusCode: 0
StatusDescription:
COMPLETED LOGOUTS
My solution has a test project with this RestSharp test, and a WebAPI project that's supposed to be accepting these requests. If I debug, the RestClient connects. If not, it times out. Any tips?
When debugging is not possible to solve the problem go to the old fashion way.
Add Trace.WriteLine (or even append text to a C:\temp.txt file).
Write some string before every return in the LogOut method, then try writing some more information (if it's the last return then write the Exception message, if it's the second return write the Identity information.
Hope this helps.
How are you hosting the server? I see this that you're using port 24144. Maybe in debug mode you're running the express IIS Web Server and that's the port, but in non-debug mode it's not?

Saving Data Locally and Remotely (Syncing)

When data is entered, it ultimately needs to be saved remotely on a server. I do want the app to work if there is no data connection at the time also, so I need to save everything locally on the phone too. The app can then sync with the server when it gets a connection.
This brings up a little issue. I'm used to saving everything on the server and then getting the records back with id's generated from the server for them. If there is no connection, the app will save locally to the phone but not the server. When syncing with the server, I don't see a way for the phone to know when a record comes back which locally record it's associated with. There isn't enough unique data to figure this out.
What is the best way to handle this?
One way I've been thinking is to change the id of the records to a GUID and let the phone set the id. This way, all records will have an id locally, and when saving to the server, it should still be a unique id.
I'd like to know what other people have been doing, and what works and what doesn't from experience.
This is how we done with a first windows phone 7 app finished few days ago with my friend.
It might not be the best solution but 'till additional refactoring it works just fine.
It's an application for a web app like a mint.com called slamarica.
If we have feature like save transaction, we first check if we have connection to internet.
// Check if application is in online or in offline mode
if (NetworkDetector.IsOnline)
{
// Save through REST API
_transactionBl.AddTransaction(_currentTransaction);
}
else
{
// Save to phone database
SaveTransactionToPhone(_currentTransaction);
}
If transaction is successfully saved via REST, it responses with transaction object and than we save it to local database. If REST save failed we save data to local database.
private void OnTransactionSaveCompleted(bool isSuccessful, string message, Transaction savedTransaction)
{
MessageBox.Show(message);
if(isSuccessful)
{
// save new transaction to local database
DatabaseBl.Save(savedTransaction);
// save to observable collection Transactions in MainViewModel
App.ViewModel.Transactions.Add(App.ViewModel.TransactionToTransactionViewModel(savedTransaction));
App.ViewModel.SortTransactionList();
// Go back to Transaction List
NavigationService.GoBack();
}
else
{
// if REST is failed save unsent transaction to Phone database
SaveTransactionToPhone(_currentTransaction);
// save to observable collection Transactions in MainViewModel
App.ViewModel.Transactions.Add(App.ViewModel.TransactionToTransactionViewModel(_currentTransaction));
App.ViewModel.SortTransactionList();
}
}
Every Transaction object has IsInSync property. It is set to false by default until we got confirmation from REST API that it's saved successful on the server.
User has ability to refresh transactions. User can click on a button Refresh to fetch new data from the server. We do the syncing in the background like this:
private void RefreshTransactions(object sender, RoutedEventArgs e)
{
if (NetworkDetector.IsOnline)
{
var notSyncTransactions = DatabaseBl.GetData<Transaction>().Where(x => x.IsInSync == false).ToList();
if(notSyncTransactions.Count > 0)
{
// we must Sync all transactions
_isAllInSync = true;
_transactionSyncCount = notSyncTransactions.Count;
_transactionBl.AddTransactionCompleted += OnSyncTransactionCompleted;
if (_progress == null)
{
_progress = new ProgressIndicator();
}
foreach (var notSyncTransaction in notSyncTransactions)
{
_transactionBl.AddTransaction(notSyncTransaction);
}
_progress.Show();
}
else
{
// just refresh transactions
DoTransactionRefresh();
}
}
else
{
MessageBox.Show(ApplicationStrings.NETWORK_OFFLINE);
}
}
private void DoTransactionRefresh()
{
if (_progress == null)
{
_progress = new ProgressIndicator();
}
// after all data is sent do full reload
App.ViewModel.LoadMore = true;
App.ViewModel.ShowButton = false;
ApplicationBl<Transaction>.GetDataLoadingCompleted += OnTransactionsRefreshCompleted;
ApplicationBl<Transaction>.GetData(0, 10);
_progress.Show();
}
OnTransactionRefreshCompleted we delete all transaction data in local database and get the latest 10 transactions. We don't need all the data, and this way user have synced data. He can always load more data by taping load more at the end of transaction list. It's something similar like those twitter apps.
private void OnTransactionsRefreshCompleted(object entities)
{
if (entities is IList<Transaction>)
{
// save transactions
var transactions = (IList<Transaction>)entities;
DatabaseBl.TruncateTable<Transaction>();
DatabaseBl.Save(transactions);
((MainViewModel) DataContext).Transactions.Clear();
//reset offset
_offset = 1;
//update list with new transactions
App.ViewModel.LoadDataForTransactions(transactions);
App.ViewModel.LoadMore = false;
App.ViewModel.ShowButton = true;
}
if (entities == null)
{
App.ViewModel.ShowButton = false;
App.ViewModel.LoadMore = false;
}
// hide progress
_progress.Hide();
// remove event handler
ApplicationBl<Transaction>.GetDataLoadingCompleted -= OnTransactionsRefreshCompleted;
}
Caveat - I haven't tried this with windows phone development but use of GUID identities is something I usually do when faced with similar situations - eg creating records when I only have a one-way connection to the database - such as via a message bus or queue.
It works fine, albeit with a minor penalty in record sizes, and can also cause less performant indexes. I suggest you just give it a shot.

Resources