I'm trying to implement video casting for my flutter app using the package cast
Ive registered my app on the cast console and able to cast my videos from the sender android mobile app made with flutter using the following code to the Android TV
session.value.stateStream.listen((state) async {
log('session state->' + state.toString());
if (state == CastSessionState.connecting) {
log('Connecting...');
}
if (state == CastSessionState.connected) {
var media = {
"contentId": _purchaseController.purchasedMedia.value.mpdH264,
'contentUrl': _purchaseController.purchasedMedia.value.mpdH264,
"streamType": "BUFFERED", // or LIVE
"contentType": "application/dash+xml",
"metadata": {
"metadataType": 1,
"title": video.title,
"studio": "Lersia",
"releaseDate": video.releaseYear,
"images": [
{"url": video.lgLandscape}
]
},
"customData": {
"token": _purchaseController.purchasedMedia.value.drmTokenH264,
}
};
try {
session.value.sendMessage(CastSession.kNamespaceMedia, {
'type': 'LOAD',
'autoPlay': true,
'media': media,
});
Get.snackbar("Cast Success", "Video casted to $_deviceName");
setupNotifications();
MediaPlayerCentral.add(video);
MediaPlayerCentral.playPause();
} catch (e) {
log('stateStreamOnConnectedError=>' + e.toString());
}
}
if (state == CastSessionState.closed) {
try {
Get.snackbar("Cast Disconnected", "Disconnected from $_deviceName");
connectedDevice.value = null;
castingVideo.value = null;
NotificationUtils.cancelNotification(100);
MediaPlayerCentral.stop();
connectedDevice.value = null;
if (session.value != null) {
await session.value.socket.flush();
await session.value.socket.close();
session.value = null;
} else {
session.value = null;
}
} catch (e) {
log('stateStreamOnClosedError=>' + e.toString());
}
}
});
var index = 0;
session.value.messageStream.listen((message) {
log("messageStream " + message.toString());
var _status = message['status'];
if (_status != null && _status is List && _status.isNotEmpty) {
var _returnedTime = _status.first['currentTime'];
currentPlayInSeconds.value = (_returnedTime is int)
? _returnedTime
: int.tryParse(_returnedTime.toString());
}
requestId.value = message['requestId'];
index += 1;
if (index == 4) {
Future.delayed(Duration(seconds: 2)).then((x) {
var media = {
"contentId": _purchaseController.purchasedMedia.value.mpdH264,
"streamType": "BUFFERED", // or LIVE
"contentType": "application/dash+xml",
"metadata": {
"metadataType": 1,
"title": video.title,
"studio": "Lersia",
"releaseDate": "30 Sep 2021",
'posterUrl': video.lgLandscape,
"images": [
{"url": video.lgLandscape}
]
},
"customData": {
"licenseUrl":
_purchaseController.purchasedMedia.value.drmWidevine,
"token": _purchaseController.purchasedMedia.value.drmTokenH264,
}
};
try {
session.value.sendMessage(CastSession.kNamespaceMedia, {
'requestId': 1,
'type': '',
'media': media,
});
} catch (e) {
log('messageStreamListenError=>' + e.toString());
}
});
}
});
session.value.sendMessage(CastSession.kNamespaceReceiver, {
'type': 'LAUNCH',
'appId': '0E0BA573', // set the appId of your app here
});
On the android TV, the cast message is received and the video is opened on the browser even when the TV app is installed. I've added the CastOptionsProvider class and specified it on the manifest like below
class CastOptionsProvider : OptionsProvider {
override fun getCastOptions(context: Context): CastOptions? {
val notificationOptions = NotificationOptions.Builder()
.build()
val mediaOptions = CastMediaOptions.Builder()
.setNotificationOptions(notificationOptions)
.build()
val credentialsData = CredentialsData.Builder()
.setCredentials("{\"userId\": \"abc\"}")
.build()
val launchOptions: LaunchOptions = LaunchOptions.Builder()
.setAndroidReceiverCompatible(true)
.setCredentialsData(credentialsData)
.build()
return CastOptions.Builder()
.setLaunchOptions(launchOptions)
.setReceiverApplicationId(context.getString(R.string.app_id))
.setCastMediaOptions(mediaOptions)
.build();
}
override fun getAdditionalSessionProviders(context: Context?): List<SessionProvider?>? {
return null
}
}
And this is the manifest file
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.lersia.mobile">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:label="Lersia Play"
android:icon="#mipmap/ic_launcher"
android:roundIcon="#mipmap/ic_launcher_round">
<activity
android:name="com.lersia.mobile.MainActivity"
android:launchMode="singleTop"
android:theme="#style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:exported="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="#style/NormalTheme"
/>
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="#drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.lersia.mobile.CastOptionsProvider" />
<!-- Don't delete the meta-data below.CastOptionsProvider
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
I've also added the CastReceiverOptionsProvider on the TV app like below
class CastReceiverOptionsProvider : ReceiverOptionsProvider {
override fun getOptions(context: Context?): CastReceiverOptions? {
return CastReceiverOptions.Builder(context)
.setStatusText("Lersia tv status text")
.build()
}
}
and specified it on the manifest file of the TV app as below
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.lersia.tv">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-feature android:name="android.software.leanback" android:required="true" />
<uses-feature android:name="android.hardware.touchscreen"
android:required="false" />
<application
android:name="com.lersia.tv.MyApplication"
android:label="Lersia Play"
android:banner="#drawable/launch_banner"
android:icon="#mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:banner="#drawable/launch_banner"
android:launchMode="singleTask"
android:theme="#style/LaunchTheme"
android:exported="true"
android:label="Lersia Play"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="#style/NormalTheme"/>
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="#drawable/launch_background"/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LAUNCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name="com.lersia.tv.TestIntent"
android:exported="false"
android:launchMode="singleTask">
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LOAD"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<meta-data
android:name="com.google.android.gms.cast.tv.RECEIVER_OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.lersia.tv.CastReceiverOptionsProvider" />
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
Am I missing something on why the TV app is not opening? Would be glad if someone can help
Related
I've been trying to use Android code in a Flutter project to do audio-related tasks, and I've been following Flutter's guide to do so. However, when I try to call the method on the method channel, I've been getting a MissingPluginException that says that the method (getAudioSessionID) isn't implemented. I found a post on a similar issue, but it doesn't seem like it was solved. I put some print statements in MainActivity, but they aren't being called.
This is what the MainActivity class looks like in MainActivity.kt:
class MainActivity: FlutterActivity() {
private val CHANNEL = "XXX.XXX/eqAndroid"
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
call, result ->
print("This is being called")
if (call.method == "getAudioSessionID") {
val audioSessionID = getAudioSessionID()
print(audioSessionID)
} else {
result.notImplemented()
print("else block in if/else statement")
}
}
}
private fun getAudioSessionID(): String {
return AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION
}
}
I defined a global variable for the method channel in a constants.dart file:
const eqAndroidPlatform = MethodChannel('XXX.XXX/eqAndroid');
, and this is the function that is supposed to invoke the getAudioSessionID method:
Future<void> getAudioSessionID() async {
String audioSessionID;
try {
final String result = await eqAndroidPlatform.invokeMethod('getAudioSessionID');
audioSessionID = 'Currently playing audio session ID is $result .';
} on PlatformException catch (e) {
audioSessionID = "Failed to get audio session ID: '${e.message}'.";
}
setState(() {
this.audioSessionID = audioSessionID;
});
}
This is the code in AndroidManifest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="XXX.XXX.XXX">
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<application
android:name="${applicationName}"
android:icon="#mipmap/ic_launcher"
android:label="XXX">
<activity
android:name="com.ryanheise.audioservice.AudioServiceActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:theme="#style/LaunchTheme"
android:windowSoftInputMode="adjustResize"
tools:ignore="Instantiatable">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="#style/NormalTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="com.ryanheise.audioservice.AudioService"
android:exported="true" tools:ignore="Instantiatable">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
<receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver"
android:exported="true" tools:ignore="Instantiatable">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
<receiver android:name=".receivers.AudioSessionReceiver">
<intent-filter>
<action android:name="android.media.action.OPEN_AUDIO_EFFECT_CONTROL_SESSION"/>
</intent-filter>
</receiver>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
I want to show an AlertDialog when the Firebase cloud message received (and either the Android application is foreground or background).
In forground, the app shows the AlertDialog, but in background, the app doesn't show the AlertDialog.
MyFirebaseMessageService.kt
class MyFirebaseMessageService : FirebaseMessagingService() {
private val TAG = "fcm_tag"
override fun onMessageReceived(remoteMessage: RemoteMessage) {
val title = remoteMessage.notification?.title
val body = remoteMessage.notification?.body
Log.d(TAG, "fcm title: $title")
Log.d(TAG, "fcm body: $body")
val alertDialogIntent = Intent(baseContext, AlertDialogActivity::class.java)
alertDialogIntent.putExtra("title", title)
alertDialogIntent.putExtra("body", body)
try {
PendingIntent.getActivity(
baseContext,
0,
alertDialogIntent,
PendingIntent.FLAG_IMMUTABLE
).send()
} catch (e: Exception) {
Log.d(TAG, "fcm error: $e")
}
}
}
AlertDialogActivity.kt
class AlertDialogActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val bund = intent.extras
val title = bund?.getString("title")
val body = bund?.getString("body")
AlertDialog.Builder(this).run {
setTitle(title)
setMessage(body)
setPositiveButton("OK") { _, _ ->
finish()
}
setCancelable(false)
show()
}
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.myproject">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.MyProject">
<service
android:name=".MyFirebaseMessageService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<activity
android:name=".AlertDialogActivity"
android:exported="true"
android:theme="#style/Theme.AppCompat.DayNight.Dialog">
</activity>
<activity
android:name=".MainActivity"
android:exported="true"
android:label="#string/app_name"
android:theme="#style/Theme.MyProject">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="fcm_default_channel" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="#drawable/ic_launcher_foreground" />
</application>
</manifest>
AlertDialog alertDialog = new AlertDialog.Builder(this)
.setTitle("Title")
.setMessage("Are you sure?")
.create();
alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
alertDialog.show();
And ask for this permission
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
I am trying to create payment app which support the tap & pay functionality. I am already done with the Connecting App to NFC terminal with use of SELECT APDU Command.
Now, I want to send the stored card details from app to NFC Terminal, so it can make transaction.
Can you suggest me any kind of documents, which can help me to achieve it, such as how to send the details securely and in what kind of format it should be send?
any help would be appreciated.
here is the code
Android Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.nfcemulator">
<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc.hce"
android:required="true" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.NFCEmulator">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".service.HCEService"
android:exported="true"
android:permission="android.permission.BIND_NFC_SERVICE">
<intent-filter>
<action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE" />
</intent-filter>
<meta-data
android:name="android.nfc.cardemulation.host_apdu_service"
android:resource="#xml/apduservice" />
</service>
</application>
</manifest>
HCE Service
class HCEService: HostApduService() {
companion object {
val TAG = "Host Card Emulator"
val STATUS_SUCCESS = "9000"
val STATUS_FAILED = "6F00"
val CLA_NOT_SUPPORTED = "6E00"
val INS_NOT_SUPPORTED = "6D00"
val AID = "A0000002471001"
val SELECT_INS = "A4"
val DEFAULT_CLA = "00"
val MIN_APDU_LENGTH = 12
}
override fun onDeactivated(reason: Int) {
Log.d(TAG, "Deactivated: " + reason)
}
override fun processCommandApdu(commandApdu: ByteArray?, extras: Bundle?): ByteArray {
if (commandApdu == null) {
return Utils.hexStringToByteArray(STATUS_FAILED)
}
val hexCommandApdu = Utils.toHex(commandApdu)
if (hexCommandApdu.length < MIN_APDU_LENGTH) {
return Utils.hexStringToByteArray(STATUS_FAILED)
}
if (hexCommandApdu.substring(0, 2) != DEFAULT_CLA) {
return Utils.hexStringToByteArray(CLA_NOT_SUPPORTED)
}
if (hexCommandApdu.substring(2, 4) != SELECT_INS) {
return Utils.hexStringToByteArray(INS_NOT_SUPPORTED)
}
if (hexCommandApdu.substring(10, 24) == AID) {
return Utils.hexStringToByteArray(STATUS_SUCCESS)
} else {
return Utils.hexStringToByteArray(STATUS_FAILED)
}
}
}
apduservices.xml
<?xml version="1.0" encoding="utf-8"?>
<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="#string/hce_service"
android:requireDeviceUnlock="false">
<aid-group android:description="#string/aid_groups"
android:category="other">
<aid-filter android:name="325041592E5359532E4444463031"/>
</aid-group>
</host-apdu-service>
I know this's a repeated question but i've looked all over the place and couldn't find a solution that works for me so...
I've an app that fetches movie data from TMDB API, it fetches it by page using a sync adapter, basically it runs great except when the sync adapter's periodic sync is ran while the app is open and the user is not at the first page, so my options were to force update the movies list and send the user to the top of the list which is totally bad experience so not considered as an option, Or i could check if the app is running, either in foreground or in the app stack that it's not visible to the user yet still running, so the best i could get to by searching was this piece of code:
public static boolean isAppRunning(final Context context, final String packageName) {
final ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
final List<ActivityManager.RunningAppProcessInfo> procInfos = activityManager.getRunningAppProcesses();
if (procInfos != null) {
for (final ActivityManager.RunningAppProcessInfo processInfo : procInfos) {
if (processInfo.processName.equals(packageName))
return true;
}
}
return false;
}
but for whatever reason it doesn't work correctly and sees the app as running even if i killed it by swiping, at first i thought it was because i have a sync adapter which could be considered as the app running so i used another process for it in the manifest using the
android:process=":service"
but it didn't work
Also thought about using variables in onStart and onStop of the activity but it's not guaranteed that onStop will be called when app is closed or killed so trying to avoid this method
this's my manifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="inc.mourad.ahmed.watchme">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<permission
android:name="inc.ahmed.mourad.ACCESS_WATCHME_DATABASE"
android:label="access my movies content provider"
android:protectionLevel="dangerous" />
<permission
android:name="inc.ahmed.mourad.ACCESS_WATCHME_SYNC_SERVICE"
android:label="access my sync service"
android:protectionLevel="dangerous" />
<permission
android:name="inc.ahmed.mourad.ACCESS_WATCHME_SYNC_AUTHENTICATOR_SERVICE"
android:label="access my sync authenticator dummy service"
android:protectionLevel="dangerous" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#style/Theme.AppCompat">
<activity android:name=".SplashScreenActivity"
android:theme="#style/Theme.AppCompat.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:theme="#style/Theme.AppCompat">
<intent-filter>
<data
android:host="www.ahmedmourad.com"
android:pathPrefix="/watchme/"
android:scheme="http" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
</intent-filter>
</activity>
<activity
android:name=".SettingsActivity"
android:label="#string/title_activity_settings"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="inc.mourad.ahmed.watchme.MainActivity" />
</activity>
<activity android:name=".MovieDetailsActivity" />
<provider
android:name=".data.MovieProvider"
android:authorities="#string/content_authority"
android:enabled="true"
android:exported="false"
android:permission="inc.ahmed.mourad.ACCESS_WATCHME_DATABASE"
android:syncable="true" />
<!-- SyncAdapter's dummy authentication service -->
<service android:name=".sync.WatchMeAuthenticatorService"
android:permission="inc.ahmed.mourad.ACCESS_WATCHME_SYNC_AUTHENTICATOR_SERVICE"
android:process=":service">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="#xml/authenticator" />
</service>
<!-- The SyncAdapter service -->
<service
android:name=".sync.WatchMeSyncService"
android:exported="true"
android:permission="inc.ahmed.mourad.ACCESS_WATCHME_SYNC_SERVICE"
android:process=":service">
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="#xml/syncadapter" />
</service>
</application>
so, any ideas? Thanks in advance
We tweaked your idea and came up with this (in Kotlin, see below for Java):
fun appInForeground(context: Context): Boolean {
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val runningAppProcesses = activityManager.runningAppProcesses ?: return false
return runningAppProcesses.any { it.processName == context.packageName && it.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND }
}
This is the equivalent in Java:
private boolean appInForeground(#NonNull Context context) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> runningAppProcesses = activityManager.getRunningAppProcesses();
if (runningAppProcesses == null) {
return false;
}
for (ActivityManager.RunningAppProcessInfo runningAppProcess : runningAppProcesses) {
if (runningAppProcess.processName.equals(context.getPackageName()) &&
runningAppProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
return true;
}
}
return false;
}
Here's the relevant documentation.
Original Answer : https://stackoverflow.com/a/48767617/10004454
The recommended way to do it in accordance with Android documentation is
class MyApplication : Application(), LifecycleObserver {
override fun onCreate() {
super.onCreate()
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
}
fun isInForeground(): Boolean {
return ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)
}
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onAppBackgrounded() {
//App in background
Log.e(TAG, "************* backgrounded")
Log.e(TAG, "************* ${isInForeground()}")
}
#OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onAppForegrounded() {
Log.e(TAG, "************* foregrounded")
Log.e(TAG, "************* ${isInForeground()}")
// App in foreground
}}
In your gradle (app) add : implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
Then to check the state at runtime call (applicationContext as MyApplication).isActivityVisible()
Is it possible in current state of NativeScript to create an app which listens for share intents on Android?
What I would like to achieve is for example having a website opened in my web browser on Android, tap on share and see my NativeScript app on the list of share targets.
I did accomplish this on a native Android app but can't get it to work in a NativeScript app. I've messed with the AndroidManifest.xml to add
<action android:name="android.intent.action.SEND"></action>
<category android:name="android.intent.category.DEFAULT"></category>
into intent-filter but this did not help. My app does not show up in the list of share targets.
I've been searching for a solution to this question myself and found all of the others answers here very helpful.
Yet, I'm a noob (only two days in) for NativeScript and couldn't actually get where and how to implement all the code bits together to work.
By using the answers here I could continue my search and found a finished GITHUB EXAMPLE: NickIliev/nativescript-receiving-shared-content
For other freshmen (or freshwoman) looking for a finished example go to the repo and explore the code in /demo/app/ directory. It was helpful for me and I'll hope it will help you too.
NativeScript should support this scenario out of the box. Here's what my AndroidManifest in app/App_resources/Android of a default bootstrapped application looks like:
<activity
android:name="com.tns.NativeScriptActivity"
android:label="#string/title_activity_kimera"
android:configChanges="keyboardHidden|orientation|screenSize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
edit:
Very simple implementation to send intent to any of my other application:
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.setType("text/plain");
sendIntent.putExtra("string", "the data Im sending you");
Intent chooser = Intent.createChooser(sendIntent, "Share with ");
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(chooser);
}
}
});
In addition to the intent-filter you have to add in your AppManifest.xml
make sure that you rebuild your app (livesync option may not reflect changes in AppManifest.xml)
Here is an NativeScript implementation for basic share
var app = require("application");
function onShare() {
var sharingIntent = new android.content.Intent(android.content.Intent.ACTION_SEND);
sharingIntent.setType("text/plain");
var shareBody = "Here is the share content body";
sharingIntent.addFlags(android.content.Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
sharingIntent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK | android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Subject Here");
sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareBody);
app.android.context.startActivity(sharingIntent);
}
exports.onShare = onShare;
First update your AndroidManifest.xml in app/App_Resources/AndroidManifest.xml
Add following intent-filter like below
<application android:name="com.tns.NativeScriptApplication" android:allowBackup="true" android:icon="#drawable/icon" android:label="#string/app_name"
android:theme="#style/AppTheme"> <activity android:name="com.tns.NativeScriptActivity"
android:label="#string/title_activity_kimera" android:configChanges="keyboardHidden|orientation|screenSize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.APP_BROWSER" />
<data android:mimeType="text/plain" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.APP_BROWSER" />
<data android:mimeType="image/*" />
</intent-filter>
</activity> <activity android:name="com.tns.ErrorReportActivity"/> </application>
Then Add following lines of code in your app.js
application.android.on(application.AndroidApplication.activityResumedEvent, function (args) {
console.log("Event: " + args.eventName + ", Activity: " + args.activity);
var a = args.activity;
try {
var Intent_1 = android.content.Intent;
var actionSend = Intent_1.ACTION_SEND;
var actionSendMultiple = Intent_1.ACTION_SEND_MULTIPLE;
var argIntent = a.getIntent();
var argIntentAction = argIntent.getAction();
var argIntentType = argIntent.getType();
console.log(" ~~~~ Intent is ~~~~ :" + new String(argIntent.getAction()).valueOf());
String.prototype.startsWith = function (str) {
return this.substring(0, str.length) === str;
};
if (new String(argIntentAction).valueOf() === new String(Intent_1.ACTION_SEND).valueOf()) {
if (new String(argIntentType).valueOf() === new String("text/plain").valueOf()) {
console.dump(cbParseTextAndUrl(argIntent));
}
else if (argIntentType.startsWith("image/")) {
console.log(cbParseImageUrl(argIntent));
}
}
else if (new String(argIntentAction).valueOf() === new String(Intent_1.ACTION_SEND_MULTIPLE).valueOf()) {
if (argIntentType.startsWith("image/")) {
var Uri = cbParseMultipleImageUrl(argIntent);
if (Uri !== null) {
var Uris = JSON.parse(Uri);
console.log(Uris);
}
}
}
function cbParseTextAndUrl(argIntent) {
var Patterns = android.util.Patterns;
//let Matcher = java.util.regex.Matcher;
var ListUrl = [];
var text = argIntent.getStringExtra(Intent_1.EXTRA_TEXT);
if (new String().valueOf() !== "null") {
var Matcher = Patterns.WEB_URL.matcher(text);
while (Matcher.find()) {
var url = Matcher.group();
ListUrl.push(url);
}
return { "text": text, "listUrl": ListUrl };
}
}
function cbParseImageUrl(argIntent) {
var imageUri = argIntent.getParcelableExtra(Intent_1.EXTRA_STREAM);
if (imageUri != null) {
// Update UI to reflect image being shared
return imageUri;
}
}
function cbParseMultipleImageUrl(argIntent) {
var imageUris = argIntent.getParcelableArrayListExtra(Intent_1.EXTRA_STREAM);
if (imageUris != null) {
// Update UI to reflect image being shared
return JSON.stringify(imageUris.toString());
}
}
}
catch (e) {
console.log(e);
}
});
Now you can share your content from 3rd party app to your app.
As none of this answers are correct in 2023, with nativescript 7+ the way it works is
add the intent filter like mentioned already:
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
listen to the activityNewIntentEvent. This is the only way to retrieve the incoming intent and extract the extra:
if (isAndroid) {
Application.android.on(AndroidApplication.activityNewIntentEvent, function (args: AndroidActivityNewIntentEventData) {
console.log('Event : ' + args.intent);
});
}
In case if anyone is looking for most recent NS7 and NS8 compatible version, this works for me and for Android. This is tested on drawer template app from NS8. Javascript part is for home-page.js , just replace it's content with code below.
Imports are different for NS7 and NS8, which gets people confused.
Edit your AndroidManifest.xml
App_Resources/Android/src/main/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="__PACKAGE__"
android:versionCode="10000"
android:versionName="1.0">
<supports-screens
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:xlargeScreens="true"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application
android:name="com.tns.NativeScriptApplication"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme">
<activity
android:name="com.tns.NativeScriptActivity"
android:label="#string/title_activity_kimera"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout|locale|uiMode"
android:theme="#style/LaunchScreenTheme">
<meta-data android:name="SET_THEME_ON_LAUNCH" android:resource="#style/AppTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
<activity android:name="com.tns.ErrorReportActivity"/>
</application>
</manifest>
Javascript
home/home-page.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const Observable = require("#nativescript/core").Observable;
const application = require("#nativescript/core/application");
import { Application } from '#nativescript/core'
import { HomeViewModel } from './home-view-model'
var vm = new Observable();
export function onNavigatingTo(args) {
const page = args.object
page.bindingContext = new HomeViewModel();
page.bindingContext = vm;
vm.set("sharedText", "Waiting for intent...");
if (application.android) {
application.android.on(application.AndroidApplication.activityCreatedEvent, function (args) {
var activity = args.activity;
console.log(activity);
vm.set("sharedText", "Intend data received");
// Get intent, action and MIME type
var intent = activity.getIntent();
var action = intent.getAction();
var type = intent.getType();
if (android.content.Intent.ACTION_SEND === action && type != null) {
if (type.startsWith("text/")) {
handleSendText(intent); // Handle text being sent
}
else if (type.startsWith("image/")) {
handleSendImage(intent); // Handle single image being sent
}
}
else if (android.content.Intent.ACTION_SEND_MULTIPLE === action && type != null) {
if (type.startsWith("image/")) {
handleSendMultipleImages(intent); // Handle multiple images being sent
}
}
else {
// Handle other intents, such as being started from the home screen
}
});
}
}
function handleSendText(intent) {
if (application.android) {
var sharedText = intent.getStringExtra(android.content.Intent.EXTRA_TEXT);
if (sharedText != null) {
// Update UI to reflect text being shared
console.log("sharedText: ", sharedText);
console.log("Text received!");
// set timeout - enough to update UI after app loading
setTimeout(func, 1000);
function func() {
vm.set("sharedText", sharedText);
}
}
}
}
function handleSendImage(intent) {
if (application.android) {
var imageUri = intent.getParcelableExtra(android.content.Intent.EXTRA_STREAM);
if (imageUri != null) {
// Update UI to reflect image being shared
console.log("Image received!");
var appContext = application.android.context;
var bitmap = android.provider.MediaStore.Images.Media.getBitmap(appContext.getContentResolver(), imageUri);
console.log("bitmap: ", bitmap);
vm.set("bitmap", bitmap);
}
}
}
function handleSendMultipleImages(intent) {
if (application.android) {
var imageUris = intent.getParcelableArrayListExtra(android.content.Intent.EXTRA_STREAM);
if (imageUris != null) {
// Update UI to reflect multiple images being shared
console.log("imageUris: ", imageUris);
console.log("Multiple images received!");
}
}
}
export function onDrawerButtonTap(args) {
const sideDrawer = Application.getRootView()
sideDrawer.showDrawer()
}