androidapp-actions

App Action "open app" with feature parameter sometimes ignores parameter


I have been going through the codelab "Extend an Android app to the Google Assistant with App Actions (Level 2)" and am working on the step "Add the Open app feature BII capability" I have added the relevant configurations to the shortcuts.xml file (see below). The following has been tested with both a real device (A Motorola Moto G Power running Android 11 with 3664M of memory) and virtual devices (A Pixel 5 running Android 12 (API 31) that was tested with both 1536MB and 4096MB of memory)

I've confirmed that this works with the App Action test tool.

If I then invoke it from the Assistant, it will start the app and show the correct information based on the feature.

If, however, I then return to the Android home screen and again invoke it from the Assistant, trying out a different feature, it will bring up the app, but often stay on the same information as before. If I make sure to close the app (by swiping it away in the app list), it will always work, but if I just return to the home screen, sometimes it works correctly and sometimes it does not.

Working on a hunch, and having only a tenuous grasp of Android, I suspected that the call to TasksActivity.onCreate() wasn't being called because the app hadn't been destroyed. Adding logging for this method, along with onStart(), onResume(), onPause(), and onStop() seems to confirm this. From what I can gather, it is in onCreate() where it builds the layout that includes the TasksFragment.

I can confirm that TasksFragment.onCreateView() is being called only when TasksActivity.onCreate() is. It seems to use activity?.intent?.extras to get the feature parameter.

At the suggestion of some friends who are better Android developers than me, they suggested that I add a TasksFragment.onResume() to catch when the view is being re-shown and to use activity?.intent?.extras again to et the feature parameter. While I can verify that it is being called when I re-invoke it from the home screen (and when onCreate()/onCreateView() is not called), it seems to retain the old value for "feature" (and, I assume, the entire old Intent).

The question is thus:

Should we be doing something in a different part of the lifecycle? Doing work somewhere besides onCreate()?

Portion of log when invoking for the first time using "show my completed tasks using AppName". Note the onCreateView and onCreate and that feature=completed_tasks.:

2022-07-12 20:07:55.912 18392-18392/com.addventure.appactions.common D/TasksFragment: >> onCreateView
2022-07-12 20:07:55.912 18392-18392/com.addventure.appactions.common D/TasksFragment: [feature=completed_tasks]
2022-07-12 20:07:55.912 18392-18392/com.addventure.appactions.common D/TasksFragment: [com.google.android.apps.gsa.shared.util.starter.IntentStarter.ERROR_TOAST_ID=2132082905]
2022-07-12 20:07:55.912 18392-18392/com.addventure.appactions.common D/TasksFragment: [extra_accl_intent=true]
2022-07-12 20:07:55.987 18392-18392/com.addventure.appactions.common D/TasksActivity: >> onCreate
2022-07-12 20:07:55.988 18392-18392/com.addventure.appactions.common D/TasksActivity: [feature=completed_tasks]
2022-07-12 20:07:55.988 18392-18392/com.addventure.appactions.common D/TasksActivity: [com.google.android.apps.gsa.shared.util.starter.IntentStarter.ERROR_TOAST_ID=2132082905]
2022-07-12 20:07:55.988 18392-18392/com.addventure.appactions.common D/TasksActivity: [extra_accl_intent=true]
2022-07-12 20:07:56.012 18392-18392/com.addventure.appactions.common D/TasksActivity: >> onStart
2022-07-12 20:07:56.018 18392-18392/com.addventure.appactions.common D/TasksActivity: >> onResume
2022-07-12 20:07:56.020 18392-18392/com.addventure.appactions.common D/TasksFragment: >> onResume
2022-07-12 20:07:56.020 18392-18392/com.addventure.appactions.common D/TasksFragment: [feature=completed_tasks]
2022-07-12 20:07:56.020 18392-18392/com.addventure.appactions.common D/TasksFragment: [com.google.android.apps.gsa.shared.util.starter.IntentStarter.ERROR_TOAST_ID=2132082905]
2022-07-12 20:07:56.020 18392-18392/com.addventure.appactions.common D/TasksFragment: [extra_accl_intent=true]

Portion of log when returning to home screen:

2022-07-12 20:09:20.073 18392-18392/com.addventure.appactions.common D/TasksActivity: >> onPause
2022-07-12 20:09:20.124 18392-18392/com.addventure.appactions.common D/TasksActivity: >> onStop

Portion of log when then re-invoking app with "show my open tasks using AppName". Note that onCreate and onCreateView are not logged, and that feature=completed_tasks even tho this should be "active_tasks" based on the invocation feature parameter:

2022-07-12 20:09:22.130 18392-18392/com.addventure.appactions.common D/TasksActivity: >> onStart
2022-07-12 20:09:22.132 18392-18392/com.addventure.appactions.common D/TasksActivity: >> onResume
2022-07-12 20:09:22.132 18392-18392/com.addventure.appactions.common D/TasksFragment: >> onResume
2022-07-12 20:09:22.133 18392-18392/com.addventure.appactions.common D/TasksFragment: [feature=completed_tasks]
2022-07-12 20:09:22.133 18392-18392/com.addventure.appactions.common D/TasksFragment: [com.google.android.apps.gsa.shared.util.starter.IntentStarter.ERROR_TOAST_ID=2132082905]
2022-07-12 20:09:22.133 18392-18392/com.addventure.appactions.common D/TasksFragment: [extra_accl_intent=true]

Relevant portion of shortcuts.xml file:

    <!--    OPEN APP FEATURE with inline inventory
        https://developers.google.com/assistant/app/reference/built-in-intents/common/open-app-feature

        Be sure to replace all occurrences of PUT_YOUR_APPLICATION_ID_HERE with the
        application ID you specified in app/build.gradle.
    -->
    <!-- Add Get Thing BII shortcuts   -->
    <shortcut
        android:shortcutId="active_tasks"
        android:shortcutShortLabel="@string/label_active"
        android:enabled="false">
        <capability-binding
            android:key="actions.intent.OPEN_APP_FEATURE">
            <parameter-binding
                android:key="feature"
                android:value="@array/active_tasks_synonyms" />
        </capability-binding>
    </shortcut>

    <shortcut
        android:shortcutId="completed_tasks"
        android:shortcutShortLabel="@string/label_completed"
        android:enabled="false">
        <capability-binding
            android:key="actions.intent.OPEN_APP_FEATURE">
            <parameter-binding
                android:key="feature"
                android:value="@array/completed_tasks_synonyms" />
        </capability-binding>
    </shortcut>

    <!-- Add Open App Feature BII capability   -->
    <capability android:name="actions.intent.OPEN_APP_FEATURE">
        <intent
            android:action="android.intent.action.VIEW"
            android:targetPackage="com.addventure.appactions.common"
            android:targetClass="com.example.android.architecture.blueprints.todoapp.tasks.TasksActivity">
            <parameter
                android:name="feature"
                android:key="feature"/>
        </intent>
    </capability>

Relevant portion of TasksActivity.kt:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.tasks_act)
        setupNavigationDrawer()
        setSupportActionBar(findViewById(R.id.toolbar))

        // Logging for troubleshooting purposes
        log(TAG, "onCreate", intent)

        val navController: NavController = findNavController(R.id.nav_host_fragment)
        appBarConfiguration =
            AppBarConfiguration.Builder(R.id.tasks_fragment_dest, R.id.statistics_fragment_dest)
                .setOpenableLayout(drawerLayout)
                .build()
        setupActionBarWithNavController(navController, appBarConfiguration)
        findViewById<NavigationView>(R.id.nav_view)
            .setupWithNavController(navController)
    }

    override fun onStart() {
        super.onStart()
        log(TAG, "onStart")
    }

    override fun onResume() {
        super.onResume()
        log( TAG, "onResume")
    }

    override fun onPause() {
        super.onPause()
        log( TAG, "onPause" )
    }

    override fun onStop() {
        super.onStop()
        log( TAG, "onStop" )
    }

    override fun onDestroy(){
        super.onDestroy()
        log( TAG, "onDestroy" )
    }

    companion object {
        fun log( tag: String, f: String ){
            log( tag, f, null )
        }

        fun log(tag: String, f: String, intent: Intent?) {
            Log.d(tag, ">> $f")
            val bundle: Bundle = intent?.extras ?: return

            bundle.keySet().forEach { key ->
                Log.d(tag, "[$key=${bundle.get(key)}]");
            }
        }
    }

Relevant portion of TasksFragment.kt:

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        viewDataBinding = TasksFragBinding.inflate(inflater, container, false).apply {
            viewmodel = viewModel
        }
        setHasOptionsMenu(true)

        // Set a listener on task button
        setupFab()

        setFiltering( "onCreateView" )

        return viewDataBinding.root
    }

    override fun onResume() {
        super.onResume()
        setFiltering( "onResume" )
    }

    private fun setFiltering(f: String){
        TasksActivity.log( TAG, f, activity?.intent )
        viewModel.setFiltering(TasksFilterType.find(activity?.intent?.extras?.getString(OPEN_APP_FEATURE)))

        viewModel.setFiltering(activity?.intent?.extras?.getString(GET_THING))
    }

Solution

  • So one thing to check is Tasks and Back Stack

    You should also check your Android Manifest in the <activity> tag, you can set the android:launchMode attribute to something like

                android:launchMode="singleInstance"
    

    This will force Activity.onNewIntent() to be called when the app is started with a new intent. In this method, you can call Activity.setIntent() with the new Intent that is passed so it has the correct Intent set when it resumes.

    In the TasksActivity.kt file, it might look like this:

        override fun onNewIntent(intent: Intent?) {
            super.onNewIntent(intent)
            log(TAG, "onNewIntent", intent)
            setIntent( intent )
        }