I have a race condition between the PlacePicker activity and my own activity that I want to start immediately after PlacePicker ends.
Here is how my app works:
It begins in PlaceActivity. In PlaceActivity's onStart, I connect to the GoogleAPI. In onConnected, I use Places to determine the user's location. If the location has a probability of less than .8, it invokes the PlacePicker (which is it's own activity). The Placepicker is created with startActivityForResult. Within the corresponding onActivityResult, RatingsActivity is started and is passed the place from the PlacePicker. The problem is that there is a race between PlaceActivity restarting after PlacePicker stops, and the start of RatingsActivity. How do I fix this? I know I could require the user to press another button to find their location, but I would much rather it happen automatically in onStart.
#Override
public void onStart() {
super.onStart();
mGoogleApiClient.connect();
}
#Override
public void onConnected(Bundle bundle) {
detectCurrentPlace();
}
public void detectCurrentPlace() {
PendingResult<PlaceLikelihoodBuffer> result = Places.PlaceDetectionApi.getCurrentPlace(mGoogleApiClient, null);
//Find the most probable place
//If prob is greater than threshold, assume this is the correct place.
//Otherwise, open placepicker
final double thresh = .8;
//intent.putExtra("com.parse.starter.name", mostProbPlace.getName());
//intent.putExtra("com.parse.starter.address", mostProbPlace.getAddress());
result.setResultCallback(new ResultCallback<PlaceLikelihoodBuffer>() {
#Override
public void onResult(PlaceLikelihoodBuffer placeLikelihoods) {
double highestProb = 0;
PlaceLikelihood mostProbPlace = null;
for(PlaceLikelihood p : placeLikelihoods) {
if(p.getLikelihood() > highestProb) {
highestProb = p.getLikelihood();
mostProbPlace = p;
}
StringBuffer types = new StringBuffer();
for (int type : p.getPlace().getPlaceTypes()) {
types.append(", " + type);
}
Log.i(TAG, String.format("Place '%s' has liklihood: %g", p.getPlace().getName(), p.getLikelihood()));
Log.i(TAG, String.format("Website: '%s; Types: %s", p.getPlace().getWebsiteUri(), types));
}
placeLikelihoods.release();
//Log.i(TAG, "Probability of place: " + mostProbPlace.getLikelihood());
if(highestProb > thresh) {
//I'm not sure if this line is right
Intent intent = new Intent(getApplicationContext(), RatingActivity.class);
intent.putExtra("com.parse.starter.name", mostProbPlace.getPlace().getName());
intent.putExtra("com.parse.starter.address", mostProbPlace.getPlace().getAddress());
startActivity(intent);
}
else {
createPlacePicker();
}
placeLikelihoods.release();
}
});
}
public void createPlacePicker() {
PlacePicker.IntentBuilder builder = new PlacePicker.IntentBuilder();
Context context = getApplicationContext();
try {
startActivityForResult(builder.build(context), PLACE_PICKER_REQUEST);
} catch(GooglePlayServicesRepairableException e) {
e.printStackTrace();
Log.d(TAG, "REPAIRABLE_SERVICES");
} catch(GooglePlayServicesNotAvailableException e) {
e.printStackTrace();
Log.d(TAG, "NOTAVAILABLE_SERVICES");
}
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if(requestCode == PLACE_PICKER_REQUEST) {
if(resultCode == RESULT_OK) {
Place place = PlacePicker.getPlace(data, this);
String toastMsg = String.format("Place: %s", place.getName());
Toast.makeText(this, toastMsg, Toast.LENGTH_LONG).show();
Intent intent = new Intent(this, RatingActivity.class);
intent.putExtra("com.parse.starter.name", place.getName());
intent.putExtra("com.parse.starter.address", place.getAddress());
startActivity(intent);
}
}
}
From what I can tell, the race condition is caused by the fact you are running detectCurrentPlace every time onStart is called.
I think you just need to store the state of your PlaceActivity when the activity is started, so you can modify the behaviour of onStart depending upon whether it is being called from the launch, or as a result of the return from PlacePicker.
class PlaceActivity {
boolean mRunningPlacePicker;
#Override
public void onStart() {
super.onStart();
if (mRunningPlacePicker) {
// we've returned from placepicker - don't run the detectCurrentPlace again
// because RatingsActivity has already launched from onActivityResult
...
} else {
mGoogleApiClient.connect();
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// save the state in case Android destroys our activity
outState.putBoolean("mRunningPlacePicker", mRunningPlacePicker);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// restore the state
if (savedInstanceState != null) {
mRunningPlacePicker = savedInstanceState.getBoolean("mRunningPlacePicker");
}
}
#Override
public void onConnected(Bundle bundle) {
detectCurrentPlace();
}
public void detectCurrentPlace() {
// do your deciding...
result.setResultCallback(new ResultCallback<PlaceLikelihoodBuffer>() {
#Override
public void onResult(PlaceLikelihoodBuffer placeLikelihoods) {
...
if (highestProb > thresh) {
...
} else {
// save the fact we are running the place picker
mRunningPlacePicker = true;
createPlacePicker();
}
}
}
Related
I have trouble ending an AsyncTask, even though IMHO, I have adhered to the recommended flagging practice. The code below only takes the BackButton into account, but to have a general solution for all kinds of exiting the activity would be best.
I have a MeasurementLoadingsScreen Class which binds a Service like this:
ServiceConnection _connection = new ServiceConnection() {
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
_service = myService.Stub.asInterface(iBinder);
runMeasurementAsyncTask();
}
public void onServiceDisconnected(ComponentName componentName) {
_service = null;
}
};
#Override
protected void onStart() {
super.onStart();
Intent serviceIntent = new Intent();
serviceIntent.setPackage(myService.class.getPackage().getName());
serviceIntent.setAction(myService.class.getName() + ".ACTION_BIND");
bindService(serviceIntent, _connection, BIND_AUTO_CREATE);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_measurement_loadingscreen);
findViewsById();
setDefaultVisibility();
intent = getIntent();
//myStuff
}
The MeasurementLoadingScreen contains an AsyncTask with a WeakReference (or SoftReference, tried both).
private void runMeasurementAsyncTask() {
//myStuff
meTask = new MeasurementTask(this);
meTask.execute();
}
private static class MeasurementTask extends AsyncTask<Integer, Integer, String> {
private WeakReference<MeasurementLoadingScreen> activityReference;
private ServiceHelper serviceHelper;
//myStuff
public static boolean isRunning = false;
MeasurementTask(MeasurementLoadingScreen context) {
activityReference = new WeakReference<>(context);
serviceHelper = new ServiceHelper(context._service);
}
#Override
protected String doInBackground(Integer... params) {
MeasurementLoadingScreen activityReference = this.activityReference.get();
isRunning = true;
//myStuff, it's ok to finish running this before cancelling
if (isCancelled()) {
return "cancelled";
}
// more myStuff
if (isCancelled()) {
return "cancelled";
}
[...]
#Override
protected void onCancelled(){
//myStuff: safely shutdown measurement process, might take a few seconds at worst
meTask.isRunning = false;
if(activityReference.get() != null) {
activityReference.get().finish(); //it does not work without this, either
}
super.onCancelled();
}
When I tap back twice (regardless of specific behaviour), it should (safely) abort the AsyncTask:
#Override
public void onBackPressed()
{
if (mBackPressed + TIME_INTERVAL > System.currentTimeMillis())
{
if (meTask == null) {
super.onBackPressed();
return;
}
cancelTask();
Toast.makeText(getBaseContext(), "Cancelling Task", Toast.LENGTH_SHORT).show();
return;
}
else { Toast.makeText(getBaseContext(), "Tap back button in order to exit", Toast.LENGTH_SHORT).show(); }
mBackPressed = System.currentTimeMillis();
}
#Override
protected void onStop() {
super.onStop();
if (_connection != null)
unbindService(_connection);
finish();//
}
#Override
public void onPause() {
int i = 0;
super.onPause();
//poll state of task here
finish();
}
I cannot seem to get the Task to stop running. If I set the meTask =null; in onPause, for example, it will eventually go through onCancelled and then call onStop, ending the service as well. But there is no way of waiting for the onCancelled to do its stuff and notify me with the isRunningflag, getStatus or whatever. The app just freezes however I poll the Task, which stays RUNNING all the time, unless I set it to null (undesirable, of course).
Edit:
I have added a polling attempt that allows the activity to call onPause/onStop eventually, but my main issue is with the polling, I do not ever see a finished/finishing task, even though onCancelled gets called at some point, but only after I set the meTask = null;, it does not continue cancelling normally.
public void onBackPressed() {
int i=0;
if (mBackPressed + TIME_INTERVAL > System.currentTimeMillis())
{
if (meTask == null) {
super.onBackPressed();
return;
}
cancelTask();
Toast.makeText(getBaseContext(), "Cancelling Task", Toast.LENGTH_SHORT).show();
while ((meTask.getStatus()== AsyncTask.Status.RUNNING) && (i < 20000)) {
//waiting etc., checking für isRunning doesn't work either
i++;
}
Log.d(TAG, "meTask " + meTask.getStatus());
meTask = null;
Log.d(TAG, "meTask null after " + i);
super.onBackPressed();
return;
Edit 2:
Content of cancelTask()
private void cancelTask() {
if (meTask != null) {
Log.d(TAG, "Async Task Cancel... ");
meTask.cancel(true);
}
}
Edit 3:
Regardless of where I put the cancel(true), e.g. onPause, onStop, the Task stays alive and only gives me the Log output of
if (isCancelled()) {
Log.d(TAG, "Cancel after XYZ");
return "cancelled";
}
and
#Override
protected void onCancelled() {
super.onCancelled();
isRunning = false;
Log.d(TAG, "...Async Task Cancelled ");
}
at the very end, after onPause, onStop, onDestroy have finished. I am at my wits end. It is not surprising that the Task continues to run for a bit, but it must stop before the service in unbound. How can I wait for the task to finish?
When I'm using Places.GeoDataApi.getPlaceById() LeakCanary detects that an instance of my Activity has leaked.
Here is what LeakCanary reports
* com.mypackage.PlaceSearchActivity has leaked:
* GC ROOT com.google.android.gms.location.places.zzl.mContext
* leaks com.mypackage.PlaceSearchActivity instance
And this is my code. Both methods are declared within my PlaceSearchActivity.
void lookupLatLng(final Location location, final int followUpAction) {
Task.callInBackground(new Callable<LatLng>() {
#Override
public LatLng call() throws Exception {
PlaceBuffer places = Places.GeoDataApi.getPlaceById(
getGoogleApiClient(), location.getPlaceId()).await();
LatLng result = places.get(0).getLatLng();
places.release();
return result;
}
}).continueWithTask(new Continuation<LatLng, Task<Void>>() {
#Override
public Task<Void> then(Task<LatLng> task) throws Exception {
if (task.isFaulted()) {
// TODO Place lookup failed
} else if (task.isCompleted()) {
switch (followUpAction) {
case ACTION_PICK:
location.setLatLngPoint(LatLngPoint.from(task.getResult()));
onLocationSelected(location);
break;
}
}
return task.makeVoid();
}
}, Task.UI_THREAD_EXECUTOR);
}
void onLocationSelected(Location location) {
Intent resultIntent = new Intent();
resultIntent.putExtra(EXTRA_RESULT, Parcels.wrap(location));
setResult(RESULT_OK, resultIntent);
ActivityCompat.finishAfterTransition(this);
}
I'm not sure what is causing the problem. Any help is appreciated. Thanks!
UPDATE: Memory leak is also detected with folowing code.
void lookupLatLng(Location location) {
Places.GeoDataApi.getPlaceById(getGoogleApiClient(), location.getPlaceId()).setResultCallback(
new ResultCallback<PlaceBuffer>() {
#Override
public void onResult(PlaceBuffer places) {
Place place = places.get(0);
LatLng latLng = place.getLatLng();
places.release();
}
}
);
}
It might be an issue within PlayServices library. I'm going to test this on a empty project just to verify the issue.
I have just started exploring google-play-services-turnbased APIs. Till now I have been successful in creating a match. But from the documentation I haven't been able to figure out how to player's score after he completes his turn.
This is my onClickStartMatch method.
public void onStartMatchClicked() {
Intent intent =
Games.TurnBasedMultiplayer.getSelectOpponentsIntent(mHelper.getApiClient(), 1, 7, true);
startActivityForResult(intent, RC_SELECT_PLAYERS);
}
This is my onActivityResult method in my main activity class.
if (request == RC_SELECT_PLAYERS) {
if (response != RESULT_OK) {
// user canceled
return;
}
// Get the invitee list.
final ArrayList<String> invitees =
data.getStringArrayListExtra(Games.EXTRA_PLAYER_IDS);
// Get auto-match criteria.
Bundle autoMatchCriteria = null;
int minAutoMatchPlayers = data.getIntExtra(
Multiplayer.EXTRA_MIN_AUTOMATCH_PLAYERS, 0);
int maxAutoMatchPlayers = data.getIntExtra(
Multiplayer.EXTRA_MAX_AUTOMATCH_PLAYERS, 0);
if (minAutoMatchPlayers > 0) {
autoMatchCriteria = RoomConfig.createAutoMatchCriteria(
minAutoMatchPlayers, maxAutoMatchPlayers, 0);
} else {
autoMatchCriteria = null;
}
TurnBasedMatchConfig tbmc = TurnBasedMatchConfig.builder()
.addInvitedPlayers(invitees)
.setAutoMatchCriteria(autoMatchCriteria)
.build();
// Create and start the match.
Games.TurnBasedMultiplayer
.createMatch(mHelper.getApiClient(), tbmc)
.setResultCallback(new MatchInitiatedCallback());
}
This is my MatchInitiatedCallback class
public class MatchInitiatedCallback implements
ResultCallback<TurnBasedMultiplayer.InitiateMatchResult>,OnTurnBasedMatchUpdateReceivedListener {
#Override
public void onResult(TurnBasedMultiplayer.InitiateMatchResult result) {
// Check if the status code is not success.
Status status = result.getStatus();
if (status.isSuccess()) {
Log.d("turnbased","Turn Based Match Initiated successfully with result: "+status.getStatusMessage());
return;
}
TurnBasedMatch match = result.getMatch();
// If this player is not the first player in this match, continue.
if (match.getData() != null) {
showTurnUI(match);
return;
}
// Otherwise, this is the first player. Initialize the game state.
initGame(match);
// Let the player take the first turn
showTurnUI(match);
}
public void showTurnUI(TurnBasedMatch match){
if(match.getStatus() == TurnBasedMatch.MATCH_STATUS_ACTIVE){
if(match.getTurnStatus() == TurnBasedMatch.MATCH_TURN_STATUS_MY_TURN){
turnBasedMatchData = match.getData();
Games.TurnBasedMultiplayer.takeTurn(mHelper.getApiClient(),match.getMatchId(), "score:400".getBytes(Charset.forName("UTF-16")),null).setResultCallback(updateMatchResult());
}
}
}
public void initGame(TurnBasedMatch match){
Games.TurnBasedMultiplayer.takeTurn(mHelper.getApiClient(),match.getMatchId(),"score:605".getBytes(Charset.forName("UTF-16")),match.getParticipantId(Games.Players.getCurrentPlayerId(mHelper.getApiClient()))).setResultCallback(updateMatchResult());
}
public ResultCallback<TurnBasedMultiplayer.UpdateMatchResult> updateMatchResult(){
return null;
}
#Override
public void onTurnBasedMatchReceived(TurnBasedMatch turnBasedMatch) {
Log.d("turn-based","Player played his turn");
}
#Override
public void onTurnBasedMatchRemoved(String s) {
}
}
}
Also it would helpful if some can properly explain how to continue a game a game from start and when to submit score and how.
Figured it out. This is how you can do it.
public byte[] persist() {
JSONObject retVal = new JSONObject();
try {
retVal.put("turnCounter", 2);
retVal.put("score1",100);
retVal.put("score2",200);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String st = retVal.toString();
Log.d(TAG, "==== PERSISTING\n" + st);
return st.getBytes(Charset.forName("UTF-8"));
}
Games.TurnBasedMultiplayer.takeTurn(mHelper.getApiClient(),match.getMatchId(),persist(),null).setResultCallback(updateMatchResult());
I am pulling my hair out! At one point in the last week, I had this working.
I have an Android app that I am trying to add in-ap billing to. I followed the sample TrivialDrive, and my code worked a few times. Now it doesn't.
I am creating a simple trivia game that has a number of free questions, and the option to upgrade to get more questions. When the user completes the list of free questions, they are taken to a "Game Over" screen where they can erase their answers and start again, or upgrade.
When I click the "Upgrade" button, I can make a successful purchase, but as soon as the Google "Payment Successful" dialog goes away, my activity is destroyed and I am sent back to my main activity.
When I try to go back and do my purchase again, my code catches the error ("You already own this item") and handles it appropriately. My code explains to the user that they already own the upgrade, and allows them to click a button to continue playing. So it looks like the OnIabPurchaseFinishedListener is firing at this point.
I have updated the Google helper code with the latest files.
Any help or suggestions as to where to look for answers is much appreciated.
Thanks.
This is the relevant code for my activity:
public class GameOverActivity extends BaseActivity
{
private IabHelper mHelper;
private String m_base64EncodedPublicKey;
private static String THE_UPGRADE_SKU = "upgrade52";
public static int BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED = 7;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game_over);
setTitle("Game Over");
Button butPlay = (Button) findViewById(R.id.buttonPlay);
butPlay.setVisibility(View.INVISIBLE);
PrepareIAB();
}
#Override
protected void onResume()
{
super.onResume();
CURRENT_ACTIVITY = ACTIVITY_GAME_OVER;
SetMainText();
}
#Override
protected void onDestroy()
{
super.onDestroy();
try
{
if (mHelper != null)
{
mHelper.dispose();
mHelper = null;
}
}
catch (Exception e)
{
}
}
private void PrepareIAB()
{
m_base64EncodedPublicKey = "MyKey";
// compute your public key and store it in base64EncodedPublicKey
mHelper = new IabHelper(this, m_base64EncodedPublicKey);
mHelper.enableDebugLogging( true, TAG);
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener()
{
public void onIabSetupFinished(IabResult result)
{
if (!result.isSuccess())
{
ShowMessage("There was an error connecting to the Google Play Store.");
}
}
});
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
try
{
// Pass on the activity result to the helper for handling
if (!mHelper.handleActivityResult(requestCode, resultCode, data))
{
// not handled, so handle it ourselves (here's where you'd
// perform any handling of activity results not related to in-app
// billing...
super.onActivityResult(requestCode, resultCode, data);
}
else
{
// Log.d(TAG, "onActivityResult handled by IABUtil.");
}
}
catch (Exception e)
{
super.onActivityResult(requestCode, resultCode, data);
}
}
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener =
new IabHelper.OnIabPurchaseFinishedListener()
{
public void onIabPurchaseFinished(IabResult result, Purchase purchase)
{
try
{
if (result.isFailure())
{
if (result.mResponse==7)
{
UpgradeComplete();
ShowMessage("Thank you for upgrading.\r\n\r\nThis version has 400 more questions.");
}
else
{
ShowMessage("Error purchasing: " + String.valueOf(result.mResponse));
UpgradeError();
return;
}
}
else if (purchase.getSku().equals(THE_UPGRADE_SKU))
{
UpgradeComplete();
ShowMessage("Thank you for upgrading.\r\n\r\nThis version has 400 more questions.");
}
else
{
ShowMessage("Something else happened. ");
}
}
catch (Exception e)
{
Log.e(TAG, e.getLocalizedMessage());
}
}
};
private void HideUpgrade()
{
try
{
Button btnUpgrade = (Button) findViewById(R.id.buttonUpgrade);
if (btnUpgrade != null)
{
btnUpgrade.setVisibility(View.INVISIBLE);
}
TextView txtMessage = (TextView) findViewById(R.id.txtUpgradeFromGameOver);
if (txtMessage!=null)
{
txtMessage.setVisibility(View.INVISIBLE);
}
}
catch (Exception e)
{
}
}
public void onQuitButtonClick(View view)
{
finish();
}
public void onResetDBButtonClick(View view)
{
ConfirmResetDatabase();
}
private void ConfirmResetDatabase()
{
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener()
{
#Override
public void onClick(DialogInterface dialog, int which)
{
switch (which)
{
case DialogInterface.BUTTON_POSITIVE:
ResetDatabase();
Intent gameActivity = new Intent(getApplicationContext(), GameActivity.class);
gameActivity.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
// startActivityForResult(gameActivity, ACTIVITY_GAME);
startActivity(gameActivity);
break;
case DialogInterface.BUTTON_NEGATIVE:
// No button clicked
break;
}
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Do you want to erase your score and start over?").setPositiveButton("Yes", dialogClickListener).setNegativeButton("No", dialogClickListener).show();
}
public void onUpgradeButtonClick(View view)
{
try
{
if (mHelper != null)
{
mHelper.launchPurchaseFlow(this, THE_UPGRADE_SKU, 10001, mPurchaseFinishedListener, m_TriviaAppInstance.AppInstallID());
}
else
{
ShowMessage("Unable to connect to Google Play Store.");
}
}
catch (Exception e)
{
ShowMessage("Unable to connect to Google Play Store.");
SendErrorMessage(e.getLocalizedMessage());
}
}
private void UpgradeComplete()
{
try
{
HideUpgrade();
Button butPlay = (Button) findViewById(R.id.buttonPlay);
if (butPlay!=null)
{
butPlay.setVisibility(View.VISIBLE);
}
TextView txtReset = (TextView) findViewById(R.id.txtGameOverRestDB);
if (txtReset!=null)
{
txtReset.setVisibility(View.INVISIBLE);
}
Button btnReset = (Button)findViewById(R.id.buttonResetDB);
if (btnReset!=null)
{
btnReset.setVisibility(View.INVISIBLE);
}
m_TriviaAppInstance.SetUpgradedStatus(true);
}
catch (Exception e)
{
}
//
}
private void UpgradeError()
{
try
{
Button butUpgrade;
butUpgrade = (Button) findViewById(R.id.buttonUpgrade);
butUpgrade.setVisibility(View.INVISIBLE);
TextView txtMessage = (TextView) findViewById(R.id.txtUpgradeScreen);
txtMessage.setText(R.string.upgradeScreenTextError);
}
catch (Exception e)
{
}
}
public void onPlayButtonClick(View view)
{
Intent myIntent = new Intent(view.getContext(), GameActivity.class);
myIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
startActivityForResult(myIntent, ACTIVITY_GAME);
}
public void SetMainText()
{
TextView txt = (TextView) findViewById(R.id.txtScoreGlobal);
txt.setText(Integer.toString(m_TriviaAppInstance.getGlobal()) + "%");
SetPlayerScore(1);
if (m_TriviaAppInstance.getUpgradedStatus() == true)
{
HideUpgrade();
}
}
}
FYI: I think I have this figured out - for anyone else that may come across it.
The activity that I was using to launch "In App Billing" was called with a "FLAG_ACTIVITY_NO_HISTORY". I did this because I didn't want the user to be able to click to go back to this "Game Over" activity.
BUT, this causes grief with "In App Billing". So, make sure you don't try to launch "In App Billing" from an activity that has had the "FLAG_ACTIVITY_NO_HISTORY" set.
My original code:
private void GameOver()
{
m_TriviaAppInstance.setGameOver(true);
Intent gameOver = new Intent(getApplicationContext(), GameOverActivity.class);
gameOver.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
startActivity(gameOver);
}
Updated code:
private void GameOver()
{
m_TriviaAppInstance.setGameOver(true);
Intent gameOver = new Intent(getApplicationContext(), GameOverActivity.class);
startActivity(gameOver);
}
Peace
I'm not high enough to comment, but bless you. I had
android:noHistory="true"
set in AndroidManifest.xml for my activity and was experiencing the same problem.
Took it out and IAB is working. Yay!
Do not forget that your IabHelper.OnIabPurchaseFinishedListener is called on a different thread and before onResume() is called on your Activity!
So your UpgradeComplete() or UpgradeError() can cause a crash on older devices (Crashed every time on my Gingerbread Sony Xperia Mini Pro and worked without any trouble on Samsung Galaxy S4 (Android 4.2.2)
Caused a 3 day delay on my game..
I have a Form with edittexts and a button to call the camera with an intent (return a bitmap that is put into the imageview)...From the portrait mode i enter all edittext filed and then click the camera button which forwards me to the camera - in the camera i take a picture after what I get returned to Activity 1 (staying in portrait orientation - and all editext fields are restore in onRestoreInstanceState()) - and the last callback method of Activity 1 is onResume() (what is ok) - But the problem comes when I make an orientation change from this portrait to landscape mode - the callback methods are following
So the last callback orientation change is onPause(). I do not understand why? The problem is that onSaveInstanceState is called prior of onPause - so when I turn back to portrait mode everything will be empty (editexts, imageview..) - this strange behavior continues on every orientation change (the onPause() is called last).
I am sure this problem has to do something with the taking an image (startInentforResult....) because everything (editext fields) works fine on orientation change prior to taking an image...sometimes I can also take an image and it works fine, but in most cases not...
So my question is what is it that "drives" my Activity up to the onPause() method instead up to the onResume()?
Thanks, I would really appreciate if somebody knows the solution because I am struggling with this already a few days and could not find the solution.
The project has many classes but this is the activity code (Important to note is that the problem arises only when I take an image from camera app, after that the activity lifecycle goes crazy - also this activity is called from the main activity with 'startIntentforResult()'. I do not use 'android:configChanges="orientation|keyboardHidden"' to stop the recreatioin ):
public class NewCounterActivity extends Activity {
Button btnCreate;
Button btnCancel;
Button btnTakeImg;
ImageView counterImgView;
CheckBox existsDamage;
EditText inputNameFirst;
EditText inputNameLast;
EditText inputAdresse;
EditText inputCounterID;
EditText inputCounterValue;
EditText inputDescription;
TextView registerErrorMsg;
DatabaseHandler db;
//Data to be submitted
String nameFirst;
String nameLast;
String adresse;
String counterID;
String counterValue;
String countDescript;
String existsDmg;
Bitmap counterBitmap;
Bitmap recievedBitmap;
String longitude;
String latitude;
LocationTracker gps;
// JSON Response node names
private static String KEY_SUCCESS = "success";
private static String KEY_ERROR = "error";
private static String KEY_ERROR_MSG = "error_msg";
//The dimensions of the ImageView
int targetW;
int targetH;
// Some lifecycle callbacks so that the image can survive orientation change
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.e("onSaveInstanceState", "fadsfass");
outState.putParcelable("bitmap", counterBitmap);
outState.putString("fname", inputNameFirst.getText().toString());
outState.putString("lname", inputNameLast.getText().toString());
outState.putString("adrese", inputAdresse.getText().toString());
outState.putString("cID", inputCounterID.getText().toString());
outState.putString("cValue", inputCounterValue.getText().toString());
outState.putString("Descript", inputDescription.getText().toString());
outState.putString("ErrorMsg", registerErrorMsg.getText().toString());
outState.putBoolean("damageCheck", existsDamage.isChecked());
((MyApplicationClass) getApplication()).detach(this);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
Log.e("onRestoreInstanceState", "fadsfass");
counterBitmap = savedInstanceState.getParcelable("bitmap");
counterImgView.setImageBitmap(counterBitmap);
inputNameFirst.setText(savedInstanceState.getString("fname"));
inputNameLast.setText(savedInstanceState.getString("lname"));
inputAdresse.setText(savedInstanceState.getString("adrese"));
inputCounterID.setText(savedInstanceState.getString("cID"));
inputCounterValue.setText(savedInstanceState.getString("cValue"));
inputDescription.setText(savedInstanceState.getString("Descript"));
registerErrorMsg.setText(savedInstanceState.getString("ErrorMsg"));
existsDamage.setChecked(savedInstanceState.getBoolean("damageCheck"));
((MyApplicationClass) getApplication()).attach(this);
}
#Override
public void onContentChanged() {
// TODO Auto-generated method stub
super.onContentChanged();
Log.e("onContetnChanged", "fadsfass");
}
#Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
Log.e("onDestroy", "fadsfass");
}
#Override
public void onDetachedFromWindow() {
// TODO Auto-generated method stub
super.onDetachedFromWindow();
Log.e("onDetachedFromWindow", "fadsfass");
}
#Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
Log.e("onPause", "fadsfass");
}
#Override
protected void onRestart() {
// TODO Auto-generated method stub
super.onRestart();
Log.e("onRestart", "fadsfass");
}
#Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
Log.e("onResume", "fadsfass");
}
#Override
protected void onStart() {
// TODO Auto-generated method stub
super.onStart();
Log.e("onStart", "fadsfass");
}
#Override
protected void onStop() {
// TODO Auto-generated method stub
super.onStop();
Log.e("onStop", "fadsfass");
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.newcounteractivity_layout);
Log.e("onCreate", "mActivity equals NULL");
inputNameFirst = (EditText) findViewById(R.id.createFirstName);
inputNameLast = (EditText) findViewById(R.id.createLastName);
inputAdresse = (EditText) findViewById(R.id.createAdresse);
inputCounterID = (EditText) findViewById(R.id.createCounterID);
inputCounterValue = (EditText) findViewById(R.id.createCounterValue);
inputDescription = (EditText) findViewById(R.id.createDescription);
registerErrorMsg = (TextView) findViewById(R.id.create_error);
btnCreate = (Button) findViewById(R.id.btnCreate);
btnCancel = (Button) findViewById(R.id.btnCancel);
btnTakeImg = (Button) findViewById(R.id.btnImage);
counterImgView = (ImageView) findViewById(R.id.counterImgView);
existsDamage = (CheckBox) findViewById(R.id.createDamageExists);
//REGISTER BUTTON CLICK EVENTS
btnCreate.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
//new DoBackgroundTask(NewCounterActivity.this).execute();
//CounterUser data to submit
nameFirst = inputNameFirst.getText().toString().trim();
nameLast = inputNameLast.getText().toString().trim();
adresse = inputAdresse.getText().toString().trim();
counterID = inputCounterID.getText().toString().trim();
counterValue = inputCounterValue.getText().toString().trim();
countDescript = inputDescription.getText().toString().trim();
existsDmg = Integer.toString((existsDamage.isChecked()) ? 1 : 0);
// create LocationTracker class object
gps = new LocationTracker(NewCounterActivity.this);
if(!gps.canGetLocation()){
gps.stopUsingGPS();
gps.showSettingsAlert();
//Ovo se mozda treba i izbaciti
gps.getLocation();
}
else{
processInput();
}
}
});
btnCancel.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Intent returnIntent = new Intent();
setResult(RESULT_CANCELED, returnIntent);
finish();
}
});
btnTakeImg.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if(isIntentAvailable(NewCounterActivity.this, MediaStore.ACTION_IMAGE_CAPTURE)){
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(takePictureIntent,2);
}
else {
Toast.makeText(NewCounterActivity.this, "No Camera Available", Toast.LENGTH_SHORT).show();
}
}
});
}
/************************************************************************************************
* Methods used in this class
* */
public void processInput(){
//Get current Longitude and Latitude
longitude = Double.toString(gps.getLongitude());
latitude = Double.toString(gps.getLatitude());
//Na kraju iskljuci location updatese - ne moze na emulatru jer ja emit coordinate preko DDMS... a kad emit on mora biti ukljucen da bi primio
//gps.stopUsingGPS();
Toast.makeText(getApplicationContext(), "Your Location is - \nLat: " + longitude + "\nLong: " + latitude, Toast.LENGTH_LONG).show();
if (!nameFirst.equals("") && !nameLast.equals("") && !adresse.equals("") && !counterID.equals("") && !counterValue.equals("")
&& counterBitmap != null ){
new DoBackgroundTask(NewCounterActivity.this).execute();
}
else{
// Not all fields are filled
registerErrorMsg.setText("Not all fields are filled");
}
}
//Method to check whether an app can handle your intent
public boolean isIntentAvailable(Context context, String action) {
final PackageManager packageManager = context.getPackageManager();
final Intent intent = new Intent(action);
List<ResolveInfo> list = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
return list.size() > 0;
}
/**************************************************************************************************
*
* When the calling activity, Activity #1, resumes after having called another activity, Activity #2, using startActivityForResult,
* the method onActivityResult in Activity #1 is called BEFORE onResume.
* This is important to know if you are instantiating your SQLite Database objects from within onResume in Activity #1. If so, you will also need to instantiate the object from within onActivityResult,
* when returning from Activity #2.
*
* startActivityForResult() is asynchronous. It can feel synchronous to the user since the UI will change and your calling activity will be paused
* (your onPause() method will be called).
*/
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// TODO Auto-generated method stub
super.onActivityResult(requestCode, resultCode, data);
Log.e("onActivityResult", "fadsfass");
if (requestCode == 2) {
if(resultCode == RESULT_OK){
Bundle extras = data.getExtras();
recievedBitmap = (Bitmap) extras.get("data");
}
if (resultCode == RESULT_CANCELED) {
Toast.makeText(NewCounterActivity.this, "No Image Taken", Toast.LENGTH_SHORT).show();
}
}
}
/**
* Koristim onWindowFocusChanged jer kad se vratim na Activity 1 onda dodje do potpunog recreate Activitija i getWidth()/height() ne mogu dobiti
* ni u jednom od lifecicle methoda - naime ide start onCreate,...onActivityResult(), onResume() - u onactivityResult izvadim bitmap i pohranim ga u receivedBitmap
* te kad getWidth() postane dostupan system invoke ovu dole methodu. :D
*/
#Override
public void onWindowFocusChanged(boolean hasFocus){
if(recievedBitmap != null){
targetW=counterImgView.getWidth();
targetH=counterImgView.getHeight();
Log.e("onWindowFocusChanged", "fadsfass" + " " + targetW + " " + targetH);
// http://stackoverflow.com/questions/4837715/how-to-resize-a-bitmap-in-android
// http://sunil-android.blogspot.com/2013/03/resize-bitmap-bitmapcreatescaledbitmap.html
// Scale or resize Bitmap to ImageView dimensions
counterBitmap = Bitmap.createScaledBitmap(recievedBitmap, targetW, targetH, false);
/**
* Canvas: trying to use a recycled bitmap android.graphics - This exception occurs when you try to recycle a bitmap which is already recycled.
* http://androdevvision.blogspot.com/2011/10/solution-for-out-of-memory-error-and.html
*/
if(recievedBitmap != null && !recievedBitmap.isRecycled()){
recievedBitmap.recycle();
recievedBitmap = null;
}
counterImgView.setImageBitmap(counterBitmap);
}
}
/************************************************************************************************
* Background AsyncTask to create new counterUser - https://github.com/callorico/CustomAsyncTask - najbolje radi
* new DoBackgroundTask(NewCounterActivity.this).execute();
* */
private static class DoBackgroundTask extends CustomAsyncTask<Void, Integer, JSONObject> {
private static final String TAG = "DoBackgroundTask";
private ProgressDialog mProgress;
private int mCurrProgress;
private NewCounterActivity myActivity = null;
public DoBackgroundTask(NewCounterActivity activity) {
super(activity);
}
#Override
protected void onPreExecute() {
super.onPreExecute();
showProgressDialog();
}
#Override
protected void onActivityDetached() {
if (mProgress != null) {
mProgress.dismiss();
mProgress = null;
}
}
#Override
protected void onActivityAttached() {
showProgressDialog();
}
private void showProgressDialog() {
mProgress = new ProgressDialog(mActivity);
mProgress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
mProgress.setIndeterminate(true);
mProgress.setMessage(" Saljem na server... ");
mProgress.setCancelable(true);
mProgress.setOnCancelListener(new OnCancelListener() {
#Override
public void onCancel(DialogInterface dialog) {
cancel(true);
}
});
mProgress.show();
mProgress.setProgress(mCurrProgress);
}
#Override
protected JSONObject doInBackground(Void... params) {
//so you need to either pass an instance of the outer class to the inner class method (or its constructor) as a parameter,
//or create it inside the method.
JSONObject json = null;
if(mActivity != null){
myActivity = (NewCounterActivity) mActivity;
//Prepare counterBitmap as String
ByteArrayOutputStream stream = new ByteArrayOutputStream();
//Write a compressed version of the bitmap to the specified output stream.
myActivity.counterBitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream);
byte [] b_array = stream.toByteArray();
String bitmapString = Base64.encodeBytes(b_array);
//Get workerId from logged worker
Functions workerFunction = new Functions();
DatabaseHandler db = new DatabaseHandler(mActivity);
String workerID = db.retrieveWorker().get("workerId");
if(myActivity != null){
//Get JsonObject from Functions.java
json = workerFunction.newCounterUser(myActivity.counterID, myActivity.counterValue, myActivity.adresse, myActivity.nameFirst, myActivity.nameLast, bitmapString, myActivity.existsDmg, myActivity.countDescript, workerID, myActivity.longitude, myActivity.latitude);
}
}
return json;
}
#Override
protected void onPostExecute(JSONObject jsonObject) {
super.onPostExecute(jsonObject);
if (mActivity != null) {
mProgress.dismiss();
try {
if (jsonObject.getString(KEY_SUCCESS) != null) {
myActivity.registerErrorMsg.setText("");
String res = jsonObject.getString(KEY_SUCCESS);
if(Integer.parseInt(res) == 1){
// counterUser successfully registered
Toast.makeText(mActivity, "New counterUser is created", Toast.LENGTH_LONG).show();
// Return back to MainActivity
Intent returnIntent = new Intent();
returnIntent.putExtra("result",jsonObject.toString());
mActivity.setResult(RESULT_OK,returnIntent);
// Close all views before launching MainActivity
returnIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
mActivity.finish();
}else{
// Error in registration
myActivity.registerErrorMsg.setText("Error occured in registration");
}
}
} catch (JSONException e) {
Log.e("Error","NO Json at all");
e.printStackTrace();
}
} else {
Log.d(TAG, "AsyncTask finished while no Activity was attached.");
}
}
}
Same issues on call to recreate(), when activity has been updated it get onPause after onResume. Tested on emulator, bug exists on Marshallow and below.
This is my fix
private static boolean isRecreate = false;
private void reCreateActivity() {
isRecreate = true;
recreate();
}
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if(isRecreate) {
isRecreate = false;
runOnUiThread(new Runnable() {
#Override
public void run() {
onResume();
}
});
}
}
I don't know if that's correct, but it works.
EDIT
Best solution to avoid this issues, call recreate in postDelayed with 0 delayMillis
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
recreate();
}
}, 0);
It is only normal, that strange code produces strange behavior ...
replace this line:
((MyApplicationClass) getApplication()).detach(this);
with this line:
super.onSaveInstanceState(outState);