platform: android file opening, better exit

This commit is contained in:
ardocrat 2024-09-13 14:22:15 +03:00
parent d78ec570b0
commit c73cd58eed
11 changed files with 193 additions and 68 deletions

View file

@ -1,15 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android"
> >
<uses-feature android:name="android.hardware.camera" android:required="false"/> <uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR" /> <uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" tools:ignore="ScopedStorage"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage"/>
<application <application
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
@ -18,7 +20,6 @@
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="Grim" android:label="Grim"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Main"> android:theme="@style/Theme.Main">
<receiver android:name=".NotificationActionsReceiver"/> <receiver android:name=".NotificationActionsReceiver"/>
@ -44,6 +45,22 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter android:scheme="http" tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/*" />
</intent-filter>
<intent-filter android:scheme="http" tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/*" />
</intent-filter>
<meta-data android:name="android.app.lib_name" android:value="grim" /> <meta-data android:name="android.app.lib_name" android:value="grim" />
</activity> </activity>
<service android:name=".BackgroundService" android:stopWithTask="true" /> <service android:name=".BackgroundService" android:stopWithTask="true" />

View file

@ -152,6 +152,7 @@ public class BackgroundService extends Service {
// Show notification with sync status. // Show notification with sync status.
Intent i = getPackageManager().getLaunchIntentForPackage(this.getPackageName()); Intent i = getPackageManager().getLaunchIntentForPackage(this.getPackageName());
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_IMMUTABLE); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_IMMUTABLE);
try {
mNotificationBuilder = new NotificationCompat.Builder(this, TAG) mNotificationBuilder = new NotificationCompat.Builder(this, TAG)
.setContentTitle(this.getSyncTitle()) .setContentTitle(this.getSyncTitle())
.setContentText(this.getSyncStatusText()) .setContentText(this.getSyncStatusText())
@ -159,6 +160,9 @@ public class BackgroundService extends Service {
.setSmallIcon(R.drawable.ic_stat_name) .setSmallIcon(R.drawable.ic_stat_name)
.setPriority(NotificationCompat.PRIORITY_MAX) .setPriority(NotificationCompat.PRIORITY_MAX)
.setContentIntent(pendingIntent); .setContentIntent(pendingIntent);
} catch (UnsatisfiedLinkError e) {
return;
}
Notification notification = mNotificationBuilder.build(); Notification notification = mNotificationBuilder.build();
// Start service at foreground state to prevent killing by system. // Start service at foreground state to prevent killing by system.

View file

@ -7,9 +7,9 @@ import android.content.*;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.*;
import android.os.Bundle;
import android.os.Process; import android.os.Process;
import android.provider.Settings;
import android.system.ErrnoException; import android.system.ErrnoException;
import android.system.Os; import android.system.Os;
import android.util.Size; import android.util.Size;
@ -51,8 +51,7 @@ public class MainActivity extends GameActivity {
@Override @Override
public void onReceive(Context ctx, Intent i) { public void onReceive(Context ctx, Intent i) {
if (i.getAction().equals(STOP_APP_ACTION)) { if (i.getAction().equals(STOP_APP_ACTION)) {
onExit(); exit();
Process.killProcess(Process.myPid());
} }
} }
}; };
@ -67,11 +66,19 @@ public class MainActivity extends GameActivity {
private ExecutorService mCameraExecutor = null; private ExecutorService mCameraExecutor = null;
private boolean mUseBackCamera = true; private boolean mUseBackCamera = true;
private ActivityResultLauncher<Intent> mFilePickResultLauncher = null; private ActivityResultLauncher<Intent> mFilePickResult = null;
private ActivityResultLauncher<Intent> mOpenFilePermissionsResult = null;
@SuppressLint("UnspecifiedRegisterReceiverFlag") @SuppressLint("UnspecifiedRegisterReceiverFlag")
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
// Check if activity was launched to exclude from recent apps on exit.
if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0) {
super.onCreate(null);
finish();
return;
}
// Clear cache on start. // Clear cache on start.
if (savedInstanceState == null) { if (savedInstanceState == null) {
Utils.deleteDirectoryContent(new File(getExternalCacheDir().getPath()), false); Utils.deleteDirectoryContent(new File(getExternalCacheDir().getPath()), false);
@ -91,8 +98,21 @@ public class MainActivity extends GameActivity {
// Register receiver to finish activity from the BackgroundService. // Register receiver to finish activity from the BackgroundService.
registerReceiver(mBroadcastReceiver, new IntentFilter(STOP_APP_ACTION)); registerReceiver(mBroadcastReceiver, new IntentFilter(STOP_APP_ACTION));
// Register file pick result launcher. // Register associated file opening result.
mFilePickResultLauncher = registerForActivityResult( mOpenFilePermissionsResult = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (Build.VERSION.SDK_INT >= 30) {
if (Environment.isExternalStorageManager()) {
onFile();
}
} else if (result.getResultCode() == RESULT_OK) {
onFile();
}
}
);
// Register file pick result.
mFilePickResult = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(), new ActivityResultContracts.StartActivityForResult(),
result -> { result -> {
int resultCode = result.getResultCode(); int resultCode = result.getResultCode();
@ -124,7 +144,7 @@ public class MainActivity extends GameActivity {
// Listener for display insets (cutouts) to pass values into native code. // Listener for display insets (cutouts) to pass values into native code.
View content = getWindow().getDecorView().findViewById(android.R.id.content); View content = getWindow().getDecorView().findViewById(android.R.id.content);
ViewCompat.setOnApplyWindowInsetsListener(content, (v, insets) -> { ViewCompat.setOnApplyWindowInsetsListener(content, (v, insets) -> {
// Setup cutouts values. // Get display cutouts.
DisplayCutoutCompat dc = insets.getDisplayCutout(); DisplayCutoutCompat dc = insets.getDisplayCutout();
int cutoutTop = 0; int cutoutTop = 0;
int cutoutRight = 0; int cutoutRight = 0;
@ -140,7 +160,7 @@ public class MainActivity extends GameActivity {
// Get display insets. // Get display insets.
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
// Setup values to pass into native code. // Pass values into native code.
int[] values = new int[]{0, 0, 0, 0}; int[] values = new int[]{0, 0, 0, 0};
values[0] = Utils.pxToDp(Integer.max(cutoutTop, systemBars.top), this); values[0] = Utils.pxToDp(Integer.max(cutoutTop, systemBars.top), this);
values[1] = Utils.pxToDp(Integer.max(cutoutRight, systemBars.right), this); values[1] = Utils.pxToDp(Integer.max(cutoutRight, systemBars.right), this);
@ -166,7 +186,60 @@ public class MainActivity extends GameActivity {
BackgroundService.start(this); BackgroundService.start(this);
} }
}); });
// Check if intent has data on launch.
if (savedInstanceState == null) {
onNewIntent(getIntent());
} }
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
String action = intent.getAction();
// Check if file was open with the application.
if (action != null && action.equals(Intent.ACTION_VIEW)) {
Intent i = getIntent();
i.setData(intent.getData());
setIntent(i);
onFile();
}
}
// Callback when associated file was open.
private void onFile() {
Uri data = getIntent().getData();
if (data == null) {
return;
}
if (Build.VERSION.SDK_INT >= 30) {
if (!Environment.isExternalStorageManager()) {
Intent i = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
mOpenFilePermissionsResult.launch(i);
return;
}
}
try {
ParcelFileDescriptor parcelFile = getContentResolver().openFileDescriptor(data, "r");
FileReader fileReader = new FileReader(parcelFile.getFileDescriptor());
BufferedReader reader = new BufferedReader(fileReader);
String line;
StringBuilder buff = new StringBuilder();
while ((line = reader.readLine()) != null) {
buff.append(line);
}
reader.close();
fileReader.close();
// Provide file content into native code.
onData(buff.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
// Pass data into native code.
public native void onData(String data);
@Override @Override
public void onConfigurationChanged(Configuration newConfig) { public void onConfigurationChanged(Configuration newConfig) {
@ -232,17 +305,17 @@ public class MainActivity extends GameActivity {
// Implemented into native code to handle key code BACK event. // Implemented into native code to handle key code BACK event.
public native void onBack(); public native void onBack();
// Actions on app exit. // Called from native code to exit app.
private void onExit() { public void exit() {
unregisterReceiver(mBroadcastReceiver); finishAndRemoveTask();
BackgroundService.stop(this);
} }
@Override @Override
protected void onDestroy() { protected void onDestroy() {
onExit(); unregisterReceiver(mBroadcastReceiver);
BackgroundService.stop(this);
// Kill process after 3 seconds if app was terminated from recent apps to prevent app hanging. // Kill process after 3 secs if app was terminated from recent apps to prevent app hang.
new Thread(() -> { new Thread(() -> {
try { try {
onTermination(); onTermination();
@ -253,9 +326,7 @@ public class MainActivity extends GameActivity {
} }
}).start(); }).start();
// Destroy an app and kill process.
super.onDestroy(); super.onDestroy();
Process.killProcess(Process.myPid());
} }
// Notify native code to stop activity (e.g. node) if app was terminated from recent apps. // Notify native code to stop activity (e.g. node) if app was terminated from recent apps.
@ -298,18 +369,16 @@ public class MainActivity extends GameActivity {
// Called from native code to start camera. // Called from native code to start camera.
public void startCamera() { public void startCamera() {
// Check permissions.
String notificationsPermission = Manifest.permission.CAMERA; String notificationsPermission = Manifest.permission.CAMERA;
if (checkSelfPermission(notificationsPermission) != PackageManager.PERMISSION_GRANTED) { if (checkSelfPermission(notificationsPermission) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[] { notificationsPermission }, CAMERA_PERMISSION_CODE); requestPermissions(new String[] { notificationsPermission }, CAMERA_PERMISSION_CODE);
} else { } else {
// Start .
if (mCameraProviderFuture == null) { if (mCameraProviderFuture == null) {
mCameraProviderFuture = ProcessCameraProvider.getInstance(this); mCameraProviderFuture = ProcessCameraProvider.getInstance(this);
mCameraProviderFuture.addListener(() -> { mCameraProviderFuture.addListener(() -> {
try { try {
mCameraProvider = mCameraProviderFuture.get(); mCameraProvider = mCameraProviderFuture.get();
// Launch camera. // Start camera.
openCamera(); openCamera();
} catch (Exception e) { } catch (Exception e) {
View content = findViewById(android.R.id.content); View content = findViewById(android.R.id.content);
@ -402,7 +471,7 @@ public class MainActivity extends GameActivity {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT); Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*"); intent.setType("*/*");
try { try {
mFilePickResultLauncher.launch(Intent.createChooser(intent, "Pick file")); mFilePickResult.launch(Intent.createChooser(intent, "Pick file"));
} catch (android.content.ActivityNotFoundException ex) { } catch (android.content.ActivityNotFoundException ex) {
onFilePick(""); onFilePick("");
} }

View file

@ -110,7 +110,7 @@ impl<Platform: PlatformCallbacks> App<Platform> {
} }
// Provide incoming data to wallets. // Provide incoming data to wallets.
if let Some(data) = crate::consume_passed_data() { if let Some(data) = crate::consume_incoming_data() {
if !data.is_empty() { if !data.is_empty() {
self.content.wallets.on_data(ui, Some(data), &self.platform); self.content.wallets.on_data(ui, Some(data), &self.platform);
} }

View file

@ -66,6 +66,10 @@ impl PlatformCallbacks for Android {
*w_ctx = Some(ctx.clone()); *w_ctx = Some(ctx.clone());
} }
fn exit(&self) {
self.call_java_method("exit", "()V", &[]).unwrap();
}
fn show_keyboard(&self) { fn show_keyboard(&self) {
// Disable NDK soft input show call before fix for egui. // Disable NDK soft input show call before fix for egui.
// self.android_app.show_soft_input(false); // self.android_app.show_soft_input(false);
@ -137,20 +141,18 @@ impl PlatformCallbacks for Android {
fn share_data(&self, name: String, data: Vec<u8>) -> Result<(), std::io::Error> { fn share_data(&self, name: String, data: Vec<u8>) -> Result<(), std::io::Error> {
// Create file at cache dir. // Create file at cache dir.
let default_cache = OsString::from(dirs::cache_dir().unwrap()); let default_cache = OsString::from(dirs::cache_dir().unwrap());
let mut cache = PathBuf::from(env::var_os("XDG_CACHE_HOME").unwrap_or(default_cache)); let mut file = PathBuf::from(env::var_os("XDG_CACHE_HOME").unwrap_or(default_cache));
cache.push("images"); file.push(name);
std::fs::create_dir_all(cache.to_str().unwrap())?; if file.exists() {
cache.push(name); std::fs::remove_file(file.clone())?;
if cache.exists() {
std::fs::remove_file(cache.clone())?;
} }
let mut image = File::create_new(cache.clone())?; let mut image = File::create_new(file.clone())?;
image.write_all(data.as_slice())?; image.write_all(data.as_slice())?;
image.sync_all()?; image.sync_all()?;
// Call share modal at system. // Call share modal at system.
let vm = unsafe { jni::JavaVM::from_raw(self.android_app.vm_as_ptr() as _) }.unwrap(); let vm = unsafe { jni::JavaVM::from_raw(self.android_app.vm_as_ptr() as _) }.unwrap();
let env = vm.attach_current_thread().unwrap(); let env = vm.attach_current_thread().unwrap();
let arg_value = env.new_string(cache.to_str().unwrap()).unwrap(); let arg_value = env.new_string(file.to_str().unwrap()).unwrap();
self.call_java_method("shareImage", self.call_java_method("shareImage",
"(Ljava/lang/String;)V", "(Ljava/lang/String;)V",
&[JValue::Object(&JObject::from(arg_value))]).unwrap(); &[JValue::Object(&JObject::from(arg_value))]).unwrap();

View file

@ -43,6 +43,14 @@ impl PlatformCallbacks for Desktop {
*w_ctx = Some(ctx.clone()); *w_ctx = Some(ctx.clone());
} }
fn exit(&self) {
let r_ctx = self.ctx.read();
if r_ctx.is_some() {
let ctx = r_ctx.as_ref().unwrap();
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
}
}
fn show_keyboard(&self) {} fn show_keyboard(&self) {}
fn hide_keyboard(&self) {} fn hide_keyboard(&self) {}

View file

@ -23,6 +23,7 @@ pub mod platform;
pub trait PlatformCallbacks { pub trait PlatformCallbacks {
fn set_context(&mut self, ctx: &egui::Context); fn set_context(&mut self, ctx: &egui::Context);
fn exit(&self);
fn show_keyboard(&self); fn show_keyboard(&self);
fn hide_keyboard(&self); fn hide_keyboard(&self);
fn copy_string_to_buffer(&self, data: String); fn copy_string_to_buffer(&self, data: String);

View file

@ -40,8 +40,8 @@ pub struct Content {
/// Central panel [`WalletsContent`] content. /// Central panel [`WalletsContent`] content.
pub wallets: WalletsContent, pub wallets: WalletsContent,
/// Check if app exit is allowed on close event of [`eframe::App`] implementation. /// Check if app exit is allowed on Desktop close event.
pub(crate) exit_allowed: bool, pub exit_allowed: bool,
/// Flag to show exit progress at [`Modal`]. /// Flag to show exit progress at [`Modal`].
show_exit_progress: bool, show_exit_progress: bool,
@ -83,7 +83,7 @@ impl ModalContainer for Content {
modal: &Modal, modal: &Modal,
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
match modal.id { match modal.id {
Self::EXIT_CONFIRMATION_MODAL => self.exit_modal_content(ui, modal), Self::EXIT_CONFIRMATION_MODAL => self.exit_modal_content(ui, modal, cb),
Self::SETTINGS_MODAL => self.settings_modal_ui(ui, modal), Self::SETTINGS_MODAL => self.settings_modal_ui(ui, modal),
Self::ANDROID_INTEGRATED_NODE_WARNING_MODAL => self.android_warning_modal_ui(ui, modal), Self::ANDROID_INTEGRATED_NODE_WARNING_MODAL => self.android_warning_modal_ui(ui, modal),
Self::CRASH_REPORT_MODAL => self.crash_report_modal_ui(ui, modal, cb), Self::CRASH_REPORT_MODAL => self.crash_report_modal_ui(ui, modal, cb),
@ -206,11 +206,11 @@ impl Content {
} }
/// Draw exit confirmation modal content. /// Draw exit confirmation modal content.
fn exit_modal_content(&mut self, ui: &mut egui::Ui, modal: &Modal) { fn exit_modal_content(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
if self.show_exit_progress { if self.show_exit_progress {
if !Node::is_running() { if !Node::is_running() {
self.exit_allowed = true; self.exit_allowed = true;
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close); cb.exit();
modal.close(); modal.close();
} }
ui.add_space(16.0); ui.add_space(16.0);
@ -244,7 +244,7 @@ impl Content {
View::button_ui(ui, t!("modal_exit.exit"), Colors::white_or_black(false), |ui| { View::button_ui(ui, t!("modal_exit.exit"), Colors::white_or_black(false), |ui| {
if !Node::is_running() { if !Node::is_running() {
self.exit_allowed = true; self.exit_allowed = true;
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close); cb.exit();
modal.close(); modal.close();
} else { } else {
Node::stop(true); Node::stop(true);

View file

@ -36,12 +36,10 @@ pub struct WalletsContent {
/// Wallet selection [`Modal`] content. /// Wallet selection [`Modal`] content.
wallet_selection_content: Option<WalletsModal>, wallet_selection_content: Option<WalletsModal>,
/// Wallet opening [`Modal`] content. /// Wallet opening [`Modal`] content.
open_wallet_content: Option<OpenWalletModal>, open_wallet_content: Option<OpenWalletModal>,
/// Wallet connection selection content. /// Wallet connection selection content.
conn_modal_content: Option<WalletConnectionModal>, conn_selection_content: Option<WalletConnectionModal>,
/// Selected [`Wallet`] content. /// Selected [`Wallet`] content.
wallet_content: WalletContent, wallet_content: WalletContent,
@ -70,7 +68,7 @@ impl Default for WalletsContent {
wallets: WalletList::default(), wallets: WalletList::default(),
wallet_selection_content: None, wallet_selection_content: None,
open_wallet_content: None, open_wallet_content: None,
conn_modal_content: None, conn_selection_content: None,
wallet_content: WalletContent::new(None), wallet_content: WalletContent::new(None),
creation_content: WalletCreation::default(), creation_content: WalletCreation::default(),
show_wallets_at_dual_panel: AppConfig::show_wallets_at_dual_panel(), show_wallets_at_dual_panel: AppConfig::show_wallets_at_dual_panel(),
@ -106,7 +104,7 @@ impl ModalContainer for WalletsContent {
self.creation_content.name_pass_modal_ui(ui, modal, cb) self.creation_content.name_pass_modal_ui(ui, modal, cb)
}, },
CONNECTION_SELECTION_MODAL => { CONNECTION_SELECTION_MODAL => {
if let Some(content) = self.conn_modal_content.as_mut() { if let Some(content) = self.conn_selection_content.as_mut() {
content.ui(ui, modal, cb, |id| { content.ui(ui, modal, cb, |id| {
// Update wallet connection on select. // Update wallet connection on select.
let list = self.wallets.list(); let list = self.wallets.list();
@ -303,6 +301,7 @@ impl WalletsContent {
} }
} }
/// Show wallet selection with provided optional data.
fn show_wallet_selection_modal(&mut self, data: Option<String>) { fn show_wallet_selection_modal(&mut self, data: Option<String>) {
self.wallet_selection_content = Some(WalletsModal::new(None, data, true)); self.wallet_selection_content = Some(WalletsModal::new(None, data, true));
// Show wallet selection modal. // Show wallet selection modal.
@ -557,7 +556,7 @@ impl WalletsContent {
/// Show [`Modal`] to select connection for the wallet. /// Show [`Modal`] to select connection for the wallet.
fn show_connection_selector_modal(&mut self, wallet: &Wallet) { fn show_connection_selector_modal(&mut self, wallet: &Wallet) {
let ext_conn = wallet.get_current_ext_conn(); let ext_conn = wallet.get_current_ext_conn();
self.conn_modal_content = Some(WalletConnectionModal::new(ext_conn)); self.conn_selection_content = Some(WalletConnectionModal::new(ext_conn));
// Show modal. // Show modal.
Modal::new(CONNECTION_SELECTION_MODAL) Modal::new(CONNECTION_SELECTION_MODAL)
.position(ModalPosition::CenterTop) .position(ModalPosition::CenterTop)

View file

@ -260,15 +260,15 @@ fn setup_i18n() {
} }
} }
/// Get data provided from deeplink or opened file. /// Get data from deeplink or opened file.
pub fn consume_passed_data() -> Option<String> { pub fn consume_incoming_data() -> Option<String> {
let has_data = { let has_data = {
let r_data = PASSED_DATA.read(); let r_data = INCOMING_DATA.read();
r_data.is_some() r_data.is_some()
}; };
if has_data { if has_data {
// Clear data. // Clear data.
let mut w_data = PASSED_DATA.write(); let mut w_data = INCOMING_DATA.write();
let data = w_data.clone(); let data = w_data.clone();
*w_data = None; *w_data = None;
return data; return data;
@ -278,11 +278,35 @@ pub fn consume_passed_data() -> Option<String> {
/// Provide data from deeplink or opened file. /// Provide data from deeplink or opened file.
pub fn on_data(data: String) { pub fn on_data(data: String) {
let mut w_data = PASSED_DATA.write(); let mut w_data = INCOMING_DATA.write();
*w_data = Some(data); *w_data = Some(data);
} }
lazy_static! { lazy_static! {
/// Data provided from deeplink or opened file. /// Data provided from deeplink or opened file.
pub static ref PASSED_DATA: Arc<RwLock<Option<String>>> = Arc::new(RwLock::new(None)); pub static ref INCOMING_DATA: Arc<RwLock<Option<String>>> = Arc::new(RwLock::new(None));
}
/// Callback from Java code with with passed data.
#[allow(dead_code)]
#[allow(non_snake_case)]
#[cfg(target_os = "android")]
#[no_mangle]
pub extern "C" fn Java_mw_gri_android_MainActivity_onData(
_env: jni::JNIEnv,
_class: jni::objects::JObject,
char: jni::sys::jstring
) {
unsafe {
let j_obj = jni::objects::JString::from_raw(char);
if let Ok(j_str) = _env.get_string_unchecked(j_obj.as_ref()) {
match j_str.to_str() {
Ok(str) => {
let mut w_path = INCOMING_DATA.write();
*w_path = Some(str.to_string());
}
Err(_) => {}
}
};
}
} }

View file

@ -48,9 +48,10 @@ pub struct Settings {
impl Settings { impl Settings {
/// Main application directory name. /// Main application directory name.
pub const MAIN_DIR_NAME: &'static str = ".grim"; pub const MAIN_DIR_NAME: &'static str = ".grim";
/// Crash report file name. /// Crash report file name.
pub const CRASH_REPORT_FILE_NAME: &'static str = "crash.log"; pub const CRASH_REPORT_FILE_NAME: &'static str = "crash.log";
/// Application socket name.
pub const SOCKET_NAME: &'static str = "grim.sock";
/// Initialize settings with app and node configs. /// Initialize settings with app and node configs.
fn init() -> Self { fn init() -> Self {
@ -142,10 +143,10 @@ impl Settings {
} }
/// Get desktop application socket path. /// Get desktop application socket path.
pub fn socket_path() -> String { pub fn socket_path() -> PathBuf {
let mut socket_path = Self::base_path(None); let mut socket_path = Self::base_path(None);
socket_path.push("grim.socket"); socket_path.push(Self::SOCKET_NAME);
socket_path.to_str().unwrap().to_string() socket_path
} }
/// Get configuration file path from provided name and sub-directory if needed. /// Get configuration file path from provided name and sub-directory if needed.