forked from zulip/zulip-mobile
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
android: Kotlin-side code for receiving data from other apps.
This is part of the implementation of zulip#117, to show up in the "share" UI from other apps so the user can send a message with the shared data. This commit consists of the needed Android-native code, which uses the relevant Android APIs to get the data and then the relevant RN-on-Android APIs to send the data over to our main JS codebase. On its own, this code doesn't yet do anything useful because we don't have the JS code to listen for the data. That's still in development as zulip#4124, and coming soon. Because the feature doesn't yet work in this version, the manifest elements to advertise it are commented out, so that it doesn't actually get presented to users. Also include the new dummy root React component, `SharingRoot`, because the Kotlin-side code refers to it by name. It exists in the first place because 'Sharing' is linked with launching an activity in the Android ecosystem. But we don't always want to launch `MainActivity` when receiving a share because it may cause two instances of it to be opened simultaneously. We can't check whether the app is running before an activity launches. So, we launch this dummy component, then process the intent, identify whether the app is running or not, and handle that as mentioned in the paragraph above, and then quickly kill this dummy Activity. All of this happens fast enough that the component does not even get time to render, so it's seamless. [greg: split out Android-native portion as its own commit; adjusted commit message accordingly]
- Loading branch information
Showing
8 changed files
with
212 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
110 changes: 110 additions & 0 deletions
110
android/app/src/main/java/com/zulipmobile/sharing/ReceiveShareActivity.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package com.zulipmobile.sharing | ||
|
||
import android.content.Context | ||
import android.content.Intent | ||
import android.content.res.Configuration | ||
import android.net.Uri | ||
import android.os.Bundle | ||
import android.util.Log | ||
import android.webkit.WebView | ||
import androidx.annotation.Nullable | ||
import com.facebook.react.ReactActivity | ||
import com.facebook.react.ReactApplication | ||
import com.facebook.react.bridge.ReactContext | ||
import com.facebook.react.bridge.WritableMap | ||
import com.facebook.react.bridge.Arguments | ||
import com.zulipmobile.notifications.* | ||
|
||
const val TAG = "ZulipReceiveShare" | ||
|
||
class ReceiveShareActivity : ReactActivity() { | ||
|
||
/** | ||
* Returns the name of the main component registered from JavaScript. | ||
* This is used to schedule rendering of the component. | ||
*/ | ||
override fun getMainComponentName(): String? { | ||
return "SharingRoot" | ||
} | ||
|
||
private fun sendEvent(reactContext: ReactContext, | ||
eventName: String, | ||
@Nullable params: WritableMap) { | ||
Log.d(TAG, "Sending event with shared data") | ||
emit(reactContext, eventName, params) | ||
} | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
WebView.setWebContentsDebuggingEnabled(true) | ||
if (intent?.action == Intent.ACTION_SEND) { | ||
handleSend(intent) | ||
} | ||
finish() | ||
} | ||
|
||
private fun handleSend(intent: Intent) { | ||
val application = application as ReactApplication | ||
val host = application.reactNativeHost | ||
val reactContext = host.tryGetReactInstanceManager()?.currentReactContext | ||
val params: WritableMap | ||
try { | ||
params = getParamsFromIntent(intent) | ||
} catch (e: ShareParamsParseException) { | ||
Log.w(TAG, "Ignoring malformed share Intent: ${e.message}") | ||
return | ||
} | ||
|
||
val appStatus = reactContext?.appStatus | ||
when (appStatus) { | ||
null, ReactAppStatus.NOT_RUNNING -> | ||
// Either there's no JS environment running, or we haven't yet | ||
// reached foreground. Expect the app to check | ||
// initialSharedData on launch. | ||
SharingModule.initialSharedData = params | ||
ReactAppStatus.BACKGROUND, ReactAppStatus.FOREGROUND -> | ||
// JS is running and has already reached foreground. It won't | ||
// check initialSharedData again, but it will see a | ||
// shareReceived event. | ||
sendEvent(reactContext, "shareReceived", params) | ||
} | ||
when (appStatus) { | ||
null, ReactAppStatus.NOT_RUNNING, ReactAppStatus.BACKGROUND -> | ||
launchMainActivity(application as Context) | ||
ReactAppStatus.FOREGROUND -> Unit | ||
} | ||
} | ||
|
||
private fun getParamsFromIntent(intent: Intent): WritableMap { | ||
val params = Arguments.createMap() | ||
when { | ||
"text/plain" == intent.type -> { | ||
val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT) | ||
params.putString("type", "text") | ||
params.putString("sharedText", sharedText) | ||
} | ||
intent.type?.startsWith("image/") == true -> { | ||
val url = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM) | ||
?: throw ShareParamsParseException("Could not extract URL from Image Intent") | ||
params.putString("type", "image") | ||
params.putString("sharedImageUrl", url.toString()) | ||
} | ||
else -> { | ||
val url = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM) | ||
?: throw ShareParamsParseException("Could not extract URL from File Intent") | ||
params.putString("type", "file") | ||
params.putString("sharedFileUrl", url.toString()) | ||
} | ||
} | ||
return params | ||
} | ||
|
||
override fun onConfigurationChanged(newConfig: Configuration) { | ||
super.onConfigurationChanged(newConfig) | ||
val intent = Intent("onConfigurationChanged") | ||
intent.putExtra("newConfig", newConfig) | ||
this.sendBroadcast(intent) | ||
} | ||
} | ||
|
||
class ShareParamsParseException(errorMessage: String) : RuntimeException(errorMessage) |
24 changes: 24 additions & 0 deletions
24
android/app/src/main/java/com/zulipmobile/sharing/SharingModule.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package com.zulipmobile.sharing | ||
|
||
import com.facebook.react.bridge.* | ||
|
||
internal class SharingModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { | ||
|
||
override fun getName(): String { | ||
return "Sharing" | ||
} | ||
|
||
@ReactMethod | ||
fun getInitialSharedContent(promise: Promise) { | ||
if (null == initialSharedData) { | ||
promise.resolve(null) | ||
} else { | ||
promise.resolve(initialSharedData) | ||
} | ||
|
||
} | ||
|
||
companion object { | ||
var initialSharedData: WritableMap? = null | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
android/app/src/main/java/com/zulipmobile/sharing/SharingPackage.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package com.zulipmobile.sharing | ||
|
||
|
||
import com.facebook.react.ReactPackage | ||
import com.facebook.react.bridge.NativeModule | ||
import com.facebook.react.bridge.ReactApplicationContext | ||
import com.facebook.react.uimanager.ViewManager | ||
|
||
import java.util.ArrayList | ||
|
||
class SharingPackage : ReactPackage { | ||
|
||
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> { | ||
return emptyList() | ||
} | ||
|
||
override fun createNativeModules( | ||
reactContext: ReactApplicationContext): List<NativeModule> { | ||
val modules = ArrayList<NativeModule>() | ||
|
||
modules.add(SharingModule(reactContext)) | ||
|
||
return modules | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
/* @flow strict-local */ | ||
import { AppRegistry } from 'react-native'; | ||
import ZulipMobile from './src/ZulipMobile'; | ||
import SharingRoot from './src/sharing/SharingRoot'; | ||
|
||
AppRegistry.registerComponent('ZulipMobile', () => ZulipMobile); | ||
AppRegistry.registerComponent('SharingRoot', () => SharingRoot); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/* @flow strict-local */ | ||
import React from 'react'; | ||
import { View } from 'react-native'; | ||
|
||
/** | ||
* This is a dummy component to by-pass some weird quirks of Android Activity | ||
* launches in a React Native context. The native code in | ||
* `ReceiveShareActivity.kt` finishes this activity quickly, after either | ||
* i) Sending events to an already open app in the background | ||
* ii) Launching `MainActivity` with some initial share data. | ||
*/ | ||
class SharingRoot extends React.Component<{||}> { | ||
render() { | ||
return <View />; | ||
} | ||
} | ||
|
||
export default SharingRoot; |