Get Android Auto notification to launch auto app when clicked - Kotlin - android

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

Related

How to start another application from broadcast-receiver or a service when my application is in background

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.

How do I make may Android TV App relaunch after a crash

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
}
}

How to open app after installing manually

Installing App Manually
val install = Intent(Intent.ACTION_VIEW)
install.setDataAndType(
uri,
APP_INSTALL_PATH
)
// install.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
install.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
}
startActivity(install)
Handling Broadcast Receiver
val intentFilter = IntentFilter()
intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED)
intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED)
intentFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED)
intentFilter.addAction(Intent.ACTION_PACKAGE_INSTALL)
intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED)
intentFilter.addAction(Intent.ACTION_MY_PACKAGE_REPLACED)
intentFilter.addDataScheme("package")
registerReceiver(restartAppReceiver, intentFilter)
private val restartAppReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Toast.makeText(this#LoginActivity, getString(R.string.sign_out), Toast.LENGTH_LONG)
.show()
//start activity
val i = Intent()
Log.i("App_started", "Yes")
i.setClassName(packageName, packageName+".screen.activity.LoginActivity")
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context?.startActivity(i)
}
}
Problem Facing
Here I'm not able to receive anything on receiver when app gets installed manually. I need to open the app automatically when it gets installed manually without any action from users end.
thats simple: you can't. user must open it manually. starting Android 10 you can't start any Activity from BroadcastReceiver. besides that: most of your IntentFilter entries are no-op (why are you calling addDataScheme)

Starting an activity intent from a broadcast receiver shows the wrong activity

I am making a library that can be used to incorporate breaking and entering detection into any application
There is an arduino set to the alarm of the house which sends an SMS to a specific phone upon trigger
Within my sdk I register an sms receiver which upon receiving an sms with a specific text, should show a full screen activity (on top of the lockscreen too) that will alert the user
I created an application to test this behaviour
the application's package is : com.example.demo
the library's package is : com.example.sdk
the sms receiver looks like this:
class SMSReceiver : BroadcastReceiver() {
companion object {
const val TAG = "SMSReceiver"
}
private val logger by lazy { Injections.logger }
override fun onReceive(context: Context?, intent: Intent?) {
logger.log(TAG) { "Got sms" }
val ctx = context ?: return
val bundle = intent?.extras ?: return
val format = bundle.getString("format") ?: return
val pdus = (bundle["pdus"] as? Array<*>) ?: return
for (idx in pdus.indices) {
val pdu = pdus[idx] as? ByteArray ?: continue
val msg = SmsMessage.createFromPdu(pdu, format)
if (msg.messageBody.startsWith("theft event", true)) {
logger.log(TAG) { "Got theft event" }
abortBroadcast()
showTheftActivity(ctx, msg.messageBody)
break
}
}
}
private fun showTheftActivity(context: Context, messageBody: String) {
val intent = Intent(context, TheftActivity::class.java)
intent.addFlags(Intent.FLAG_FROM_BACKGROUND)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
// .addCategory(Intent.CATEGORY_LAUNCHER)
val location = messageBody.split(" ").getOrNull(2)
if (location != null) {
val coords = location.split(",")
if (coords.size == 2) {
val x = coords[0].toBigDecimalOrNull()
val y = coords[1].toBigDecimalOrNull()
if (x != null && y != null) {
intent.putExtra(TheftActivity.X, x.toString())
intent.putExtra(TheftActivity.Y, y.toString())
}
}
}
context.startActivity(intent)
}
}
the activity that should show on top of everything is this :
class TheftActivity : Activity() {
companion object {
const val X = "locationX"
const val Y = "locationY"
}
private val x: String? by lazy { intent.getStringExtra(X) }
private val y: String? by lazy { intent.getStringExtra(Y) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_theft)
val location = findViewById<Button>(R.id.locate)
if (x != null && y != null) {
location.setOnClickListener { Toast.makeText(applicationContext, "Going to $x , $y", Toast.LENGTH_SHORT).show() }
location.isEnabled = true
finish()
} else {
location.isEnabled = false
}
findViewById<Button>(R.id.cancel).setOnClickListener {
finish()
}
turnScreenOnAndKeyguardOff()
}
private fun turnScreenOnAndKeyguardOff() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(true)
setTurnScreenOn(true)
} else {
window.addFlags(
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
)
}
with(getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
requestDismissKeyguard(this#TheftActivity, null)
}
}
}
override fun onDestroy() {
super.onDestroy()
turnScreenOffAndKeyguardOn()
}
private fun turnScreenOffAndKeyguardOn() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(false)
setTurnScreenOn(false)
} else {
window.clearFlags(
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
)
}
}
}
and the sdk's android manifest contains this:
<application>
<activity
android:name=".ui.TheftActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:excludeFromRecents="true"
android:label="#string/title_activity_theft"
android:showOnLockScreen="true"
android:theme="#style/Theme.Sdk.Fullscreen" />
<receiver
android:name=".receivers.SMSReceiver"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BROADCAST_SMS">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>
when testing this on an emulator I send the sms to trigger the theft event
if the activity for testing (the one in the com.example.demo package) is not closed then it is brought to the front , but if it is closed nothing happens (though I do see the log messages from the receiver)
how can I make my sms receiver open the TheftActivity instead of the main activity from the main package?
edit: if it helps, the theft activity seems to start and then get immediately destroyed
It looks like the system can't bring the activity to the foreground due to the restrictions implemented in Android Q
With Android Q, it is impossible to start an activity from the background automatically if your app does not include those exceptions listed in the link below.
https://developer.android.com/guide/components/activities/background-starts
For possible solutions :
https://stackoverflow.com/a/59421118/11982611

Detecting whether a headset is plugged into an Android device or not.

How can I determine whether a headset is plugged into an Android device or not?
You can use the broadcast receiver.
So, You might write this code in "AndroidManifest.xml"
<receiver android:name="com.juno.brheadset.HeadsetStateReceiver">
<intent-filter>
<action android:name="android.intent.action.HEADSET_PLUG"/>
</intent-filter>
</receiver>-->
But, This doesn't work. When OS send this "HEADSET_PLUG" intent, OS set the flag "Intent.FLAG_RECEIVER_REGISTERED_ONLY" So, You should write the code like below in Activity or Service class instead of "AndroidManifest" things.
public class BRHeadsetActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
IntentFilter receiverFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
HeadsetStateReceiver receiver = new HeadsetStateReceiver();
registerReceiver( receiver, receiverFilter );
}
I hope this article help you. Bye!
This is the part of "HeadsetObserver.java", Android SDK Source.
private final void sendIntent(int headset, int headsetState, int prevHeadsetState, String headsetName) {
if ((headsetState & headset) != (prevHeadsetState & headset)) {
// Pack up the values and broadcast them to everyone
Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG);
**intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);**
int state = 0;
int microphone = 0;
if ((headset & HEADSETS_WITH_MIC) != 0) {
microphone = 1;
}
if ((headsetState & headset) != 0) {
state = 1;
}
intent.putExtra("state", state);
intent.putExtra("name", headsetName);
intent.putExtra("microphone", microphone);
if (LOG) Slog.v(TAG, "Intent.ACTION_HEADSET_PLUG: state: "+state+" name: "+headsetName+" mic: "+microphone);
// TODO: Should we require a permission?
ActivityManagerNative.broadcastStickyIntent(intent, null);
}
}
When you say "headset", do you mean "wired headset"? If so, there's an intent to detect whether or not one is being plugged or unplugged: ACTION_HEADSET_PLUG.
To check the status, you can use AudioManager.isWiredHeadsetOn(), although that may return false if there is also a bluetooth headset, and audio is routed to that instead.
AudioManager.isWiredHeadsetOn() always return false because it requires user-permission MODIFY_AUDIO_SETTINGS.
I spent several days while found answer. There are no info about this in official documentation. And this bug already registered in BugTracker.
This should help you : http://developer.android.com/reference/android/content/Intent.html#ACTION_HEADSET_PLUG
You can create this kind of receiver class (Kotlin with Flow) in your project:
class HeadsetPlugReceiver : BroadcastReceiver() {
private val _isPlugged = MutableStateFlow<Boolean>(false)
val isPlugged: StateFlow<Boolean> = _isPlugged.asStateFlow()
override fun onReceive(context: Context, intent: Intent) {
context.appComponent.inject(this)
val action = intent.action
Log.i(TAG, "onReceive: $action")
when (action) {
Intent.ACTION_HEADSET_PLUG -> sendEvent(intent)
else -> checkStateOff(intent)
}
}
private fun checkStateOff(intent: Intent) {
Log.i(TAG, "onReceive: the local Bluetooth adapter is off")
}
private fun sendEvent(intent: Intent) {
val isPlugged = intent.getIntExtra(HEADSET_STATE, 0) == 1
Log.i(TAG, "sendEvent: $isPlugged")
_isPlugged.value = isPlugged
}
private companion object {
private const val TAG = "HeadsetPlugReceiver"
// Headset constant
private const val HEADSET_STATE = "state"
}
}
then register this receiver in some class with context:
val headsetReceiver = HeadsetPlugReceiver()
val headsetFilter = IntentFilter(Intent.ACTION_HEADSET_PLUG)
context.registerReceiver(headsetReceiver, headsetFilter)
then collect isPlugged and you get the state of your wire headset connecting state
PS: don't forget to unregister your receiver when it no needs context.unregisterReceiver(headsetReceiver)
First , create receiver in your manifest:
<receiver android:name="com.yourapplication.BroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.HEADSET_PLUG"/>
</intent-filter>
</receiver>
don't forget to change com.yourapplication according to your project name
Create two variables in the head of your activity :
private BroadcastReceiver mReceiver ;
boolean Microphone_Plugged_in = false;
Define your receiver inside onCreate of your activity :
mReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
int iii=2;
if (Intent.ACTION_HEADSET_PLUG.equals(action)) {
iii=intent.getIntExtra("state", -1);
if(Integer.valueOf(iii)==0){
Microphone_Plugged_in = false;
Toast.makeText(getApplicationContext(),"microphone not plugged in",Toast.LENGTH_LONG).show();
}if(Integer.valueOf(iii)==1){
Microphone_Plugged_in = true;
Toast.makeText(getApplicationContext(),"microphone plugged in",Toast.LENGTH_LONG).show();
}
}
}};
IntentFilter receiverFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
registerReceiver( mReceiver, receiverFilter );
add onResume and onStope :
#Override
protected void onResume() {
super.onResume();
IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
getApplicationContext().registerReceiver(mReceiver, filter);
}
#Override
protected void onStop() {
super.onStop();
getApplicationContext().unregisterReceiver(mReceiver);
}
To add to the other answers, from Android documentation:
Warning: Limit how many broadcast receivers you set in your app.
Having too many broadcast receivers can affect your app's performance
and the battery life of users' devices. For more information about
APIs you can use instead of the BroadcastReceiver class for scheduling
background work, see Background Optimizations.
https://developer.android.com/guide/topics/manifest/receiver-element
Which means that you should create a small number of broadcast receivers as possible, to prevent memory issues with your app.
I will suggest using a singleton class with this receiver. In Kotlin:
class HeadsetReceiver private constructor(): BroadcastReceiver() {
// instances
var callback: HeadsetReceiverCallback? = null
//region singleton
private object HOLDER {
val INSTANCE = HeadsetReceiver()
}
companion object {
val instance: HeadsetReceiver by lazy { HOLDER.INSTANCE }
}
//endregion
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_HEADSET_PLUG) {
if(intent.getIntExtra("state", -1) == 0) {
callback?.onHeadsetDisconnected()
} else {
callback?.onHeadsetConnected()
}
}
}
fun register(context: Context) {
val receiverFilter = IntentFilter(Intent.ACTION_HEADSET_PLUG)
context.registerReceiver(this, receiverFilter)
}
fun unregister(context: Context) {
context.unregisterReceiver(this)
callback = null
}
interface HeadsetReceiverCallback {
fun onHeadsetConnected()
fun onHeadsetDisconnected()
}
}
Register:
HeadsetReceiver.instance.register(context)
HeadsetReceiver.instance.callback = object : HeadsetReceiver.HeadsetReceiverCallback {
override fun onHeadsetConnected() {
println("connected!")
}
override fun onHeadsetDisconnected() {
println("disonnected!")
}
}
Unregister:
HeadsetReceiver.instance.unregister(context)

Categories

Resources