0

I have a strange issue which I cannot explain. In the manifest file the launch activity of my app is defined as follows:

<activity
    android:name="com.xxx.xxx.xxx.StartupActivity"
    android:label="@string/app_name"
    android:theme="@android:style/Theme.Light.NoTitleBar"
    android:screenOrientation="sensorPortrait" >
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

In the StartUpActivity the following check is performed:

protected void startIntent() {

    Intent intent;

    if (checkCurrentProfile()) {

        Notifier.showHttpToast(R.string.toastLoggedIn);

        //try to update the device token in the database
        Request.updateDeviceToken();

        intent = new Intent(this, GameListActivity.class);
    } else {
        intent = new Intent(this, RegisterActivity.class);
    }

    startActivity(intent);
    finish();
}

So if the user has a valid account the GameListActivity is shown as root activity:

<activity
    android:name="com.xxx.xxx.xxx.xxx.GameListActivity"
    android:label="@string/app_name"
    android:theme="@style/MyTheme"
    android:screenOrientation="sensorPortrait" >       
</activity>

The issue now is the following: sometimes the system brings the root activity to the front spontaneously without any user actions. It only occurs sometimes, but I can't figure out the cause. Can anyone help me out here?

The StartUpActivity looks like this:

public class StartupActivity extends StartupCoreActivity implements OnRegisterGCMListener {

private IabHelper mHelper;
private IabHelper.QueryInventoryFinishedListener mQueryInventoryFinishedListener;

@Override
protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    registerOnRegisterGCMListener(this);

    if (!registerGCMneeded()) {
        initInAppBilling();
    }
}

@Override
public void registerGCMfinished() {
    initInAppBilling();
}

private void initInAppBilling() {

    boolean hasPremium = Prefs.getBoolValue(getResources().getString(R.string.pref_key_upgrade_premium), false);

    if (hasPremium) {
        //unlocking contents not needed
        startIntent();
    } else {

        Prefs.storeValue(getResources().getString(R.string.pref_key_upgrade_premium), false);

        mHelper = new IabHelper(this, C.Billing.BILLING_KEY);

        mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {

            public void onIabSetupFinished(IabResult result) {

                if (result.isSuccess()) {
                    queryInventory();
                } else {
                    startIntent();
                }
            }
        });
    }
}

private void queryInventory() {

    String[] products = {C.Billing.ITEM_SKU};

    mQueryInventoryFinishedListener = new IabHelper.QueryInventoryFinishedListener() {

        @Override
        public void onQueryInventoryFinished(IabResult result, Inventory inv) {

            if (result.isSuccess()) {
                checkPremiumVersion(inv);
            } else {
                startIntent();
            }
        }
    };

    mHelper.queryInventoryAsync(true, Arrays.asList(products), mQueryInventoryFinishedListener);
}

private void checkPremiumVersion(Inventory inv) {

    if (inv.hasPurchase(C.Billing.ITEM_SKU)) {

        Request.updPremiumVersion();
        Prefs.storeValue(getResources().getString(R.string.pref_key_upgrade_premium), true);

        Notifier.showHttpToast(R.string.toastPremiumContentsUnlocked);  
    }

    startIntent();
}
}

And the StartupCoreActivity looks like this:

public class StartupCoreActivity extends Activity {

private final static int PLAY_SERVICES_RESOLUTION_REQUEST = xxxx;

GoogleCloudMessaging mGcm;
Context mContext;
String mRegId;

/**
 * Substitute you own sender ID here. This is the project number you got
 * from the API Console, as described in "Getting Started."
 */
String SENDER_ID = "xxxxxxxxxx";

private OnRegisterGCMListener mOnRegisterGCMListener = null;

public void registerOnRegisterGCMListener(OnRegisterGCMListener listener) {
    mOnRegisterGCMListener = listener;
}

protected boolean registerGCMneeded() {

    mContext = getApplicationContext();

    // Check device for Play Services APK.
    if (checkPlayServices()) {
        // If this check succeeds, proceed with normal processing.
        // Otherwise, prompt user to get valid Play Services APK.
        mGcm = GoogleCloudMessaging.getInstance(this);
        mRegId = getRegistrationId(mContext);

        if (mRegId.isEmpty()) {
            registerInBackground();
            return true;
        } else {
            // note we never called setContentView()
            return false;
        }
    }

    return false;
}

protected void startIntent() {

    Intent intent;

    if (checkCurrentProfile()) {

        Notifier.showHttpToast(R.string.toastLoggedIn);

        //try to update the device token in the database
        Request.updateDeviceToken();

        intent = new Intent(this, GameListActivity.class);
    } else {
        intent = new Intent(this, RegisterActivity.class);
    }

    startActivity(intent);
    finish();
}

private boolean checkCurrentProfile() {

    KUPlayer me = G.getMySelf();

    if (me.getPlayerId() <= 0) {

        //database was not present yet and has been created
        //or database was present, but profile cannot be read anymore

        // make sure Login screen appears ONLY if PlayerID cannot be retrieved anymore
        if (G.getPlayerID() <=0) {
            Prefs.storeValue(Prefs.PREF_KEY_PWD_SAVED, false);
            return false;
        }
    }

    return true;
}

// You need to do the Play Services APK check here too.
@Override
protected void onResume() {
    super.onResume();
    checkPlayServices();
}

/**
 * Check the device to make sure it has the Google Play Services APK. If
 * it doesn't, display a dialog that allows users to download the APK from
 * the Google Play Store or enable it in the device's system settings.
 */
private boolean checkPlayServices() {
    int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
    if (resultCode != ConnectionResult.SUCCESS) {
        if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
            GooglePlayServicesUtil.getErrorDialog(resultCode, this,
                    PLAY_SERVICES_RESOLUTION_REQUEST).show();
        } else {

            finish();
        }
        return false;
    }
    return true;
}

/**
 * Registers the application with mGcm servers asynchronously.
 * <p>
 * Stores the registration ID and the app versionCode in the application's
 * shared preferences.
 */
private void registerInBackground() {
    new AsyncTask<Void, Void, String>() {
        @Override
        protected String doInBackground(Void... params) {
            String msg = "";
            try {
                if (mGcm == null) {
                    mGcm = GoogleCloudMessaging.getInstance(mContext);
                }
                mRegId = mGcm.register(SENDER_ID);
                msg = "Device registered, registration ID=" + mRegId;

                // Persist the mRegId - no need to register again.
                storeRegistrationId(mContext, mRegId);
            } catch (IOException ex) {
                msg = "Error :" + ex.getMessage();
                // If there is an error, don't just keep trying to register.
                // Require the user to click a button again, or perform
                // exponential back-off.
            }
            return msg;
        }

        @Override
        protected void onPostExecute(String msg) {
            // note we never called setContentView()
            if (mOnRegisterGCMListener != null) {
                mOnRegisterGCMListener.registerGCMfinished();
            }
        }
    }.execute(null, null, null);
}

/**
 * @return Application's version code from the {@code PackageManager}.
 */
private static int getAppVersionCode(Context context) {
    try {
        PackageInfo packageInfo = context.getPackageManager()
                .getPackageInfo(context.getPackageName(), 0);
        return packageInfo.versionCode;
    } catch (NameNotFoundException e) {
        // should never happen
        throw new RuntimeException("Could not get package name: " + e);
    }
}

private static String getAppVersionName(Context context) {
    try {
        PackageInfo packageInfo = context.getPackageManager()
                .getPackageInfo(context.getPackageName(), 0);
        return packageInfo.versionName;
    } catch (NameNotFoundException e) {
        // should never happen
        throw new RuntimeException("Could not get package name: " + e);
    }
}

/**
 * Stores the registration ID and the app versionCode in the application's
 * {@code SharedPreferences}.
 *
 * @param mContext application's mContext.
 * @param mRegId registration ID
 */
private void storeRegistrationId(Context context, String regId) {

    Prefs.storeValue(Prefs.PREF_KEY_DEVICE_TOKEN, regId);
    Prefs.storeValue(Prefs.PREF_KEY_APP_VERSION_CODE, getAppVersionCode(context));
    Prefs.storeValue(Prefs.PREF_KEY_APP_VERSION_NAME, getAppVersionName(context));

    AppRate.resetAfterUpdate();
}

/**
 * Gets the current registration ID for application on mGcm service, if there is one.
 * <p>
 * If result is empty, the app needs to register.
 *
 * @return registration ID, or empty string if there is no existing
 *         registration ID.
 */
private String getRegistrationId(Context context) {

    String registrationId = Prefs.getStringValue(Prefs.PREF_KEY_DEVICE_TOKEN);
    if (registrationId.isEmpty()) {
        return "";
    }
    // Check if app was updated; if so, it must clear the registration ID
    // since the existing mRegId is not guaranteed to work with the new
    // app version.
    int registeredVersion = Prefs.getIntValue(Prefs.PREF_KEY_APP_VERSION_CODE);
    int currentVersion = getAppVersionCode(context);
    if (registeredVersion != currentVersion) {
        return "";
    }

    return registrationId;
}
}
  • Is your startIntent() called when this happens and in general when is your startIntent() method called, in which cases? – dragi Sep 16 '14 at 09:29
  • I added some more code, hopefully you can see any issue I overlooked – Jaap-Jan Hellinga Sep 16 '14 at 12:16
  • I am a little confused so could you provide more details about the problem itself? I was not able to understand which activity is started without user interaction, is it StartupActivity, or GameListActivity? And does it happen after your StartupActivity is started (i.e you start the application without doing anything else) or when your application has been closed and while you are in lets say Android's home screen? Besides that, I would suggest you make use of logging, so that you can easily trace what happens and maybe what triggers it. – dragi Sep 16 '14 at 14:25
  • Sorry for the confusion. The StartupActivity is started spontaneously sometimes when you are in Android's home screen, or when you go to home screen from any other app. What the user sees then is the GameListActivity, which is fired by the StartupActivity – Jaap-Jan Hellinga Sep 16 '14 at 14:29
  • Do you use StartupCoreActivity as a parent activity class for another application that is on the device/emulator? – dragi Sep 16 '14 at 14:34
  • No I don't use that. In fact on my Samsung Galaxy I don't have any issues, but some people are complaining about it on a Nexus 7 (grouper). So when I can't reproduce the issue, it's hard to solve – Jaap-Jan Hellinga Sep 16 '14 at 14:42
  • 1
    Is it possible that registerInBackground() takes some time to execute during which the user moves to another application (or home screen) and from onPostExecute() initInAppBilling() would be called which will start an activity? – dragi Sep 16 '14 at 14:54
  • If that would be true, how would you solve this? – Jaap-Jan Hellinga Sep 16 '14 at 15:23
  • Maybe I would add a dialog to let the user decide whether he/she wants the activity to be started at that time, but only if the application was not on focus. – dragi Sep 16 '14 at 15:32
  • The user states that it does not happen always, but when it happens it occurs when you open another app, like Google Chrome or Telephone app. What might cause this? I don't have the feeling that the registerInBackground() could have anything to do with it... – Jaap-Jan Hellinga Sep 16 '14 at 18:27
  • Well for me, it should be related to a call/execution of a method that is able to start an activity, and it could from a thread running in the background, and/or a callback, being called while your app is in background(but still not killed), which triggers starting of an activity. I don't know it this is possible at all, but you could test yourself: make a background thread sleeping for some seconds and make it start an activity after the sleep, meanwhile you will switch to another application and see the behaviour. If my assumption is wrong, then there could be some problem with Android... – dragi Sep 17 '14 at 06:29

1 Answers1

1

I guess it has to do with the following known Android bug:

How to prevent multiple instances of an activity when it is launched with different intents

And as suggested put the following code in the onCreate method of the rootActivity:

// Possible work around for market launches. See http://code.google.com/p/android/issues/detail?id=2373
    // for more details. Essentially, the market launches the main activity on top of other activities.
    // we never want this to happen. Instead, we check if we are the root and if not, we finish.
    if (!isTaskRoot()) {
        final Intent intent = getIntent();
        final String intentAction = intent.getAction(); 
        if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && intentAction != null && intentAction.equals(Intent.ACTION_MAIN)) {
            finish();
            return;       
        }
    }

I tested it and when you start your app from Google Play store in stead of launch screen, then indeed finish() is called from above code.

Community
  • 1
  • 1
  • I wasn't aware of this issue. It is helpful to know it for sure. But am I getting it wrong, or this means that when a user starts the application from the market, it will not open it? – dragi Sep 17 '14 at 12:43
  • It will be opened differently (so twice) unless you add above workaround. Can you up vote the answer plz? – Jaap-Jan Hellinga Sep 17 '14 at 13:08
  • But using this workaround, when you open the application from the market, it will be shown to the user and that there will be no more 2 instances of it? – dragi Sep 17 '14 at 13:42
  • Yes, it works perfectly, user won't notice. You can test it yourself in debug mode and see that finish is called when started from google play store, but app still launches – Jaap-Jan Hellinga Sep 17 '14 at 14:26
  • Then I guess that could be your solution :) – dragi Sep 17 '14 at 14:30