Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(share_plus): return correct share result on android #1301

Merged
merged 3 commits into from Oct 31, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1,6 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="dev.fluttercommunity.plus.share">
<application>
<!-- Declares a provider which allows us to store files to share in
'.../caches/share_plus' and grant the receiving action access -->
<provider
android:name="dev.fluttercommunity.plus.share.ShareFileProvider"
android:authorities="${applicationId}.flutter.share_provider"
Expand All @@ -10,5 +12,12 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/flutter_share_file_paths"/>
</provider>
<!-- This manifest declared broadcast receiver allows us to use an explicit
Intent when creating a PendingItent to be informed of the user's choice -->
<receiver android:name=".SharePlusPendingIntent" android:exported="true">
<intent-filter>
<action android:name="EXTRA_CHOSEN_COMPONENT" />
</intent-filter>
</receiver>
</application>
</manifest>
Expand Up @@ -33,7 +33,7 @@ internal class Share(
*/
private val immutabilityIntentFlags: Int by lazy {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE
PendingIntent.FLAG_MUTABLE
} else {
0
}
Expand Down Expand Up @@ -71,9 +71,9 @@ internal class Share(
PendingIntent.getBroadcast(
context,
0,
Intent(ShareSuccessManager.BROADCAST_CHANNEL),
Intent(context, SharePlusPendingIntent::class.java),
PendingIntent.FLAG_UPDATE_CURRENT or immutabilityIntentFlags
).intentSender
).getIntentSender()
Coronon marked this conversation as resolved.
Show resolved Hide resolved
)
} else {
Intent.createChooser(shareIntent, null /* dialog title optional */)
Expand Down Expand Up @@ -129,9 +129,9 @@ internal class Share(
PendingIntent.getBroadcast(
context,
0,
Intent(ShareSuccessManager.BROADCAST_CHANNEL),
Intent(context, SharePlusPendingIntent::class.java),
PendingIntent.FLAG_UPDATE_CURRENT or immutabilityIntentFlags
).intentSender
).getIntentSender()
)
} else {
Intent.createChooser(shareIntent, null /* dialog title optional */)
Expand Down
@@ -0,0 +1,43 @@
package dev.fluttercommunity.plus.share

import android.content.*
import android.os.Build

/**
* This helper class allows us to use FLAG_MUTABLE on the PendingIntent used in the Share class,
* as it allows us to make the underlying Intent explicit, therefore avoiding any risks an implicit
* mutable Intent may carry.
*
* When the PendingIntent is sent, the system will instantiate this class and call `onReceive` on it.
*/
internal class SharePlusPendingIntent: BroadcastReceiver() {
/**
* Companion object to achieve static behaviour.
Coronon marked this conversation as resolved.
Show resolved Hide resolved
*/
companion object {
@JvmField
Coronon marked this conversation as resolved.
Show resolved Hide resolved
/**
* Static result to access the result of the system instantiated instance
*/
var result: String = ""
}

/**
* Handler called after an action was chosen. Called only on success.
*/
override fun onReceive(context: Context, intent: Intent) {
// Extract chosen ComponentName
val chosenComponent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// Only available from API level 33 onwards
intent.getParcelableExtra(Intent.EXTRA_CHOSEN_COMPONENT, ComponentName::class.java)
} else {
// Deprecated in API level 33
intent.getParcelableExtra<ComponentName>(Intent.EXTRA_CHOSEN_COMPONENT)
}

// Unambiguously identify the chosen action
if (chosenComponent != null) {
result = chosenComponent.flattenToString()
}
}
}
Expand Up @@ -15,14 +15,12 @@ class SharePlusPlugin : FlutterPlugin, ActivityAware {
override fun onAttachedToEngine(binding: FlutterPluginBinding) {
methodChannel = MethodChannel(binding.binaryMessenger, CHANNEL)
manager = ShareSuccessManager(binding.applicationContext)
manager.register()
share = Share(context = binding.applicationContext, activity = null, manager = manager)
val handler = MethodCallHandler(share, manager)
methodChannel.setMethodCallHandler(handler)
}

override fun onDetachedFromEngine(binding: FlutterPluginBinding) {
manager.discard()
methodChannel.setMethodCallHandler(null)
}

Expand Down
Expand Up @@ -9,31 +9,18 @@ import java.util.concurrent.atomic.AtomicBoolean
* Handles the callback based status information about a successful or dismissed
* share. Used to link multiple different callbacks together for easier use.
*/
internal class ShareSuccessManager(private val context: Context) : BroadcastReceiver(),
ActivityResultListener {
internal class ShareSuccessManager(private val context: Context) : ActivityResultListener {
private var callback: MethodChannel.Result? = null
private var isCalledBack: AtomicBoolean = AtomicBoolean(true)

/**
* Register listener. Must be called before any share sheet is opened.
*/
fun register() {
context.registerReceiver(this, IntentFilter(BROADCAST_CHANNEL))
}

/**
* Deregister listener. Must be called before the base activity is invalidated.
*/
fun discard() {
context.unregisterReceiver(this)
}

/**
* Set result callback that will wait for the share-sheet to close and get either
* the componentname of the chosen option or an empty string on dismissal.
*/
fun setCallback(callback: MethodChannel.Result): Boolean {
return if (isCalledBack.compareAndSet(true, false)) {
// Prepare all state for new share
SharePlusPendingIntent.result = ""
isCalledBack.set(false)
this.callback = callback
true
Expand All @@ -51,7 +38,7 @@ internal class ShareSuccessManager(private val context: Context) : BroadcastRece
* Must be called if `.startActivityForResult` is not available to avoid deadlocking.
*/
fun unavailable() {
returnResult("dev.fluttercommunity.plus/share/unavailable")
returnResult(RESULT_UNAVAILABLE)
}

/**
Expand All @@ -70,24 +57,19 @@ internal class ShareSuccessManager(private val context: Context) : BroadcastRece
*/
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
return if (requestCode == ACTIVITY_CODE) {
returnResult("")
returnResult(SharePlusPendingIntent.result)
true
} else {
false
}
}

/**
* Handler called after a sharesheet was closed. Called only on success.
* Companion object holds constants used throughout the plugin when attempting to return
* the share result.
*/
override fun onReceive(context: Context, intent: Intent) {
returnResult(
intent.getParcelableExtra<ComponentName>(Intent.EXTRA_CHOSEN_COMPONENT).toString()
)
}

companion object {
const val BROADCAST_CHANNEL = "dev.fluttercommunity.plus/share/success"
const val ACTIVITY_CODE = 17062003
const val RESULT_UNAVAILABLE = "dev.fluttercommunity.plus/share/unavailable"
}
}
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path name="cache" path="share_plus/" />
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Used in conjunction with the provider declared in AndroidManifest.xml -->
<cache-path name="cache" path="share_plus/" />
</paths>