Can anyone please help. Am a noob to Android studio. My first major app, is a streaming media player. Problem, i wish to use foreground service, but i just cannot understand how to implement foreground service. I tried searching youtube, but not much on kotlin, and the same with the sites. Can anyone please give me a step by step guide for this.
This is my MainActivity below. The app is working, just missing the foreground service.
package com.example.al_bunyan
import android.app.Dialog
import android.content.Intent
import android.graphics.Color
import android.graphics.drawable.AnimationDrawable
import android.media.MediaPlayer
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.ImageView
import android.widget.RelativeLayout
import android.widget.VideoView
import java.time.Instant
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var web_btn = findViewById<Button>(R.id.link_btn)
web_btn.setOnClickListener {
var intent = Intent(this#MainActivity, AlbunyaWebview::class.java)
startActivity(intent)
}
}
fun video_check(view:View) {
val fm105 = findViewById<Button>(R.id.fm105_7)
val alert = Dialog(this#MainActivity)
alert.setContentView(R.layout.audio_play)
val video = alert.findViewById<VideoView>(R.id.video_alert)
val play = alert.findViewById<Button>(R.id.play)
val pause = alert.findViewById<Button>(R.id.pause)
val resume = alert.findViewById<Button>(R.id.resume)
if (fm105.isPressed) {
val video_1 = Uri.parse("http://albunyan.fm:8000/live")
video.setVideoURI(video_1)
alert.show()
play.setOnClickListener {
video.start()
}
pause.setOnClickListener {
video.pause()
}
resume.setOnClickListener {
video.start()
}
}
}
}
There are many resources available including code examples.
following are some of them
Android developer docs Services
Android developer docs Foreground services
Sample Android project on github LocationUpdatesForegroundService
I suggest first you read some theory from the first two resources and then try to learn from the code in the github project, which clearly shows how you can implement foreground service.
Related
How can I test an Android Jetpack Compose composable that calls rememberLauncherForActivityResult?
For example:
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
import androidx.compose.runtime.Composable
import androidx.compose.material3.Button
import androidx.compose.material3.Text
#Composable
fun PickFileButton() {
val launcher = rememberLauncherForActivityResult(CreateDocument("text/plain")) {
/* do something with the returned Uri */
}
Button(onClick={launcher.launch("default_file_name.txt")}) {
Text("Pick File")
}
}
Clicking the button launches another activity that the user uses to pick a file.
A test would look like:
import org.junit.Rule
import org.junit.Test
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
class TestPickFileButton {
#get:Rule val composeTestRule = createComposeRule()
#Test
fun testPickFileButton() {
composeTestRule.setContent { PickFileButton() }
composeTestRule.onNodeWithText("Pick File").performClick()
// now Android CreateDocument activity has launched
// and "composeTestRule" cannot interact with this activity
// rest of test eg to check the file was created
}
}
I cannot complete the test because the external activity has been launched and I can't interact with this activity with the ComposeContentTestRule object.
The solution involves using CompositionLocalProvider to override the default ActivityResultRegistry. This allows you to send a custom response with ActivityResultRegistry.dispatchResult instead of launching an activity.
I had to read the source code for the CreateDocument contract to understand what kind of Intent was expected to be returned.
A working solution for this particular question is:
import android.app.Activity
import android.content.Intent
import android.net.Uri
import androidx.activity.compose.LocalActivityResultRegistryOwner
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.ActivityResultRegistry
import androidx.activity.result.ActivityResultRegistryOwner
import androidx.activity.result.contract.ActivityResultContract
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.core.app.ActivityOptionsCompat
import androidx.core.net.toFile
import org.junit.Rule
import org.junit.Test
#Composable
fun PickFileButton() {
val launcher =
rememberLauncherForActivityResult(CreateDocument("text/plain")) {
/* do something with the returned Uri */
}
Button(onClick = { launcher.launch("default_file_name.txt") }) { Text("Pick File") }
}
class TestPickFileButton {
#get:Rule val composeTestRule = createComposeRule()
#Test
fun testPickFileButton() {
val testUri = Uri.parse("uri_string_to_return")
composeTestRule.setContent {
// ActivityResultRegistry is responsible for handling the
// contracts and launching the activity
val registryOwner = ActivityResultRegistryOwner {
object : ActivityResultRegistry() {
override fun <I : Any?, O : Any?> onLaunch(
requestCode: Int,
contract: ActivityResultContract<I, O>,
input: I,
options: ActivityOptionsCompat?
) {
// don't launch an activity, just respond with the test Uri
val intent = Intent().setData(testUri)
this.dispatchResult(requestCode, Activity.RESULT_OK, intent)
}
}
}
CompositionLocalProvider(LocalActivityResultRegistryOwner provides registryOwner) {
// any composable inside this block will now use our mock ActivityResultRegistry
PickFileButton()
}
}
composeTestRule.onNodeWithText("Pick File").performClick()
// no activity is launched, PickFileButton will received testUri as a respose.
// rest of test eg to check the file was created
assert(testUri.toFile().exists())
}
}
Sources:
Discussion in Kotlin Lang Slack Chat,
ActivityResultRegistryTest's in Android repository,
CompositionLocalProvider
Recently we noticed that messages sent from our wear app to the phone through MessageClient don't trigger the WearableListenerService start in some cases.
One of the cases in which it can be reproduced is when the phone app is force stopped. Once the phone app is force stopped once, the WearableListenerService will never start, even if we have the app opened on the screen.
We don't understand exactly what is missing, because before force stopping the phone app the messages are received correctly without any failure. We also tried to use the MessageClient inside an Activity, where it works perfectly after phone app restart.
Looks like the problem has to do with the Service declaration.
¿Do you have any tips on what might be missing in the Service declaration? ¿Is it possible that the mentioned case is a restriction that we cannot solve?
Note: This case only applies on messages sent from wear to phone. If we force stop wear app, the ListenerService is correctly started on wear.
If you think more information needs to be provided don't hesitate to ask. Additionally, I am sorry if I am not expressing correctly in English.
Here is an example of the code related to the messages sent from wear to phone:
Service declaration on Manifest:
<service
android:name=".DataLayerListenerService"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
<data
android:host="*"
android:pathPrefix="/start-activity-app"
android:scheme="wear" />
</intent-filter>
</service>
WearableListenerService:
package com.example.android.wearable.datalayer
import android.content.Intent
import android.util.Log
import com.google.android.gms.wearable.MessageEvent
import com.google.android.gms.wearable.WearableListenerService
class DataLayerListenerService : WearableListenerService() {
override fun onCreate() {
super.onCreate()
Log.d("POC", "Start service")
}
override fun onDestroy() {
super.onDestroy()
Log.d("POC", "Stop service")
}
override fun onMessageReceived(messageEvent: MessageEvent) {
super.onMessageReceived(messageEvent)
Log.d("POC", "I am receiving a message.")
when (messageEvent.path) {
START_ACTIVITY_PATH -> {
Log.d("POC", "The message is the expected one.")
startActivity(
Intent(this, MainActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
}
}
}
companion object {
private const val START_ACTIVITY_PATH = "/start-activity-app"
}
}
Wear method that invokes the app:
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import com.google.android.gms.wearable.CapabilityClient
import com.google.android.gms.wearable.Node
import com.google.android.gms.wearable.Wearable
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.launch
import kotlinx.coroutines.tasks.await
//...
private val dataClient by lazy { Wearable.getDataClient(this) }
private val messageClient by lazy { Wearable.getMessageClient(this) }
private val capabilityClient by lazy { Wearable.getCapabilityClient(this) }
//...
private fun onQueryAppInitClicked() {
lifecycleScope.launch {
try {
val capabilityInfo = capabilityClient.getCapability("mobile", CapabilityClient.FILTER_REACHABLE).await()
capabilityInfo.nodes.forEach { node ->
messageClient.sendMessage(node.id, "/start-activity-app", null).await()
Log.d("POC", "Sended message to node " + node.displayName)
}
Log.d("POC", "---")
} catch (cancellationException: CancellationException) {
throw cancellationException
} catch (exception: Exception) {
Log.d(TAG, "Querying nodes failed: $exception")
}
}
}
//...
I'm tinkering with nats.io as an alternative to Firebase Cloud Messaging
Here's the MainActivity:
mport android.content.Intent
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Button
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
class MainActivity : AppCompatActivity() {
lateinit var btnStart: Button
lateinit var btnStop: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnStart = findViewById<Button>(R.id.btnStartService)
btnStop = findViewById<Button>(R.id.btnStopService)
btnStart.setOnClickListener {
Log.d("ENDLESS_SERVICE","START THE FOREGROUND SERVICE ON DEMAND")
actionOnService(Actions.START_SERVICE)
WorkManager.getInstance(this)
.beginUniqueWork("MyBackgroundWorker", ExistingWorkPolicy.APPEND_OR_REPLACE,
OneTimeWorkRequest.from(MyBackgroundWorker::class.java)).enqueue().state
.observe(this) { state ->
Log.d("NATSDemo", "MyBackgroundWorker: $state")
}
}
btnStop.setOnClickListener {
Log.d("ENDLESS_SERVICE","START THE FOREGROUND SERVICE ON DEMAND")
actionOnService(Actions.STOP_SERVICE)
}
}
private fun actionOnService(action: Actions) {
if (getServiceState(this) == ServiceState.SERVICE_STOPPED && action == Actions.STOP_SERVICE) return
Intent(this, EndlessService::class.java).also {
it.action = action.name
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Log.d("ENDLESS_SERVICE","Starting the service in >=26 Mode")
startForegroundService(it)
return
}
Log.d("ENDLESS_SERVICE", "Starting the service in < 26 Mode")
startService(it)
}
}
}
If you take a look at the whole code, you'll notice the NATS connection is initiated each 3 minutes. I send messages using nats-cli, and interestingly, all the notifications appear in the Android notification tray have the title "NATS Demo" (from EndlessService.kt), but not even one from MyBackgroundWorker.kt, which has the title "Message from NATS". So MyBackgroundWorker is not executed at all. Why?
BTW, I found this on logcat, probably could be a useful hint:
2022-01-13 01:58:13.926 19560-19592/com.anta40.app.natsservicedemo I/WM-WorkerWrapper: Worker result SUCCESS for Work [ id=18c9ba36-a578-4957-b7f6-9205ef6183c9, tags={ com.anta40.app.natsservicedemo.MyBackgroundWorker } ]
2022-01-13 01:58:13.927 19560-19560/com.anta40.app.natsservicedemo I/WM-SystemFgDispatcher: Started foreground service Intent { act=ACTION_START_FOREGROUND cmp=com.anta40.app.natsservicedemo/androidx.work.impl.foreground.SystemForegroundService (has extras) }
2022-01-13 01:58:13.941 19560-19560/com.anta40.app.natsservicedemo I/WM-SystemFgDispatcher: Stopping foreground service
What do I use now that BootstrapNotifier interface is deprecated in IBEACON?
Ibeacon deprecated : 1) RegionBootstrap 2) BootstrapNotifier 3)
BackgroundPowerSaver
is there any alternate solution or reference link?
I shared my full code
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NotificationCompat
import org.altbeacon.beacon.BeaconManager
import org.altbeacon.beacon.Region
import org.altbeacon.beacon.powersave.BackgroundPowerSaver
import org.altbeacon.beacon.startup.BootstrapNotifier
import org.altbeacon.beacon.startup.RegionBootstrap
class MainActivity2 : AppCompatActivity(), BootstrapNotifier {
private var regionBootstrap: RegionBootstrap? = null
private var backgroundPowerSaver: BackgroundPowerSaver? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// showNotification(this,"found beacon init")
//enable beacon features///////////////////////////////////////////////////////////////////////
val beaconManager: BeaconManager = BeaconManager.getInstanceForApplication(this)
beaconManager.beaconParsers.clear()
beaconManager.beaconParsers
.add(org.altbeacon.beacon.BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"))
beaconManager.setEnableScheduledScanJobs(false) // disable JobScheduler-based scans (used on Android 8+)
beaconManager.backgroundBetweenScanPeriod = 0 // set the time between each scan to be 1 hour (3600 seconds)
beaconManager.backgroundScanPeriod = 1100 // set the duration of the scan to be 1.1 seconds
val region = Region("backgroundRegion", null, null, null)
regionBootstrap = RegionBootstrap(this, region) // wake up the app when a beacon is seen
backgroundPowerSaver = BackgroundPowerSaver(this) //This reduces bluetooth power usage by about 60%
//////////////////////////////////////////////////////////////////////////////////////////////
}
override fun didEnterRegion(arg0: Region?) {
Log.d("mybeacon", "i found a enter")
showNotification(this,"found beacon enter")
}
override fun didExitRegion(region: Region?) {
Log.d("mybeacon", "i found a exit")
showNotification(this,"found beacon exit")
}
override fun didDetermineStateForRegion(state: Int, region: Region?) {}
//............................................................
fun showNotification(context: Context, message: String?) {
}
}
implementation :
implementation 'org.altbeacon:android-beacon-library:2.19.3'
Library version 2.19+ introduces autowind APIs that make setting up beacon scanning much simpler, more intuitive and less prone to configuration errors.
In earlier versions of the library, auto-launch of an app on beacon detection was handled by the RegionBootstrap utility class, but as of 2.19 it is no longer necessary -- you simply start monitoring with startMonitoring(region). The library sample code shows an example of how to start up auto-launch section of the code samples
To start up beacon scanning in the background simply replace regionBootstrap = RegionBootstrap(this, region) with this:
beaconManager.addMonitorNotifier(this)
beaconManager.startMonitoring(region)
Instead of the BootstrapNotifier interface, your class should implement MonitorNotifier.
While it is unrelated to the autobind API changes in 2.19, be aware that if you want the library to automatically launch your app on beacon detection, you must implement the above code in the onCreate method of a custom Android Application class and not an Activity as shown in the question. If you don't care about auto launch, then doing this in an Activity class is fine.
Our app needs to prevent users to record our screen for some important reasons. How to prevent screen-record on iOS and android?
It seems that we can get a call-back if users are recording the screen after iOS 11. Is there any method to use that call-back to realize screen-record preventing?
That is something which is not completely possible. There are 2 scenarios for Android OS
1 - When Android device is not rooted - You can use flag FLAG_SECURE which will prevent screenshot and video recording. You can read further here https://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#FLAG_SECURE
2 - When Android device is rooted - You can have a check programatically to know if device is rooted or not. If it is rooted then you can stop your application from running further. Here is the link to check for root device - Determining if an Android device is rooted programmatically?
Put this code in your MainActivity.kt and You're done :)
(The code isn't mine, I found it on Github)
import io.flutter.embedding.android.FlutterActivity
import android.os.Build
import android.view.ViewTreeObserver
import android.app.ActivityManager
import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import android.view.SurfaceView
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.WindowManager.LayoutParams
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val flutter_native_splash = true
var originalStatusBarColor = 0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
originalStatusBarColor = window.statusBarColor
window.statusBarColor = 0xffeaeaea.toInt()
}
val originalStatusBarColorFinal = originalStatusBarColor
if (!setSecureSurfaceView()) {
Log.e("MainActivity", "Could not secure the MainActivity!")
// React as appropriate.
}
getWindow().addFlags(LayoutParams.FLAG_SECURE)
getWindow().setFlags(LayoutParams.FLAG_SECURE,
LayoutParams.FLAG_SECURE)
SurfaceView(applicationContext).setSecure(true)
}
private fun setSecureSurfaceView(): Boolean {
val content = findViewById<ViewGroup>(android.R.id.content)
if (!isNonEmptyContainer(content)) {
return false
}
val splashView = content.getChildAt(0)
if (!isNonEmptyContainer(splashView)) {
return false
}
val flutterView = (splashView as ViewGroup).getChildAt(0)
if (!isNonEmptyContainer(flutterView)) {
return false
}
val surfaceView = (flutterView as ViewGroup).getChildAt(0)
if (surfaceView !is SurfaceView) {
return false
}
surfaceView.setSecure(true)
this.window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
return true
}
private fun isNonEmptyContainer(view: View): Boolean {
if (view !is ViewGroup) {
return false
}
if (view.childCount < 1) {
return false
}
return true
}
}
You can use the flutter_windowmanager plugin. Once you have added to your pubspec.yaml file then import it where you want to disable screenshot. Using
import 'package:flutter_windowmanager/flutter_windowmanager.dart';
Now add this line into your Stateful Widget.
Future<void> disableScreenShot() async {
await FlutterWindowManager.addFlags(FlutterWindowManager.FLAG_SECURE);
}
#override
void initState() {
disableScreenShot();
super.initState();
}
Now Normal Users can't take screenshots.