Search code examples
androidkotlinandroid-intentandroid-lifecycle

Android - Inconsistent finish() behavior in onCreate


I would like to create an Android application that is capable of setting timer and that would handle implicit intent AlarmClock.ACTION_SET_TIMER. This also will allow it to react to Google assistant 'Set timer for' command.

Upon receiving this special intent i would like the app to set timer without showing the activity (to respect AlarmClock.EXTRA_SKIP_UI).

If the application was not launched when I use Google Assistant to set timer, it works as expected: the activity is not shown, but the code to set the timer gets executed.

However, if I launch my activity with launcher, then use home button to return to the main screen (i.e. the activity can still be found in the recents), the behavior is wrong. The activity is displayed, the onStart() method is called (even though I call finish() from onCreate).

As far as I understand from the documentation, if I call finish() from onCreate no other lifecycle callbacks should be invoked, and the activity should not be displayed.

What's even more confusing, apparently onCreate and onStart see different intents.

From AndroidManifest.xml:

<activity android:name="com.example.testlifecycle.MainActivity">
  <intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
  </intent-filter>

  <intent-filter>
    <action android:name="android.intent.action.SET_TIMER" />
    <category android:name="android.intent.category.DEFAULT" />
  </intent-filter>
</activity>

MainActivity.kt:

class MainActivity : AppCompatActivity() {
    companion object {
        const val TAG = "MainActivity"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        Log.d(TAG, "onCreate called")
        super.onCreate(savedInstanceState)
        logIntent()

        if (intent.action == AlarmClock.ACTION_SET_TIMER) {
            Log.d(TAG, "Timer intent received, closing")
            // Do something useful here
            setResult(Activity.RESULT_OK)
            finish()
            return
        }

        setContentView(R.layout.activity_main)
    }

    override fun onStart() {
        Log.d(TAG,"onStart called")
        super.onStart()
        logIntent()
    }

    private fun logIntent() {
        Log.d(TAG, "Intent action ${intent.action}")
        Log.d(TAG, "Intent flags ${intent.flags.toString(16)}")

        intent.extras?.keySet()?.forEach {
            Log.d(TAG, "Intent extra $it = ${intent.extras?.get(it)}")
        }
    }
}

Scenario 1

Application is not launched, use Google Assistant to set timer.

2020-02-02 18:21:28.376 22269-22269/com.example.testlifycycle D/MainActivity: onCreate called
2020-02-02 18:21:28.408 22269-22269/com.example.testlifycycle D/MainActivity: Intent action android.intent.action.SET_TIMER
2020-02-02 18:21:28.409 22269-22269/com.example.testlifycycle D/MainActivity: Intent flags 10000000
2020-02-02 18:21:28.409 22269-22269/com.example.testlifycycle D/MainActivity: Intent extra android.intent.extra.alarm.SKIP_UI = true
2020-02-02 18:21:28.409 22269-22269/com.example.testlifycycle D/MainActivity: Intent extra com.google.android.apps.gsa.shared.util.starter.IntentStarter.ERROR_TOAST_ID = 2131951799
2020-02-02 18:21:28.410 22269-22269/com.example.testlifycycle D/MainActivity: Intent extra android.intent.extra.REFERRER_NAME = android-app://com.google.android.googlequicksearchbox/https/www.google.com
2020-02-02 18:21:28.410 22269-22269/com.example.testlifycycle D/MainActivity: Intent extra android.intent.extra.alarm.LENGTH = 3600
2020-02-02 18:21:28.410 22269-22269/com.example.testlifycycle D/MainActivity: Intent extra KEY_HANDOVER_THROUGH_VELVET = true
2020-02-02 18:21:28.410 22269-22269/com.example.testlifycycle D/MainActivity: Timer intent received, closing

Here everything works as expected.

Scenario 2

Application is started by launcher, press Home button and then use Google Assistant to set timer.

Start the app with launcher, looks as expected:

2020-02-02 18:24:30.052 22269-22269/com.example.testlifycycle D/MainActivity: onCreate called
2020-02-02 18:24:30.055 22269-22269/com.example.testlifycycle D/MainActivity: Intent action android.intent.action.MAIN
2020-02-02 18:24:30.055 22269-22269/com.example.testlifycycle D/MainActivity: Intent flags 10200000
2020-02-02 18:24:30.055 22269-22269/com.example.testlifycycle D/MainActivity: Intent extra profile = 0
2020-02-02 18:24:30.158 22269-22269/com.example.testlifycycle D/MainActivity: onStart called
2020-02-02 18:24:30.158 22269-22269/com.example.testlifycycle D/MainActivity: Intent action android.intent.action.MAIN
2020-02-02 18:24:30.158 22269-22269/com.example.testlifycycle D/MainActivity: Intent flags 10200000
2020-02-02 18:24:30.158 22269-22269/com.example.testlifycycle D/MainActivity: Intent extra profile = 0

Press home button, and use Google Assistant:

2020-02-02 18:26:21.398 23158-23158/com.example.testlifycycle D/MainActivity: onCreate called
2020-02-02 18:26:21.402 23158-23158/com.example.testlifycycle D/MainActivity: Intent action android.intent.action.SET_TIMER
2020-02-02 18:26:21.402 23158-23158/com.example.testlifycycle D/MainActivity: Intent flags 10400000
2020-02-02 18:26:21.402 23158-23158/com.example.testlifycycle D/MainActivity: Intent extra android.intent.extra.alarm.SKIP_UI = true
2020-02-02 18:26:21.402 23158-23158/com.example.testlifycycle D/MainActivity: Intent extra com.google.android.apps.gsa.shared.util.starter.IntentStarter.ERROR_TOAST_ID = 2131951799
2020-02-02 18:26:21.403 23158-23158/com.example.testlifycycle D/MainActivity: Intent extra android.intent.extra.REFERRER_NAME = android-app://com.google.android.googlequicksearchbox/https/www.google.com
2020-02-02 18:26:21.403 23158-23158/com.example.testlifycycle D/MainActivity: Intent extra android.intent.extra.alarm.LENGTH = 60
2020-02-02 18:26:21.403 23158-23158/com.example.testlifycycle D/MainActivity: Intent extra KEY_HANDOVER_THROUGH_VELVET = true
2020-02-02 18:26:21.403 23158-23158/com.example.testlifycycle D/MainActivity: Timer intent received, closing
2020-02-02 18:26:21.446 23158-23158/com.example.testlifycycle D/MainActivity: onStart called
2020-02-02 18:26:21.446 23158-23158/com.example.testlifycycle D/MainActivity: Intent action android.intent.action.MAIN
2020-02-02 18:26:21.446 23158-23158/com.example.testlifycycle D/MainActivity: Intent flags 10200000
2020-02-02 18:26:21.447 23158-23158/com.example.testlifycycle D/MainActivity: Intent extra profile = 0

The problems I see here:

  1. onStart is called even though finsh was called from onCreate
  2. onStart logs an intent with action MAIN while onCreate logs (expected) SET_TIMER

I guess I might lack understanding on Android intent/lifecycle mechanisms. I would appreciate any help with this matter.


Solution

  • You are dealing with two difference instances of MainActivity:

    • You tap the launcher icon
    • Android creates an instance of MainActivity, and onCreate()/onStart() are called on it
    • You press home
    • You do some Assistant-y thing that triggers that SET_ALARM implicit Intent
    • Android creates a second instance of MainActivity into your existing task and brings that task to the foreground
    • That second instance of MainActivity is called with onCreate(), and there you finish() that instance
    • Since your task is in the foreground, and there is another activity in that task (the first instance of MainActivity), that activity is brought back to the foreground, and onStart() is called on it

    I cannot speak specifically to Assistant, as I avoid it entirely. However, if you want the Assistant-launched activity to be in a separate task, and not bring any existing task to the foreground, you will need to do some stuff in the manifest for that. My starting point would be to have separate activities for the separate scenarios, then use android:taskAffinity on the SET_ALARM activity to have it route to a separate task.