I'm building a corporate app for android TV that I need to have always in the foreground. Every now and then the app will crash along with the service that will relaunch it. Is there a best practice to ensure the app is always running. What I can't figure out is how to launch after a force stop. The app can be side loaded so we don't have to worry about App Store approval.
The problem is when I use a service worker it will also die since it is attached to the original process https://developer.android.com/reference/android/app/Service
Same issue with the https://developer.android.com/topic/libraries/architecture/workmanager
Any ideas on an approach to basically check if the app is running and if it isn't start it up ? Is there any other event that I can hook into to launch the app ?
If a crash happens the app will rerun itself:
class AppExceptionHandler(private val context: Context, private val myActivityClass: Class<*>) :
Thread.UncaughtExceptionHandler {
override fun uncaughtException(thread: Thread, exception: Throwable) {
Logger.d(exception.javaClass.simpleName)
exception.printStackTrace()
val intent = Intent(context, myActivityClass)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
context.startActivity(intent)
//restarting the Activity
Process.killProcess(Process.myPid())
System.exit(0)
}
}
and call from your main activity:
Thread.setDefaultUncaughtExceptionHandler(
AppExceptionHandler(
applicationContext,
MainActivity::class.java
)
)
In this way, I don't think you need to know whether the app is now in the foreground and running but you can get the top running package name in a running service. Also making your app a default launcher could be a workaround.
fun topPackageNameObservable(): Observable<String?>? {
return Observable.fromCallable {
var topPackageName = ""
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val mUsageStatsManager =
getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
val stats =
mUsageStatsManager.queryUsageStats(
UsageStatsManager.INTERVAL_DAILY,
System.currentTimeMillis() - TimeUnit.DAYS.toMillis(
1
),
System.currentTimeMillis() + TimeUnit.DAYS.toMillis(
1
)
)
if (stats != null) {
val mySortedMap: SortedMap<Long, UsageStats> =
TreeMap()
for (usageStats in stats) {
mySortedMap[usageStats.lastTimeUsed] = usageStats
}
if (!mySortedMap.isEmpty()) {
topPackageName = mySortedMap[mySortedMap.lastKey()]!!.packageName
}
} else {
topPackageName = activityManager.getRunningAppProcesses().get(0).processName
}
} else {
topPackageName =
activityManager.getRunningTasks(1).get(0).topActivity.getPackageName()
}
} catch (e: Exception) {
e.printStackTrace()
}
topPackageName
}
}
Try using PackageManager and getLaunchIntentForPackage()
You can try to use the BroadcastReceiver to handle this, which will be invoked when your app is closed.
In your manifest, add this:
<receiver android:name="com.your.package.name.YourReceiver">;
<intent-filter>;
<action android:name="android.intent.action.USER_PRESENT" />;
</intent-filter>;
</receiver>;
And then, in YourReceiver.java
public class YourReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context mContext, Intent intent) {
// when app is closed, the method will be invoked
// do something here to launch your app
}
}
Related
I have an Android mobile app with Auto support, and when in Java, the auto app was working fine to launch the auto app when the notification was clicked.
However, since I've migrated everything to kotlin I only have one remaining issue. The notification in Auto does not want to let me open the auto app, unless I've already started the app first. Then, it seems to work fine until I start over, and it's the same error.
I can launch the auto app directly from the launcher in auto without a problem.
The logs are sparse and vague. In Android Auto when clicking my notification, I get:
"Android Auto has encountered an unexpected error" and the only relevant logs I can find are below.
It seems like there's some type of dynamic binding of the service class name which is incorrect?
I'm referring to the differences in the logs like "com.lusion.sgauto/.SGService" vs "com.lusion.sgauto/com.lusion.sgauto.SGService"
CarApp.H.Tem: Intent to bind to car app service Intent { act=android.intent.action.VIEW flg=0x10 cmp=com.lusion.sgauto/.SGService (has extras) }
CarApp.H.Tem: Binding to: com.lusion.sgauto/.SGService
D CarApp.H: Fragment does not have a context, will return an empty surface provider, detached: false
W PackageManager: Intent does not match component's intent filter: Intent { act=android.intent.action.VIEW id=0/main flg=0x10 cmp=com.lusion.sgauto/.SGService (has extras) }
W PackageManager: Access blocked: ComponentInfo{com.lusion.sgauto/com.lusion.sgauto.SGService}
W ActivityManager: Unable to start service Intent { act=android.intent.action.VIEW id=0/main flg=0x10 cmp=com.lusion.sgauto/.SGService (has extras) } U=0: not found
CarApp.H.Tem: Error: [type: null, cause: null, debug msg: Failed to bind to ComponentInfo{com.lusion.sgauto/com.lusion.sgauto.SGService}]
I have the service registered in androidmanifest.xml as
<service
android:name=".SGService"
android:exported="true"
>
<intent-filter>
<action android:name="androidx.car.app.CarAppService" />
<category android:name="androidx.car.app.category.IOT" />
</intent-filter>
</service>
My notification builder looks like this
.extend(
CarAppExtender.Builder()
.setContentTitle(notificationTitle!!)
.setContentIntent(
CarPendingIntent.getCarApp(
context.applicationContext, 0,
Intent(Intent.ACTION_VIEW).setComponent(
ComponentName(
context.applicationContext,
SGService::class.java
)
), 0
)
)
.addAction(icon1, title1, autoPendingIntent1!!)
.addAction(icon2, title2, autoPendingIntent2!!)
.setImportance(importance).build()
)
The service is defined like below
class SGService : CarAppService() {
private var garageUpdateThread: Thread? = null
override fun onCreateSession(): Session {
Log.i(TAG, "onCreateSession")
initNotifications(this)
try {
this.registerReceiver(
uiMessageBroadcastReceiver,
IntentFilter(R.string.local_main_activity_listener.toString())
)
registerBroadcastReceiver()
}
catch (e:java.lang.Exception)
{
// already registered
}
ongoingGarageStatusThread(applicationContext, "SGService")
return object : Session() {
override fun onCreateScreen(intent: Intent): Screen {
return SGScreen(carContext)
}
}
}
#SuppressLint("UnspecifiedRegisterReceiverFlag")
private fun registerBroadcastReceiver() {
Log.i(TAG, "registerBroadcastReceiver")
val filter = IntentFilter("com.lusion.sgauto.SGService")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
applicationContext.registerReceiver(mBroadcastReceiver, filter, RECEIVER_EXPORTED)
}
else
applicationContext.registerReceiver(mBroadcastReceiver, filter)
}
private val mBroadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val responseType: LusionUtility.UpdateType? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getSerializableExtra("TYPE", LusionUtility.UpdateType::class.java)
} else {
intent.getSerializableExtra("TYPE") as LusionUtility.UpdateType
}
Log.i(TAG, "SGService BroadcastReceiver $responseType")
if (responseType != null) {
if (responseType == LusionUtility.UpdateType.GARAGESTATUS) {
var garageStatus: GarageStatus?
try {
garageStatus = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getSerializableExtra(responseType.toString(), GarageStatus::class.java)
} else {
intent.getSerializableExtra(responseType.toString()) as GarageStatus?
}
} catch (e: Exception) {
garageStatus = GarageStatus.UNKNOWN
Log.e(TAG, "SGScreen mBroadcastReceiver received unknown garage status")
}
// only if garage status has changed will we update the screen
if (garageStatus !== autoGarageStatus) {
Log.i(TAG, "triggerNotification $garageStatus")
// LusionUtility.triggerNotification(getCarContext(), "GARAGESTATUS", garageStatus);
autoGarageStatus = garageStatus
}
// Move this invalidate to SGScreen broadcast receiver
val relayIntent = Intent("com.lusion.sgauto.SGScreen")
relayIntent.putExtras(intent)
Log.i(TAG, "SGService send intent com.lusion.sgauto.SGScreen")
context.sendBroadcast(relayIntent)
}
}
}
}
All of the details are in the posted question
I have socket IO connected to an android app and it's receiving events via background services. while this is working fine when the app is in foreground. When I close the app or when app goes in background I have confirmed that the sockets are connected but I'm not able to receive events when app is in background.
This is my service
const val SOCKET_EVENT = "message:response"
#AndroidEntryPoint
class MService : LifecycleService() {
#Inject
lateinit var ioSocket: Socket
override fun onCreate() {
super.onCreate()
ioSocket.connect()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
ioSocket.connect()
AndroidRemoteDebugger.Log.d("Starting My Service")
listenToSocketsEvents()
return START_STICKY
}
private fun listenToSocketsEvents() {
ioSocket.on(SOCKET_EVENT, onSocketEvent)
}
private val onSocketEvent: Emitter.Listener = object : Emitter.Listener {
override fun call(vararg args: Any?) {
try {
val data = args[0] as JSONObject
//While the main thing about the service is that I'm logging data jhere when I get an
// incoming socket event it does log when app is in foreground but when it's in
// background it doesn't log
AndroidRemoteDebugger.Log.v(data.toString())
val message = data.getJSONObject("message")
val id = message.getString("id")
playSound()
val topic = "message-request"
val intent = Intent(
this#MService,
MessageReceiver::class.java
).apply {
putExtra("messageId", id)
putExtra("topic", topic)
}
sendBroadcast(intent)
} catch (e: JSONException) {
AndroidRemoteDebugger.Log.e(e.message)
return
}
}
}
private fun playSound() {
val notificationSound: Uri =
Uri.parse(
(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" +
packageName) + "/" + R.raw.car_horn
)
val ringtone: Ringtone = RingtoneManager.getRingtone(
applicationContext,
notificationSound
)
ringtone.play()
}
}
This is my receiver
class MessageReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val topic = intent?.getStringExtra("topic")
val tripId = intent?.getStringExtra("tripId")
val intent = Intent(Constants.NOTIFICATION).apply {
putExtra("Notify", topic)
putExtra("tripId", tripId)
putExtra("from", Constants.SOCKETS)
}
if (context != null) {
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
}
}
}
While the main thing about the service is that I'm logging something when I get an incoming socket event it does log when app is in foreground but when it's in background it doesn't log
LifecycleService will be killed when the app goes in the background/killed.
You should use ForegroundService to run any background tasks.
From official doc
While an app is in the foreground, it can create and run both foreground and background services freely. When an app goes into the background, it has a window of several minutes in which it is still allowed to create and use services. At the end of that window, the app is considered to be idle. At this time, the system stops the app's background services, just as if the app had called the services' Service.stopSelf() methods.
Your sockets are still connected because you don't disconnect the socket when the service gets killed. Always disconnect the socket when you're done with the service to avoid unnecessary resource consumption.
My application needs to open some other applications when it will receive a notification. Now when my application is on front and i open an application upon notification, It works fine. But when there is already another application is open on top of my application, upon launching another application doesn't get launched. I have tried this way.
class AppReceiver : BroadcastReceiver() {
companion object {
const val TAG = "AppReceiver"
}
override fun onReceive(context: Context, intent: Intent) {
try {
if (intent.action == "packageUpdated") {
val packageName = intent.getStringExtra("packageName") ?: ""
launchPackage(context, packageName)
}
} catch (ex: Exception) {
ex.printStackTrace()
}
}
fun launchPackage(context: Context, packageName: String) {
try {
val intent = context.packageManager.getLaunchIntentForPackage(packageName)
if (intent == null) {
Log.e(TAG,"Intent not found")
} else {
context.startActivity(intent)
}
} catch (ex: Exception) {
ex.printStackTrace()
}
}
}
The Manifest for receiver:
<receiver
android:name=".AppReceiver"
android:exported="true">
<intent-filter>
<action android:name="packageUpdated" />
</intent-filter>
</receiver>
launchPackage works first time when application is on top, but not in the case when application is in backstack. Also it doesn't through any exception in this case.
I need whenever broadcast receiver listens if there is an updated package name of application. It should launch that application.
I need to get the name of any Android app when it opens and launch a password window. I have searched everywhere how to find out the name of the app which the user has opened but I have found no working solution yet. I have a service which I'm running on the background, but it only returns my app's name or the home screen name, no matter which app I open.
Here is my service code:
package com.example.applock
import android.app.ActivityManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.IBinder
import android.provider.Settings
class BackAppListenerService : Service() {
private var isRunning = false
private var lastApp = ""
override fun onCreate() {
println(TAG + "Service onCreate")
isRunning = true
val intent = Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
println(TAG + "Service onStartCommand")
//Creating new thread for my service
//Always write your long running tasks in a separate thread, to avoid ANR
Thread(Runnable { //Your logic that service will perform will be placed here
//In this example we are just looping and waits for 1000 milliseconds in each loop.
while (true) {
try {
Thread.sleep(1000)
} catch (e: Exception) {
}
val mActivityManager = this.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val mPackageName = mActivityManager.runningAppProcesses[0].processName
println(mPackageName)
}
}).start()
return START_STICKY
}
override fun onBind(arg0: Intent): IBinder? {
println(TAG + "Service onBind")
return null
}
override fun onDestroy() {
isRunning = false
println(TAG + "Service onDestroy")
}
companion object {
private const val TAG = "HelloService"
}
}
Ok, so I have figured it out.
First add this to your manifest.xml under the manifest tag:
<uses-permission
android:name="android.permission.PACKAGE_USAGE_STATS"
tools:ignore="ProtectedPermissions"/>
Then go to apps permissions > Usage Stats go to your app and enable it. You can also make a pop up asking for the user to do it once the apps loads.
Now, to get name of the current foreground app:
fun getForegroundApp(): String {
var currentApp = "NULL"
// You can delete the if-else statement if you don't care about Android versions
// lower than 5.0. Just keep the code that is inside the if and delete the one
// inside the else statement.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val usm = this.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
val time = System.currentTimeMillis()
val appList =
usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - 1000 * 1000, time)
if (appList != null && appList.size > 0) {
val mySortedMap: SortedMap<Long, UsageStats> =
TreeMap<Long, UsageStats>()
for (usageStats in appList) {
mySortedMap.put(usageStats.lastTimeUsed, usageStats)
}
if (mySortedMap != null && !mySortedMap.isEmpty()) {
currentApp = mySortedMap.get(mySortedMap.lastKey())!!.getPackageName()
}
}
} else {
val am = this.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val tasks = am.runningAppProcesses
currentApp = tasks[0].processName
}
// Get only the app name name
return currentApp.split(".").last()
}
Sometimes the app name isn't the one displayed, for example the "gmail" app has the name "gm" when I tested it. The home screen name also changes from device to device
The logic to call it every few milliseconds to get the current foreground app is simple:
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
//Creating new thread for my service
//Always write your long running tasks in a separate thread, to avoid ANR
Thread(Runnable {
while (true) {
try {
Thread.sleep(10)
} catch (e: Exception) {
}
val currentApp = getForegroundApp()
if (currentApp != lastApp) {
println(currentApp)
// New app on front
lastApp = currentApp
println(currentApp)
// Do whatever you wan
}
}
}).start()
return START_STICKY
}
And that's it.
It seems if I start a Service from an app but do not call startForeground() in the onStartCommand method, and I then put the app in background, after one minute the onDestroy callback is called and the Service is destroyed.
I have not seen this stated anywhere in the Android documentation. Rather, the documentation says vaguely that eventually this kind of Service will be destroyed.
Notice that the onStartCommand method returns START_STICKY.
Any suggestions would be appreciated.
Code for the Service follows; it is meant to explore the Service capability, not as an actual production app. Also, programming sometimes does the same thing different ways; I have been trying different approaches to get more insight. And I am pretty new to Android; regret naively written code.
class IntegerCounting : Service() {
companion object {
var theCounter : Int = 0
}
private lateinit var mHandler: Handler
private lateinit var mRunnable: Runnable
override fun onBind(intent: Intent): IBinder? {
throw UnsupportedOperationException("Not yet implemented")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// Send a notification that service is started
Toast.makeText(getApplicationContext(), "Service started.", Toast.LENGTH_SHORT).show()
// Do a periodic task
mHandler = Handler()
mRunnable = Runnable { CountByOne() }
mHandler.postDelayed(mRunnable, 500)
return Service.START_STICKY
}
override fun onDestroy() {
super.onDestroy()
Toast.makeText(getApplicationContext(), "Service destroyed.", Toast.LENGTH_SHORT).show()
mHandler.removeCallbacks(mRunnable)
}
override fun onTaskRemoved(rootIntent: Intent?) {
Toast.makeText(getApplicationContext(), "On Task Removed called", Toast.LENGTH_SHORT).show()
}
// Custom method to do a task
private fun CountByOne() {
++theCounter
if (theCounter % 10 == 0) {
Toast.makeText(getApplicationContext(), "count by one " + theCounter.toString(), Toast.LENGTH_SHORT).show()
}
//showSomeText(theCounter.toString())
mHandler.postDelayed(mRunnable, 500)
}
}
The MainActivity class follows:
class MainActivity : AppCompatActivity() {
// Here is how you find out if an Android service is running
private fun isServiceRunning(serviceClass: Class<*>): Boolean {
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
// Loop through the running services
for (service in activityManager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.name == service.service.className) {
// If the service is running then return true
return true
}
}
return false
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Variable to hold service class name
val serviceClass = IntegerCounting::class.java
// Notice the service class is defined here, so subsequently all
// we need to access this service is this instance of Intent
val intent = Intent(applicationContext, serviceClass)
counterText = findViewById(R.id.counter)
counterText.text = "Ready to start counting"
val startButton: Button = findViewById(R.id.start_counting)
val stopButton: Button = findViewById(R.id.stop_counting)
val showStatus: Button = findViewById(R.id.show_status)
startButton.setOnClickListener({
if (!isServiceRunning(serviceClass)) {
counterText.text = "Starting Integer Counter service"
startService(intent)
} else {
counterText.text = "Integer Counter service already running"
}
})
stopButton.setOnClickListener {
if (isServiceRunning(serviceClass)) {
counterText.text = "Stopping Integer Counter service"
stopService(intent)
} else {
counterText.text = "Integer Counter service already stopped"
}
}
showStatus.setOnClickListener({
if (isServiceRunning(serviceClass)) {
counterText.text = "Integer Counter service is running, counter: " +
IntegerCounting.theCounter.toString()
} else {
counterText.text = "Integer Counter service is stopped, counter " +
IntegerCounting.theCounter.toString()
}
})
}
}
If your app target larger than android O it's normal behaviors when your app go background. To keeping Service try to use startForceground with Notification
While an app is in the foreground, it can create and run both foreground and background services freely. When an app goes into the background, it has a window of several minutes in which it is still allowed to create and use services. At the end of that window, the app is considered to be idle. At this time, the system stops the app's background services, just as if the app had called the services' Service.stopSelf() methods.
Documents here