I'm trying to create an app that can broadcast Android views on the Chromecast, and I thought I found something promising in CastRemoteDisplayLocalService. I created a simple test app but found the callback onCreatePresentation was never called when I casted my device. After some searching I discovered it was because my application was not published as a Remote Display Application but a Custom Application Receiver from the Google Cast Developer Console.
Unfortunately when I try to create a new application from the console, Remote Display Application is not an option. After some searching, I came across this Stack Overflow question that said Remote Display API is now deprecated. There is an interface called CastRemoteDisplayApi which is marked as deprecated, but the classes I have been trying to use are not marked as such.
This leads me to wondering if CastRemoteDisplayLocalService and all other Remote Display classes not marked as deprecated are in fact deprecated and unusable, or if perhaps the functionality was shifted to work in a Custom Receiver by configuring it to accept remote displays.
This is what the relevant code looks like right now:
MainActivity.kt
private fun startCastService() {
val intent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(this, 0, intent, 0)
val notificationSettings = CastRemoteDisplayLocalService.NotificationSettings.Builder().setNotificationPendingIntent(pendingIntent).build()
CastRemoteDisplayLocalService.startService(this, CastRemoteDisplayLocalServiceImpl::class.java, "2839EC8D", castDevice, notificationSettings, object : CastRemoteDisplayLocalService.Callbacks {
override fun onRemoteDisplaySessionEnded(p0: CastRemoteDisplayLocalService?) {
Log.d(TAG, "onRemoteDisplaySessionEnded")
}
override fun onRemoteDisplaySessionError(p0: Status?) {
Log.d(TAG, "onRemoteDisplaySessionError")
}
override fun onRemoteDisplaySessionStarted(p0: CastRemoteDisplayLocalService?) {
Log.d(TAG, "onRemoteDisplaySessionStarted")
}
override fun onServiceCreated(p0: CastRemoteDisplayLocalService?) {
Log.d(TAG, "onServiceCreated")
}
})
}
CastRemoteDisplayLocalServiceImpl.kt
class CastRemoteDisplayLocalServiceImpl : CastRemoteDisplayLocalService() {
val TAG = "CastRemoteDisplayLoc..."
// This function gets called
override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate")
}
// This function does not get called
override fun onCreatePresentation(p0: Display?) {
Log.d(TAG, "onCreatePresentation")
}
override fun onDismissPresentation() {
Log.d(TAG, "onDismissPresentation")
}
}
If there's a way with the Custom Application Receiver to get the onCreatePresentation callback that would solve this, I'm having difficulty finding it. If CastRemoteDisplayLocalService is in fact deprecated, is there another way to easily cast Android views to a Chromecast? Thanks!
Related
I am trying to integrate stripe terminal code with my android app build using kotlin, unfortunately I am getting the following run time error which I could not able to fix
java.lang.IllegalStateException: initTerminal must be called before attempting to get the instance
The code I have added is used below
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pay_screen)
onDiscoverReaders()
}
fun onDiscoverReaders() {
val config = DiscoveryConfiguration(
timeout = 0,
discoveryMethod = DiscoveryMethod.LOCAL_MOBILE,
isSimulated = false,
location = "xxxxxxxxxxxxxxx"
)
// Save this cancelable to an instance variable
discoveryCancelable = Terminal.getInstance().discoverReaders(config,
discoveryListener = object : DiscoveryListener {
override fun onUpdateDiscoveredReaders(readers: List<Reader>) {
}
}
, object : Callback {
override fun onSuccess() {
println("Finished discovering readers")
}
override fun onFailure(e: TerminalException) {
e.printStackTrace()
}
})
}
I have added this to one of my activity and my intention is to check if my phone is supporting stripe tap on mobile
I guess the issue could be calling onDiscoverReaders() from a wrong place, someone please help me to fix this issue
Thanks in advance
In stripe docs you can check
// Create your listener object. Override any methods that you want to be notified about
val listener = object : TerminalListener {
}
// Choose the level of messages that should be logged to your console
val logLevel = LogLevel.VERBOSE
// Create your token provider.
val tokenProvider = TokenProvider()
// Pass in the current application context, your desired logging level, your token provider, and the listener you created
if (!Terminal.isInitialized()) {
Terminal.initTerminal(applicationContext, logLevel, tokenProvider, listener)
}
// Since the Terminal is a singleton, you can call getInstance whenever you need it
Terminal.getInstance()
might be you missed to initialise terminal before getting Instance so try add above code before onDiscoverReaders()
The error speaks for itself - first you need to initialize the api terminal, and then call the terminal instance.
Based on the documentation, we follow the following steps to get started with the api terminal:
Initialize the terminal application in the application class of the
application
class App : Application() {
override fun onCreate() {
super.onCreate()
TerminalApplicationDelegate.onCreate(this)
}
}
We request the necessary permissions for correct work with the
terminal search (bluetooth, geolocation), if everything is provided,
we call the init terminal with parameters like that:
Terminal.initTerminal(
context = context,
logLevel = LogLevel.VERBOSE,
tokenProvider = TokenProvider(),
listener = object : TerminalListener {
override fun onUnexpectedReaderDisconnect(reader: Reader) {
Log.d("log", "onUnexpectedReaderDisconnect")
}
override fun onConnectionStatusChange(status: ConnectionStatus) {
super.onConnectionStatusChange(status)
Log.d("log", "onConnectionStatusChange")
}
override fun onPaymentStatusChange(status: PaymentStatus) {
super.onPaymentStatusChange(status)
Log.d("log", "onPaymentStatusChange")
}
}
)
After this initialization, you can call the terminal instance and
work with it.
In my project, for security reasons, I need to be aware if USB debugging is enabled on device in order to disable some sensitive functionality.
I am able to figure out if it is currently enabled or not by doing the following check:
Settings.Global.getInt(context.contentResolver, Settings.Global.ADB_ENABLED, 0) == 1
But I also need to know when it is being enabled/disabled reactively. So I need to have a broadcast receiver which listens for this setting change events.
I searched the web but did not even found any question about this and even not found if Android even has such a broadcast receiver or not.
So any information related to this topic would be useful.
Well I found the answer to this question myself.
Actually there is no BroadcastReceiver for tracking changes of USB debugging setting. However, because Android settings are mostly exposed through ContentProvider Api we can register a ContentObserver on any data exposed through ContentProvider.
Below is the code snipped that did the work for me for USB debugging, but with slightly changes this can work to listen changes for almost any other Android setting change:
class UsbDebuggingStateObserver private constructor(val context: Context, handler: Handler, val changeListener: (Boolean) -> Unit) : ContentObserver(handler) {
private var lastEnabled: Boolean = isUsbDebuggingEnabled()
override fun onChange(selfChange: Boolean) {
val nowEnabled = isUsbDebuggingEnabled()
if(lastEnabled != nowEnabled){
lastEnabled = nowEnabled
changeListener.invoke(nowEnabled)
}
}
fun isUsbDebuggingEnabled(): Boolean {
return Settings.Global.getInt(context.contentResolver, Settings.Global.ADB_ENABLED, 0) == 1
}
companion object {
fun startObserving(context: Context, changeListener: (Boolean) -> Unit){
val observer = UsbDebuggingStateObserver(context, Handler(Looper.getMainLooper()), changeListener)
context.contentResolver
.registerContentObserver(Settings.Global.getUriFor(Settings.Global.ADB_ENABLED), true, observer)
}
}
}
And the usage is as follows:
UsbDebuggingStateObserver.startObserving(app) { enabled ->
// your code here
}
Note, I needed to observe this setting through whole lifetime of my application, so I start observing in onCreate of my application class and never stop. If you have different situation you might need also to unregister the observer.
In my app I start a WebSocketWorker tasks that runs periodically every 15 minutes. As the name implies, it contains a WebSocket for listening to a socket in the background:
// MainApplication.kt
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(DebugTree())
}
val work = PeriodicWorkRequestBuilder<WebSocketWorker>(15, TimeUnit.MINUTES).build()
workManager.enqueueUniquePeriodicWork("UniqueWebSocketWorker", ExistingPeriodicWorkPolicy.KEEP, work)
}
The WebSocketWorker contains the following logic:
#HiltWorker
class WebSocketWorker #AssistedInject constructor(
#Assisted appContext: Context,
#Assisted workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {
inner class MyWebSocketListener : WebSocketListener() {
override fun onMessage(webSocket: WebSocket, text: String) {
Timber.d("The message sent is %s", text)
// do sth. with the message
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
t.localizedMessage?.let { Timber.e("onFailure: %s", it) }
response?.message?.let { Timber.e("onFailure: %s", it) }
}
}
override suspend fun doWork(): Result {
try{
// code to be executed
val request = Request.Builder().url("ws://***.***.**.***:8000/ws/chat/lobby/").build()
val myWebSocketListener = MyWebSocketListener()
val client = OkHttpClient()
client.newWebSocket(request, myWebSocketListener)
return Result.success()
}
catch (throwable:Throwable){
Timber.e("There is a failure")
Timber.e("throwable.localizedMessage: %s", throwable.localizedMessage)
// clean up and log
return Result.failure()
}
}
}
As you can see, in the Worker class I set the WebSocket and everything is fine. Listening to the socket works.
Now, I also want to add the "sending of messages" functionality to my app. How can I reuse the websocket created in WebSocketWorker? Can I pass input data to the WebSocketWorker that runs in the background ?
Let's say I have a EditText for typing the message and a Button to send the message with a setOnClickListener attached like this:
binding.sendButton.setOnClickListener {
// get message
val message = binding.chatMessageEditText.text.toString()
// check if not empty
if(message.isNotEmpty()) {
// HOW CAN I REUSE THE WEBSOCKET RUNNING PERIODICALLY IN THE BACKGROUND?
// CAN I PASS THE MESSAGE TO THAT WEBSOCKET ?
// OR SHOULD I CREATE A DIFFERENT WORKER FOR SENDING MESSAGES (e.g.: a OneTimeRequest<SendMessageWorker> for sending messages ?
}
}
From the documentation, I know that you need to build Data objects for passing inputs and so on but there was no example which showcased how to pass input to a worker running periodically in the background.
My experience is saying that you can. Basically you "can't" interact with the worker object via the API. It is really annoying.
For example, with the JS you have the option to get a job and check the parameters of the job. There is no such option with the work. For example, I want to check what is the current state of the restrictions - what is satisfied, what is not. Nothing like this. You can just check states, cancel and that is almost all.
My suggestions is that it is because the WorkManager is a "facade/adapter" over other libraries like JS. It has it's own DB to restore JS jobs on device restart and stuff like this, but beside that if you want to interact with the internals I guess it was just too complicated for them to do so they just skipped.
You can just inject some other object and every time the work can ask it for it's data. I don't see other option.
This one will require a bit of context, please bare with me...
I have migrated a dependency for evaluating NFC data to a new application. Whenever an NFC tag is discovered, my application will spawn an Activity to handle the event. In the onCreate function of this NfcActivity, a background service (let's call it MyNfcHelperService) is started to retrieve some data on the scanned tag:
class NfcActivity : AppCompatActivity() {
/*...*/
override fun onCreate(savedInstanceState: Bundle?) {
/*...*/
val intent = Intent(this, MyNfcHelperService::class.java)
.putExtra(/*...*/)
startService(intent)
}
}
The work produced by this service is later retrieved and used by the NfcActivity. It all used to work just fine, but once released into the wild we noticed some crashes, which would report Not allowed to start service Intent on the startService(intent) call.
I quickly came across this related post, suggesting this is due to some improvements in RAM management on background processes (introduced in Android 8).
Following the accepted answer and comments raised, I studied the docs on JobIntentServices and ended up with a similar setup. I would've liked to drop the MyNfcHelperService all together and move its logic into the MyJobIntentService. But what happens inside MyNfcHelperService is an absolute black-box to me. Thus, I wrapped the aforementioned service inside the onHandleWork of my derived JobIntentService like so:
class MyJobIntentService: JobIntentService() {
companion object {
private const val JOB_ID = 1000
fun start(context: Context) {
val intentPkm = Intent(context, MyNfcHelperService::class.java)
.putExtra(/*...*/)
enqueueWork(context, intentPkm)
}
private fun enqueueWork(context: Context, intent: Intent) {
enqueueWork(context, MyJobIntentService::class.java, JOB_ID, intent)
}
}
override fun onHandleWork(intent: Intent) {
applicationContext.startService(intent)
}
}
Then I applied this class in NfcActivity:
class NfcActivity : AppCompatActivity() {
/*...*/
override fun onCreate(savedInstanceState: Bundle?) {
/*...*/
MyJobIntentService.start(applicationContext)
}
}
Thus far, the code seems to work. But I am hesitant to release it into the wild, because it feels a bit hacky and I am unsure if this solution actually solved the aforementioned issue. After all, I understand that this infrastructure creates a background service from a scheduled job.
So, is my code robust towards the java.lang.IllegalStateException: Not allowed to start service Intent error, or did I totally head the wrong way? If the latter is the case, can anyone suggest an alternate approach, taking into account that I cannot access the guts of MyNfcHelperService?
After #MD's comment, I changed my approach towards using WorkManager. This should be most robust against different API versions. I followed the official docs and arrived at the following worker setup:
class MyNfcHelperServiceWorker(val context: Context, workerParams: WorkerParameters): Worker(context, workerParams) {
override fun doWork(): Result {
val intent = Intent(context, MyNfcHelperService::class.java)
.putExtra(/*...*/)
context.startService(intent)
return Result.success()
}
}
Then I adjusted the code inside NfcActivity like so:
class NfcActivity : AppCompatActivity() {
/*...*/
override fun onCreate(savedInstanceState: Bundle?) {
/*...*/
OneTimeWorkRequestBuilder<MyNfcHelperServiceWorker>()
.build()
.also { helperServiceWorkRequest ->
WorkManager.getInstance(this)
.enqueue(helperServiceWorkRequest)
}
}
}
Initial tests have worked just fine. My understanding of why this fixes the java.lang.IllegalStateException: Not allowed to start service Intent issue would be that android will now schedule a work request which meets the requirements of launching MyNfcHelperService only when my app is allowed to create background processes.
That said, I still have a bit of a headache using a worker to start a service. Feels really redundant to do so and I am unsure of any additional implications this may lead. Thus, I wont accept this as an answer just now.
I'd really appreciate any additional comments and/or answers on the matter!
So I am making a Flutter plugin and I am attempting to run Kotlin code on Android. The problem is, this code runs a method which attempts to start an activity without the FLAG_ACTIVITY_NEW_TASK flag on the intent. The problem with this is that it also does NOT have a way to give it an intent instance as it attempts to instantiate an instance inside the method itself. The method expects to be called from a button or other method that is stored on the UI and called from it. However, since it is called from the onMethodCall method in the Flutter plugin, it does not seem to work. I have attempted many workarounds such as adding a method inside the Activity and running the code inside while calling it from the flutter plugin class. I have also tried using the UIThread and no luck either. Any workarounds?
Note: I have not provided any code due to keeping this API hidden. It should only be known that I am running the code from the onMethodCall event.
Error: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
You can extend your plugin to implement ActivityAware in your plugin class, when you implement it, you get a couple of callbacks that gives you the current activity. Like this :
lateinit activity: Activity? = null
override fun onDetachedFromActivity() {
activity = null
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onDetachedFromActivityForConfigChanges() {
activity = null
}
After that you can just startActivity from the assigned activity variable.
Let me know if you need further help.
As you mentioned, For Flutter plugin any platform-dependent logics should be kept in the subclass of FlutterActivity which was used to show flutter module/screens inside a native module. Now you can launch intent from that subclass without any additional flags.
#note - Subclass of FlutterActvity should be kept in the native module.
class FlutterResponseActivity : FlutterActivity() {
private var methodResult: Result? = null
override fun provideFlutterEngine(context: Context): FlutterEngine? {
return MyApplication.mContext.flutterEngine //Pre-warmed flutter engine
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"startMainActivity" -> {
startMainActivity()
result.success(true)
}
else -> result.notImplemented()
}
}
}
private fun startMainActivity() {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
}