Android 13 - TileService for Quick Settings Panel is not collapsing onClick

686 views Asked by At

Short Question: startActivityAndCollapse not working for Android 13

Long Question: I am creating a tile for the quick settings panel. I have tried to implement this demo. It's working fine for all other devices except Android 13

override fun onClick() {
    super.onClick()
    try {
        val newIntent =
            FlutterActivity.withNewEngine().dartEntrypointArgs(listOf("launchFromQuickTile"))
                .build(this)
        newIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
        startActivityAndCollapse(newIntent)
    } catch (e: Exception) {
        Log.d("debug", "Exception ${e.toString()}")
    }
}

The above code is working to open the application but it's not collapsing the quick settings panel.

Is there any solution, any help?

Edited:

I had taken a look more into this and found that It's working only If I pass the Android activity.

Example (Android):

val newIntent = Intent(this, MainActivity::class.java)
newIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivityAndCollapse(newIntent)

Example (Flutter):

val newIntent = FlutterActivity.withNewEngine().dartEntrypointArgs(listOf("launchFromQuickTile")).build(this)
newIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivityAndCollapse(newIntent)

Is there any other way to open Flutter App with Params?

1

There are 1 answers

1
Rahul On

I tried the following code and it worked. I used https://github.com/android/user-interface-samples/tree/main/Quick-Settings sample app.

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.a">
   <application
        android:label="a"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <!-- Specifies an Android theme to apply to this Activity as soon as
                 the Android process has started. This theme is visible to the user
                 while the Flutter UI initializes. After that, this theme continues
                 to determine the Window background behind the Flutter UI. -->
            <meta-data
              android:name="io.flutter.embedding.android.NormalTheme"
              android:resource="@style/NormalTheme"
              />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />

       <service
           android:name=".QSIntentService"
           android:label="Flutter QuickTile"
       android:icon="@mipmap/ic_launcher"
                android:exported="true"
       android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
       <intent-filter>
           <action android:name="android.service.quicksettings.action.QS_TILE" />
       </intent-filter>
   </service>
    </application>
</manifest>

MainActivity.kt

package com.example.a

import android.R
import android.app.StatusBarManager
import android.content.ComponentName
import android.os.Build
import android.os.Build.VERSION
import android.os.Bundle
import android.service.quicksettings.TileService
import androidx.core.graphics.drawable.IconCompat
import io.flutter.embedding.android.FlutterActivity
import com.google.common.util.concurrent.MoreExecutors


class MainActivity: FlutterActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        if (VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            val statusBarService = this.getSystemService(
                StatusBarManager::class.java
            )
            val componentName = ComponentName(
                this.applicationContext,
                TileService::class.java.getName()
            )

            statusBarService.requestAddTileService(
                componentName,
                "Quick Settings",
                IconCompat.createWithResource(
                    applicationContext,
                    R.drawable.stat_sys_warning).toIcon(this)
                ,
                MoreExecutors.directExecutor()
            ) { integer: Int? ->
                setResult(integer!!)
                finish()
            }
        }

    }
}

TileService.kt

package com.example.a

import android.content.Intent
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import io.flutter.embedding.android.FlutterActivity.withNewEngine


class QSIntentService : TileService() {
    override fun onClick() {

        // Check to see if the device is currently locked.
        val isCurrentlyLocked = this.isLocked
        if (!isCurrentlyLocked) {
            val tile = qsTile
            val tileLabel = tile.label.toString()
            val tileState: String =
                if (tile.state == Tile.STATE_ACTIVE) "Active" else "Inactive"
            val intent =
                withNewEngine().dartEntrypointArgs(listOf("launchFromQuickTile"))
                .build(this)
                .setClass(this, MainActivity::class.java)
                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            startActivityAndCollapse(intent)
        }
    }
}

build.gradle

def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
    localPropertiesFile.withReader('UTF-8') { reader ->
        localProperties.load(reader)
    }
}

def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}

def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
    flutterVersionCode = '1'
}

def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
    flutterVersionName = '1.0'
}

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
    compileSdkVersion flutter.compileSdkVersion
    ndkVersion flutter.ndkVersion

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = '1.8'
    }

    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
    }

    defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId "com.example.a"
        // You can update the following values to match your application needs.
        // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
        minSdkVersion 24
        targetSdkVersion flutter.targetSdkVersion
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
    }

    buildTypes {
        release {
            // TODO: Add your own signing config for the release build.
            // Signing with the debug keys for now, so `flutter run --release` works.
            signingConfig signingConfigs.debug
        }
    }
}

flutter {
    source '../..'
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.5.1'
    implementation 'com.google.guava:guava:31.1-android'
}

main.dart

import 'package:flutter/material.dart';

void main(args) {
  WidgetsFlutterBinding.ensureInitialized();
  print(args);
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'List',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      debugShowCheckedModeBanner: false,
      home: const Text('Foobar'),
    );
  }
}

pubspec.

name: a
description: A new Flutter project.

publish_to: "none"

version: 1.0.0+1

environment:
  sdk: ">=2.18.5 <3.0.0"

dependencies:
  cupertino_icons: ^1.0.2
  flutter:
    sdk: flutter

dev_dependencies:
  flutter_lints: ^2.0.0
  flutter_test:
    sdk: flutter

flutter:
  uses-material-design: true

Output I/flutter: [launchFromQuickTile]

Stackoverflow does not allow video embedding hence I uploaded the working on Youtube. You can see the video here https://youtu.be/W_iM2yR_07Y