Can I share to my NativeScript app? - android

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

Related

Google cast sender app not opening TV receiver app

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

How to get intent content in receiving app when launched with getLaunchIntentForPackage()?

I have an intent filter for receiving intents with a specific URI. When another app opens my app with that specific URI, I use that deeplink/unilink, and redirect in the app using a Flutter plugin, depending on which query parameters are included in the URI.
In AndroidManifest:
<activity android:name=".MainActivity" android:theme="#style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize" android:screenOrientation="portrait">
<meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="#drawable/launch_background" />
<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>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="appspecifictheme" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="www.myappslandingpage.com"
android:pathPrefix="/myappspath" />
<data
android:scheme="https"
android:host="www.myappslandingpage.com"
android:pathPrefix="/myspecificpathprefix" />
</intent-filter>
</activity>
This is working fine when launching from another app with uri https://www.myappslandingpage.com/myspecificpathprefix/?param1=xxxx&param2=xxxx&param3=xxxx
Now to my issue: another app wants to launch my app using this intent from package manager, and passing the query parameters as extras instead:
Intent launchIntent = getPackageManager().getLaunchIntentForPackage("receiving.apps.package.name");
launchIntent.putExtra("param1", "xxxx");
launchIntent.putExtra("param2", "xxxx");
launchIntent.putExtra("param3", "xxxx");
startActivity(launchIntent);
Question 1: When I launch my app from another app using above code, and the app is opened, but how do I get the content from the intent? I have tried using getIntent() in my MainActivity's onCreate(), but it doesn't seem to be working (stringExtras and other data in the intent is null).
#Override
protected void onCreate(Bundle savedInstanceState) {
checkIntent(getIntent());
FlutterMain.startInitialization(this);
super.onCreate(savedInstanceState);
}
void checkIntent(Intent i) {
final String param1 = i.getStringExtra("param1");
final String param2 = i.getStringExtra("param2");
final String param3 = i.getStringExtra("param3");
final Bundle extras = i.getExtras();
if(extras != null) {
for (String key : extras.keySet()) {
L.d(TAG, "checkIntent() extra: " + key + " : " + (extras.get(key) !=
null ? extras.get(key) : "NULL"));
//Above logs nothing - there are no keys in extras.
}
}
if(param1 != null) {
//Do something
} else {
L.d(TAG,"checkIntent(): param1 is null");
}
}
I have also tried adding URI to the intent using launchIntent.setData("uri") before running startActivity() from the other app, and changing the intent filter in my receiving app as below, but getIntent().getData() in MainActivity also returns null.
<category android:name="android.intent.category.LAUNCHER" />
Question 2: If I find a way to receive the intent and get the stringExtras, is there a simple way to "transform" that intent so that it is interpreted the same way as if the app was opened with a deeplink?

How to make my app appear in share board of other apps - example whatsApp

I am trying to read the text file of chat that exported from WhatsApp to my android app.
When I am trying to export the chat text file to my app, it does not appear in the share board of WhatsApp, although I have added the following to the manifest.xml:
<activity android:name=".MainActivity">
<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>
Also, here is the Java Code:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();
if (Intent.ACTION_SEND.equals(action) && type != null) {
if ("text/plain".equals(type)) {
handleSendText(intent); // Handle text being sent
} else {
// Handle other intents, such as being started from the home screen
}
}
}
void handleSendText(Intent intent) {
String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
if (sharedText != null) {
Log.d(TAG, sharedText);
// Update UI to reflect text being shared
}
}}
I went through many explanations, but I could not figure out what is the problem. Thank you.
Have you added the intent-filter under the correct activity in AndroidManifest.xml?
This should work in theory, but some apps might not send the proper intent for your app. It is possible that WhatsApp is using "VIEW" instead of "SEND", so try with this:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:mimeType="text/plain" />
</intent-filter>
Let me know if it works for you.

Where to call the Android Intent handler code in Gluon

Trying to handle the intents that are specified in AndroidManifest.xml. Not sure where to call the Service method that is created. Unlike in native android code where the intents are handled in the activity.
Referring to http://gluonhq.com/handling-android-native-activities-like-a-pro/
tried calling the service in the init method of the main class of the application (But seems like the intent handler methods are not called)
For the Gluon app to receive data from other apps in android
AndroidManifest.xml
<?xml version="1.0" encoding="UTF-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.nativeeg" android:versionCode="1" android:versionName="1.0">
<supports-screens android:xlargeScreens="true"/>
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="21"/>
<application android:label="NativeEg" android:name="android.support.multidex.MultiDexApplication" android:icon="#mipmap/ic_launcher">
<activity android:name="javafxports.android.FXActivity" android:label="NativeEg" android:configChanges="orientation|screenSize">
<meta-data android:name="main.class" android:value="com.nativeeg.NativeEg"/>
<meta-data android:name="debug.port" android:value="0"/>
<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.gluonhq.impl.charm.down.plugins.android.PermissionRequestActivity" />
</application>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</manifest>
The Native Service to handle the intent
import nativeutility.ReceiveFromApps;
import javafxports.android.FXActivity;
import android.content.Intent;
/**
*
* #author Vaishnavi
*/
public class AndroidReceiveFromApps implements ReceiveFromApps{
#Override
public void receiveData() {
Intent intent = FXActivity.getInstance().getIntent();
String action = intent.getAction();
String type = intent.getType();
if (Intent.ACTION_SEND.equals(action) && type != null) {
System.out.println("in handle intent!");
if ("text/plain".equals(type)) {
// Handle text being sent
System.out.println("text data");
} else if (type.startsWith("image/")) {
// Handle single image being sent
System.out.println("image data");
}
} else if (Intent.ACTION_SEND_MULTIPLE.equals(action) && type != null) {
System.out.println("in handle intent!");
if (type.startsWith("image/")) {
// Handle multiple images being sent
System.out.println("multiple image data");
}
} else {
// Handle other intents, such as being started from the home screen
}
}
}
Gluon Main class:
public class NativeEg extends MobileApplication {
#Override
public void init() {
AppViewManager.registerViewsAndDrawer(this);
System.out.println("in main");
Services.get(ReceiveFromApps.class).ifPresent(service -> service.receiveData());
}
...
package nativeutility;
import com.gluonhq.charm.down.DefaultServiceFactory;
public class ReceiveFromAppsFactory extends DefaultServiceFactory<ReceiveFromApps> {
public ReceiveFromAppsFactory() {
super(ReceiveFromApps.class);
}}
Interface
package nativeutility;
public interface ReceiveFromApps {
public void receiveData();
}

How to grant temporary access to custom content provider using FLAG_GRANT_READ_URI_PERMISSION

I am trying to query custom content provider(App A) from another app(App B).
I can do that when there is no permission protection for content provider. Specifically, I build custom content provider on App A and sent an intent containing the URI to App B.
Here is intent-sending part in App A.
class InsertOnClickListener implements OnClickListener{
public void onClick(View v) {
ContentValues values = new ContentValues();
values.put(DataBaseConfiguation.TableConfiguation.USER_NAME, "Jack");
Uri uri = getContentResolver().insert(DataBaseConfiguation.TableConfiguation.CONTENT_URI, values);
System.out.println("uri------------------->" + uri);
// the uri above should be like "content://com.catking.contentprovider.MyContentProvider/user"
Uri uri2 = Uri.parse("content://com.catking.contentprovider.MyContentProvider/user");
Cursor c = managedQuery(uri2, null, null, null, null);
String sendvalue = null;
if (c.moveToFirst()) {
do{
System.out.println("User name:"+c.getString(c.getColumnIndex(DataBaseConfiguation.TableConfiguation.USER_NAME)).toString());
sendvalue = c.getString(c.getColumnIndex(DataBaseConfiguation.TableConfiguation.USER_NAME)).toString();
} while (c.moveToNext());
}
Intent sendIntent = new Intent();
sendIntent.setClassName("com.android.web", "com.android.web.Provid");
sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
sendIntent.putExtra("name", uri2.toString());
sendIntent.setType("text/plain");
startActivity(sendIntent);
}
}
followed by manifest file of App A.
<application
android:icon="#drawable/ic_launcher"
android:label="#string/app_name" >
<activity
android:name=".ContentProviderTestActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider android:authorities="com.catking.contentprovider.MyContentProvider"
android:exported="true"
android:grantUriPermissions="true"
android:name="com.catking.contentprovider.MyContentProvider"
android:readPermission="android.permission.permRead"
android:writePermission="android.permission.permWrite" >
</provider>
</application>
Then App B(class Provid) get the URI and query the corresponding data in content provider(using following code).
public class Provid extends Activity {
public void onCreate(Bundle savedInstanceState) {
Bundle extras = getIntent().getExtras();
String userNameuri;
if (extras != null) {
userNameuri = extras.getString("name");
Uri allTitles = Uri.parse(userNameuri);
Cursor c = managedQuery(allTitles, null, null, null, null);
if (c.moveToFirst()) {
do{
System.out.println("Name is"+c.getString(c.getColumnIndex(DataBaseConfiguation.TableConfiguation.USER_NAME)).toString());
} while (c.moveToNext());
}
}
}
}
Here's App B's manifest file.
<uses-permission android:name="android.permission.INTERNET" />
<application
android:icon="#drawable/ic_launcher"
android:label="#string/app_name" >
<activity
android:name="._GetWebResoureActivity"
android:label="#string/app_name" >
<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" >
</action>
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
<receiver android:name="StaticReceiver11" >
<intent-filter>
<action android:name="android.intent.action.MYSEND" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<activity
android:name="Provid"
android:label="#string/title_activity_provid" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
</application>
However, when I query content provider from App B, errors occur:
java.lang.RuntimeException: Unable to start activity ComponentInfo {com.android.web/com.android.web.Provid}: java.lang.SecurityException: Permission Denial: opening provider com.ck.contentprovider.MyContentProvider from ProcessRecord{426c6ea8 17032:com.android.web/u0a95} (pid=17032, uid=10095) requires android.permission.permRead or android.permission.permWrite
It seems that App B did not make use of the temporary permission to access. In other word, how to utilize FLAG_GRANT_READ_URI_PERMISSION from App B?
I have also tried directly adding Uri to intent(using setData()), instead of Uri.toString()(using putExtra()).
sendIntent.setData(uri2);
and
Uri userNameuri = getIntent().getData();
But the "userNameuri" got in App B is null.
I am totally confused...
updated
I tried "grantUriPermission("com.android.getCPaccess", uri2, Intent.FLAG_GRANT_READ_URI_PERMISSION)" according to a previous post
What is the correct permission handling when sending sensitive app data as email attachment?
And it works indeed. It can work without using FLAG_GRANT_READ_URI_PERMISSION. But the permission is not "temporary". It have to be ended manually by revokeUriPermission().
So, I am wondering if there is a way to grant temporary permission as introduced in FLAG_GRANT_READ_URI_PERMISSION, or it's a bug at all?
It seems that FLAG_GRANT_READ_URI_PERMISSION affects only Uri Intent.mData but not uris in extras.
I found similar problems when playing with ACTION_SEND, which takes an uri in EXTRA_STREAM. Providing the same uri in setData() will work, but doesn't conform to the rules and leads to unexpected behavior (e.g. Gmail recipient).
As of Jelly Bean intents can contain ClipData, which should solve the problem. For ACTION_SEND it's generated automatically from extras.
grantUriPermission will work, but requires revokeUriPermission. To do the same job as startActivity(Intent.createChooser(intent, title)) you will have to pick the target (ACTION_PICK_ACTIVITY), grant permissions to its package and revoke them when not needed anymore (onActivityResult?).
Here's some code:
Receiver app:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.handler" >
<application android:allowBackup="true" android:icon="#drawable/ic_launcher" android:label="#string/app_name" >
…
<activity
android:name=".ActivityView"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="*/*"/>
</intent-filter>
</activity>
</application>
</manifest>
ActivityView.java:
protected void onCreate(Bundle savedInstanceState) {
Intent intent = getIntent();
if ((intent != null)) {
procUri(intent.getData());
procUri(intent.<Uri>getParcelableExtra(Intent.EXTRA_STREAM));
}
}
private void procUri(Uri uri) {
if (uri != null) {
InputStream i;
try {
i = getContentResolver().openInputStream(uri);
byte[] b = new byte[i.available()];
i.read(b);
…
} catch (Throwable e) {
…
}
}
}
Sender app:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.sender" >
<application android:allowBackup="true" android:icon="#drawable/ic_launcher" android:label="#string/app_name" >
…
<activity
android:name=".ActivitySend"
android:label="#string/app_name" >
</activity>
<provider
android:name=".MyContentProvider"
android:authorities="com.example.sender.content"
android:enabled="true"
android:exported="false"
android:grantUriPermissions="true" >
</provider>
</application>
</manifest>
ActivitySend.java:
// OK!
private void doTestView(Uri uri, String type) {
startActivity(
new Intent(Intent.ACTION_VIEW)
.setDataAndType(uri, type)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
);
}
// error prior to JellyBean
// ok on JellyBean, even without explicit FLAG_GRANT_READ_URI_PERMISSION (due to generated ClipData)
private void doTestSend(Uri uri, String type, CharSequence title) {
startActivity(
Intent.createChooser(
new Intent(Intent.ACTION_SEND)
.setType(type)
.putExtra(Intent.EXTRA_STREAM, uri)
.putExtra(Intent.EXTRA_SUBJECT, title)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
,
title
)
);
}
// working but not ok, unexpected results!
private void doTestSend2(Uri uri, String type, CharSequence title) {
startActivity(
Intent.createChooser(
new Intent(Intent.ACTION_SEND)
.setDataAndType(uri, type)
.putExtra(Intent.EXTRA_STREAM, uri)
.putExtra(Intent.EXTRA_SUBJECT, title)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
,
title
)
);
}

Categories

Resources