I have an android app that needs to show a UI if the Accessibility Service API has not been enabled for the app. After the user enables the service, the activity will no longer show a UI and just acts in the background.
Manifests.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:theme="#android:style/Theme.NoDisplay">
<activity
android:theme="#android:style/Theme.NoDisplay">
.
.
.
</application>
</manifest>
MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
/**
* Get the Accessibility Manager
**/
val manager = getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
if(!manager.isEnabled){
Log.i("TESTING", "Setting theme")
setTheme(android.R.style.Theme_NoTitleBar)
}
Log.i("TESTING", "Theme in use: $theme")
super.onCreate(savedInstanceState)
/**
* Verify that it has been enabled
**/
if (manager.isEnabled) {
doSomeJob()
finish()
} else {
setContent {
MaterialTheme {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color.White)
) {
Text(
text = "This is a test"
)
}
}
}
}
When I run the app (manager is not enabled on app startup), I get the following error:
Unable to resume activity {com.th3pl4gu3.lockme/com.th3pl4gu3.lockme.MainActivity}: `java.lang.IllegalStateException: Activity {com.th3pl4gu3.lockme/com.th3pl4gu3.lockme.MainActivity} did not call finish() prior to onResume() completing`
The logs that i've set in the code is printing this:
2023-02-16 19:58:47.870 17718-17718 TESTING com.th3pl4gu3.lockme I Setting theme
2023-02-16 19:58:47.872 17718-17718 TESTING com.th3pl4gu3.lockme I Theme in use: {InheritanceMap=[id=0x1030006android:style/Theme.NoTitleBar, id=0x1030005android:style/Theme], Themes=[android:style/Theme.NoTitleBar, forced, android:style/Theme.NoDisplay, forced, android:style/Theme.DeviceDefault.Light.DarkActionBar, forced]}
If I set the theme in Manifest it works fine but that's not what i want. I need to be able to change it programatically because the UI will only be shown if the user has not enabled Accessibility services.
Can someone please help me on why the theme is not changing programmatically ?
Related
I am struggling with figure out how to close a dialog launched to explain denied permissions.
Using accompanist to ask for permissions:
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(key1 = lifecycleOwner, effect = {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_START) {
locationPermissionState.launchPermissionRequest()
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
})
In the same composable I launch a dialog depending on denied permissions:
when {
locationPermissionState.status.shouldShowRationale -> {
AlertDialog(
// Dialog to explain to users the permission
)
}
!locationPermissionState.status.isGranted && !locationPermissionState.status.shouldShowRationale -> {
AlertDialog(
// dialog to tell user they need to go to settings to enable
)
}
}
I am stuck figuring out how to close on the dialog when the user click an OK button.
I have tried to use another state that survives recomposition:
val openDialog by remember { mutableStateOf(false) }
....
// if permission state denied
openDialog = true
....
// then in dialog ok
openDialog = false
However when doing that and changing the state of openDialog the function is recomposed. Which just means when I check the permissions state again its still the same and my dialog opens again.
For a general solution handling location permissions requests in Compose you need to keep track of performed permissions requests. Android's permissions request system works in an iterative fashion, and at certain points in this iteration there is no state change to observe and to act upon besides the iteration count. The current Accompanist 0.24.12-rc has a permissions request callback that you can use to do this. Then you can structure your declarative Compose code to take action based on previously observed and saved iteration counts and the current iteration count. (And you could try to abstract it further, creating dedicated states based on iteration count values and differences, but that's not really necessary to make it work; my hope would be that someone will add this in Accompanist at some point.)
For example:
val locationPermissions: List<String> = listOf(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION)
#Stable
interface LocationPermissionsState : MultiplePermissionsState {
/**
* Supplies a well-defined measure of time/progression
*/
val requestCount: UInt
}
#Composable
fun rememberLocationPermissionsState(): LocationPermissionsState {
val requestCountState: MutableState<UInt> = remember { mutableStateOf(0u) }
val multiplePermissionsState: MultiplePermissionsState =
rememberMultiplePermissionsState(locationPermissions) {
if (it.isEmpty()) {
// BUG in accompanist-permissions library upon configuration change
return#rememberMultiplePermissionsState
}
requestCountState.value++
}
return object : LocationPermissionsState, MultiplePermissionsState by multiplePermissionsState {
override val requestCount: UInt by requestCountState
}
}
You can see this solution with a little more context at https://github.com/google/accompanist/issues/819
I have a Composable that has a Text and Button. Text will show P if the current orientation is portrait, and L otherwise. Clicking on the Button will change the orientation to landscape, (So after that, it should change the text from P to L)
Here's the Composable
#Composable
fun MyApp() {
val currentOrientation = LocalConfiguration.current.orientation
val orientation = if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
"P"
} else {
"L"
}
val activity = LocalContext.current as Activity
Column {
Text(text = orientation)
Button(onClick = {
// change orientation to landscape
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
}) {
Text(text = "DO IT")
}
}
}
and here's how am testing it
#get:Rule
val composeRule = createComposeRule()
#Test
fun test() {
composeRule.setContent { MyApp() }
// Starts with portrait
composeRule.onNodeWithText("P").assertIsDisplayed()
// Change the orientation to Landscape
composeRule.onNodeWithText("DO IT").performClick()
// Now the text should be `L`
composeRule.onNodeWithText("L").assertIsDisplayed()
}
But I am getting the below error when I run the test to see if the text is updated or not. (Manual test works though)
java.lang.IllegalStateException: No compose views found in the app. Is your Activity resumed?
at androidx.compose.ui.test.TestContext.getAllSemanticsNodes$ui_test_release(TestOwner.kt:96)
at androidx.compose.ui.test.SemanticsNodeInteraction.fetchSemanticsNodes$ui_test_release(SemanticsNodeInteraction.kt:82)
at androidx.compose.ui.test.SemanticsNodeInteraction.fetchOneOrDie(SemanticsNodeInteraction.kt:155)
at androidx.compose.ui.test.SemanticsNodeInteraction.assertExists(SemanticsNodeInteraction.kt:147)
at androidx.compose.ui.test.SemanticsNodeInteraction.assertExists$default(SemanticsNodeInteraction.kt:146)
Here's the complete test file if you want to try it yourself.
Questions
What am I missing here and how can I fix it?
Was the screen on during the test?
In my case, this is easily reproduceable by simply turning the screen off. Note that I am using the emulator.
I faced the same exact problem when performing the click event.
At the time I'm testing with my device, at happened when your test device/emulator's screen is not awake (turn off).
My fix is just turn on the screen.
I am writing custom launcher and almost at the end of my work. I would like to have the functionality to add PWA to my launcher home screen.
I've created new activity that will handle new PWAs, added it to AndroidManifest with CONFIRM_PIN_SHORTCUT intent filter and managed to receive PinItemRequest in my activity when pwa got installed from browser.
AndroidManifest.xml
<activity android:name=".HandleShortcutActivity">
<intent-filter>
<action android:name="android.content.pm.action.CONFIRM_PIN_SHORTCUT" />
</intent-filter>
</activity>
HandleShortcutActivity.kt:
class HandleShortcutActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
handlePins()
}
//LauncherApps.getPinItemRequest( this.intent );
}
#RequiresApi(Build.VERSION_CODES.O)
private fun handlePins() {
val launcherApps = applicationContext.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
val request = launcherApps.getPinItemRequest(intent)
Timber.d("handlePins: request = \n{shortcutInfo:\n\tlongLabel=${request.shortcutInfo?.longLabel}\n\tpackage=${request.shortcutInfo?.`package`}")
this.finish()
}
}
What information from shortcutInfo I can use to correctly run PWA from launcher? The package of PWA is com.android.chrome and it is accessible, but I can't see any additional info that could help me to run PWA.
Also, how can I get the icon from this received PinItemRequset which will I use as an icon in my launcher?
Edit: Updated in the bottom
I'm calling this in Application's onCreate: AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
Then when I'm going to the device's settings (Settings -> Display -> Night mode (switch: on/off)) then I'm resuming my application, the theme is not applied. It doesn't matter if I'm turning ON or OFF the night mode in the device's settings, the theme is not applied.
I also added a breakpoint and I checked that the following is returning me false even if the Dark Mode is ON from device's settings (Note: the app was started with Dark mode OFF).
fun isNightMode(app: Application): Boolean {
return when(app.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
Configuration.UI_MODE_NIGHT_NO -> false
Configuration.UI_MODE_NIGHT_YES -> true
else -> false
}
}
It looks like the application's resource doesn't get updated when I'm changing the theme from device settings.
For debug purpose I override the following function in the Application class:
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
}
and it's getting called.
Edit: It looks like this is causing the problem. Having this in the Application class:
override fun attachBaseContext(base: Context) {
val locale = Locale("de", "DE")
Locale.setDefault(locale)
val resources = base.resources
val configuration = Configuration(resources.configuration)
configuration.setLocale(locale)
val baseContext = base.createConfigurationContext(configuration)
super.attachBaseContext(baseContext)
}
If I remove the code above it's working.
Just to provide a standalone answer here.
Thanks to #Zbarcea Christian and #Prashanth for their comments.
The problem for me was the same: overriding attachBaseContext() method. So, fixed it like this:
override fun attachBaseContext(cxt: Context) {
val resources = cxt.resources
val configuration = Configuration(resources.configuration)
// Required for the day/night theme to work
configuration.uiMode = Configuration.UI_MODE_NIGHT_UNDEFINED
val baseContext = cxt.createConfigurationContext(configuration)
// set locale and so on ...
super.attachBaseContext(baseContext)
}
I have searched the Flutter documentation and googled this, but with zero result. I am developing my first Flutter app for android and I would like to create a custom quick settings tile for it. I am targeting Nougat and above. I know it's possible in Java and Kotlin (e.g. https://android.jlelse.eu/develop-a-custom-tile-with-quick-settings-tile-api-74073e849457), but how about Dart/Flutter?
You can do it natively (makes more sense since it is an Android-only feature).
Every Flutter contains an android and ios folder. Inside of those folders, you will find the wrapper apps for Android and iOS.
Just open the Android project in Android Studio and follow the tutorial you linked.
You can create Quick setting tile natively
Go to android/app/src/main/kotlin/package_name(folder)
Create a service class MyTileService.kt
MyTileService.kt
#RequiresApi(Build.VERSION_CODES.N)
//Quick Tile feature can be used Build.VERSION_CODES.N or Greater
class MyTileService : TileService() {
// Called when the user adds your tile.
override fun onTileAdded() {
super.onTileAdded()
}
// Called when your app can update your tile.
override fun onStartListening() {
super.onStartListening()
val tile = qsTile // this is getQsTile() method form java, used in Kotlin as a property
tile.label = "Set Alarm"
tile.state = Tile.STATE_ACTIVE
tile.icon = Icon.createWithResource(this, R.drawable.baseline_alarm_24)
tile.updateTile() // you need to call this method to apply changes
}
// Called when your app can no longer update your tile.
override fun onStopListening() {
super.onStopListening()
}
// Called when the user taps on your tile in an active or inactive state.
override fun onClick() {
super.onClick()
try{
// to open flutter activity
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()}")
}
}
// Called when the user removes your tile.
override fun onTileRemoved() {
super.onTileRemoved()
}
}
In AndroidManifest.xml
<service
android:name=".MyTileService"
android:exported="true"
android:icon="#drawable/baseline_alarm_24" //your_vector_icon
android:label="Set Alarm" //tile lable
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action
android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
running your app will create a quick setting tile for your app
if not showing -> try to edit the notification panel icons (xiaomi/mi devices)
onClick on this quick settings icon we want to show our flutter screen
Inside main.dart
Future<void> main(List<String> arguments) async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp(
msg: arguments.isNotEmpty ? arguments[0] : null,
));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key, this.msg}) : super(key: key);
final String? msg;
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: msg == "launchFromQuickTile" ? Create.routeName :
Home.routeName,
routes: //your routes,
);
}
}
For more information about quick settings tile