Skip to content

Commit 4d22b46

Browse files
committed
Support copying url, username and password to the clipboard.
1 parent fcf1f02 commit 4d22b46

File tree

9 files changed

+165
-46
lines changed

9 files changed

+165
-46
lines changed

java/res/drawable-hdpi/copy.png

374 Bytes
Loading

java/res/drawable-ldpi/copy.png

374 Bytes
Loading

java/res/drawable-mdpi/copy.png

374 Bytes
Loading

java/res/drawable-xhdpi/copy.png

374 Bytes
Loading

java/res/layout/fragment_show_entry.xml

Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -38,47 +38,89 @@
3838
android:text="Entry Title"
3939
android:textAppearance="?android:attr/textAppearanceSmall"/>
4040

41+
4142
<EditText
4243
android:id="@+id/editName"
4344
android:layout_width="match_parent"
4445
android:layout_height="wrap_content"
4546
android:inputType="text"/>
4647

47-
<TextView
48-
android:id="@+id/urlLabel"
49-
android:layout_width="wrap_content"
50-
android:layout_height="wrap_content"
48+
<LinearLayout
49+
android:layout_width="match_parent"
50+
android:layout_height="match_parent"
51+
android:orientation="horizontal"
5152
android:layout_marginTop="20sp"
52-
android:text="URL"
53-
android:textAppearance="?android:attr/textAppearanceSmall"/>
53+
android:layout_gravity="center">
54+
<TextView
55+
android:id="@+id/urlLabel"
56+
android:layout_width="wrap_content"
57+
android:layout_height="wrap_content"
58+
android:text="URL"
59+
android:textAppearance="?android:attr/textAppearanceSmall"/>
60+
<ImageButton
61+
android:id="@+id/copyUrlButton"
62+
android:layout_width="wrap_content"
63+
android:layout_height="wrap_content"
64+
android:src="@drawable/copy"
65+
android:layout_marginLeft="10sp"
66+
android:background="@null"/>
67+
</LinearLayout>
5468

5569
<EditText
5670
android:id="@+id/editUrl"
5771
android:layout_width="match_parent"
5872
android:layout_height="wrap_content"
5973
android:inputType="text"/>
6074

61-
<TextView
62-
android:id="@+id/userLabel"
63-
android:layout_width="wrap_content"
64-
android:layout_height="wrap_content"
65-
android:layout_marginTop="10sp"
66-
android:text="Username"
67-
android:textAppearance="?android:attr/textAppearanceSmall"/>
75+
<LinearLayout
76+
android:layout_width="match_parent"
77+
android:layout_height="match_parent"
78+
android:orientation="horizontal"
79+
android:layout_marginTop="20sp"
80+
android:layout_gravity="center">
81+
<TextView
82+
android:id="@+id/userLabel"
83+
android:layout_width="wrap_content"
84+
android:layout_height="wrap_content"
85+
android:layout_marginTop="10sp"
86+
android:text="Username"
87+
android:textAppearance="?android:attr/textAppearanceSmall"/>
88+
<ImageButton
89+
android:id="@+id/copyUsernameButton"
90+
android:layout_width="wrap_content"
91+
android:layout_height="wrap_content"
92+
android:src="@drawable/copy"
93+
android:layout_marginLeft="10sp"
94+
android:background="@null"/>
95+
</LinearLayout>
6896

6997
<EditText
7098
android:id="@+id/editUser"
7199
android:layout_width="match_parent"
72100
android:layout_height="wrap_content"
73101
android:inputType="text"/>
74102

75-
<TextView
76-
android:id="@+id/passwordLabel"
77-
android:layout_width="wrap_content"
78-
android:layout_height="wrap_content"
79-
android:layout_marginTop="10sp"
80-
android:text="Password"
81-
android:textAppearance="?android:attr/textAppearanceSmall"/>
103+
<LinearLayout
104+
android:layout_width="match_parent"
105+
android:layout_height="match_parent"
106+
android:orientation="horizontal"
107+
android:layout_marginTop="20sp"
108+
android:layout_gravity="center">
109+
<TextView
110+
android:id="@+id/passwordLabel"
111+
android:layout_width="wrap_content"
112+
android:layout_height="wrap_content"
113+
android:layout_marginTop="10sp"
114+
android:text="Password"
115+
android:textAppearance="?android:attr/textAppearanceSmall"/>
116+
<ImageButton
117+
android:id="@+id/copyPasswordButton"
118+
android:layout_width="wrap_content"
119+
android:layout_height="wrap_content"
120+
android:src="@drawable/copy"
121+
android:layout_marginLeft="10sp"
122+
android:background="@null"/>
123+
</LinearLayout>
82124

83125
<EditText
84126
android:id="@+id/editPassword"

java/src/main/java/org/astonbitecode/rustkeylock/api/InterfaceWithRust.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,10 @@ void execute(RustCallback showMenuCb, EntryCallback showEntryCb, EntriesSetCallb
157157
* file location
158158
*/
159159
void set_configuration(StringList stringList);
160+
161+
/**
162+
* Copies the data in the Clipboard
163+
* @param data
164+
*/
165+
void copy(String data);
160166
}

java/src/main/java/org/astonbitecode/rustkeylock/fragments/ShowEntry.java

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
11
package org.astonbitecode.rustkeylock.fragments;
22

3-
import android.text.InputFilter;
4-
import android.text.Spanned;
5-
import org.astonbitecode.rustkeylock.R;
6-
import org.astonbitecode.rustkeylock.api.InterfaceWithRust;
7-
import org.astonbitecode.rustkeylock.api.JavaEntry;
8-
import org.astonbitecode.rustkeylock.handlers.back.BackButtonHandler;
9-
import org.astonbitecode.rustkeylock.utils.Defs;
10-
113
import android.app.Fragment;
4+
import android.content.ClipData;
5+
import android.content.ClipboardManager;
6+
import android.content.Context;
127
import android.os.Bundle;
8+
import android.text.method.PasswordTransformationMethod;
139
import android.util.Log;
1410
import android.view.LayoutInflater;
1511
import android.view.View;
1612
import android.view.View.OnClickListener;
1713
import android.view.ViewGroup;
1814
import android.widget.Button;
1915
import android.widget.EditText;
16+
import android.widget.ImageButton;
2017
import android.widget.TextView;
18+
import org.astonbitecode.rustkeylock.R;
19+
import org.astonbitecode.rustkeylock.api.InterfaceWithRust;
20+
import org.astonbitecode.rustkeylock.api.JavaEntry;
21+
import org.astonbitecode.rustkeylock.handlers.back.BackButtonHandler;
22+
import org.astonbitecode.rustkeylock.utils.Defs;
2123

2224
import java.net.MalformedURLException;
2325
import java.net.URL;
@@ -126,9 +128,25 @@ public void onClick(View view) {
126128
Log.d(TAG, "Clicked confirm deletion on entry with id " + entryIndex);
127129

128130
InterfaceWithRust.INSTANCE.delete_entry(entryIndex);
131+
} else if (view.getId() == R.id.copyUrlButton) {
132+
Log.d(TAG, "Copying URL of entry with id " + entryIndex);
133+
addToClipboard(entryUrl);
134+
} else if (view.getId() == R.id.copyUsernameButton) {
135+
Log.d(TAG, "Copying username of entry with id " + entryIndex);
136+
addToClipboard(entryUser);
137+
} else if (view.getId() == R.id.copyPasswordButton) {
138+
Log.d(TAG, "Copying password of entry with id " + entryIndex);
139+
addToClipboard(entryPass);
129140
}
130141
}
131142

143+
private void addToClipboard(String data) {
144+
ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
145+
ClipData clip = ClipData.newPlainText("rust-keylock", data);
146+
clipboard.setPrimaryClip(clip);
147+
InterfaceWithRust.INSTANCE.copy(data);
148+
}
149+
132150
private void prepareUiElements(View v) {
133151
Button eb = (Button) v.findViewById(R.id.editButton);
134152
eb.setOnClickListener(this);
@@ -156,17 +174,9 @@ private void prepareUiElements(View v) {
156174
this.userText = userText;
157175
EditText passwordText = (EditText) v.findViewById(R.id.editPassword);
158176
passwordText.setText(entry.getPass());
177+
passwordText.setEnabled(edit);
159178
if (!edit) {
160-
passwordText.setFilters(new InputFilter[]{
161-
new InputFilter() {
162-
public CharSequence filter(CharSequence src, int start,
163-
int end, Spanned dst, int dstart, int dend) {
164-
return src.length() < 1 ? dst.subSequence(dstart, dend) : "";
165-
}
166-
}
167-
});
168-
} else {
169-
passwordText.setFilters(new InputFilter[]{});
179+
passwordText.setTransformationMethod(PasswordTransformationMethod.getInstance());
170180
}
171181
this.passwordText = passwordText;
172182
EditText descriptionText = (EditText) v.findViewById(R.id.editDescriptionArea);
@@ -180,6 +190,12 @@ public CharSequence filter(CharSequence src, int start,
180190
Button aysb = (Button) v.findViewById(R.id.areYouSureButton);
181191
aysb.setOnClickListener(this);
182192
aysb.setVisibility((!edit && delete) ? View.VISIBLE : View.GONE);
193+
ImageButton cub = (ImageButton) v.findViewById(R.id.copyUrlButton);
194+
cub.setOnClickListener(this);
195+
ImageButton cuserb = (ImageButton) v.findViewById(R.id.copyUsernameButton);
196+
cuserb.setOnClickListener(this);
197+
ImageButton cpb = (ImageButton) v.findViewById(R.id.copyPasswordButton);
198+
cpb.setOnClickListener(this);
183199
}
184200

185201
@Override

rust/src/android_editor.rs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ use rust_keylock::{Editor, UserSelection, Menu, Safe, UserOption, MessageSeverit
33
use super::{StringCallback, StringListCallback, ShowEntryCallback, ShowEntriesSetCallback,
44
LogCallback, logger, JavaEntriesSet, JavaEntry, ShowMessageCallback,
55
JavaUserOptionsSet, StringList};
6-
use std::sync::mpsc::Receiver;
6+
use std::sync::mpsc::{Receiver, Sender};
7+
use std::sync::Mutex;
78

89
pub struct AndroidImpl {
910
show_menu_cb: StringCallback,
@@ -12,6 +13,8 @@ pub struct AndroidImpl {
1213
show_message_cb: ShowMessageCallback,
1314
edit_configuration_cb: StringListCallback,
1415
rx: Receiver<UserSelection>,
16+
tx: Sender<UserSelection>,
17+
previous_menu: Mutex<Option<Menu>>,
1518
}
1619

1720
pub fn new(show_menu_cb: StringCallback,
@@ -20,7 +23,8 @@ pub fn new(show_menu_cb: StringCallback,
2023
show_message_cb: ShowMessageCallback,
2124
edit_configuration_cb: StringListCallback,
2225
log_cb: LogCallback,
23-
rx: Receiver<UserSelection>)
26+
rx: Receiver<UserSelection>,
27+
tx: Sender<UserSelection>)
2428
-> AndroidImpl {
2529

2630
// Initialize the Android logger
@@ -33,6 +37,42 @@ pub fn new(show_menu_cb: StringCallback,
3337
show_message_cb: show_message_cb,
3438
edit_configuration_cb: edit_configuration_cb,
3539
rx: rx,
40+
tx: tx,
41+
previous_menu: Mutex::new(None),
42+
}
43+
}
44+
45+
impl AndroidImpl {
46+
fn update_internal_state(&self, menu: &UserSelection) {
47+
match menu {
48+
&UserSelection::GoTo(ref menu) => { self.update_menu(menu.clone()) }
49+
_ => {
50+
// ignore
51+
}
52+
}
53+
}
54+
55+
fn update_menu(&self, menu: Menu) {
56+
match self.previous_menu.lock() {
57+
Ok(mut previous_menu_mut) => {
58+
*previous_menu_mut = Some(menu);
59+
}
60+
Err(error) => {
61+
warn!("Warning! Could not update the internal state. Reason: {:?}", error);
62+
}
63+
};
64+
}
65+
66+
fn previous_menu(&self) -> Option<Menu> {
67+
match self.previous_menu.lock() {
68+
Ok(previous_menu_mut) => {
69+
previous_menu_mut.clone()
70+
}
71+
Err(error) => {
72+
warn!("Warning! Could not retrieve the internal state. Reason: {:?}", error);
73+
Some(Menu::Main)
74+
}
75+
}
3676
}
3777
}
3878

@@ -115,6 +155,9 @@ impl Editor for AndroidImpl {
115155
configuration.nextcloud.use_self_signed_certificate.to_string()];
116156
(self.edit_configuration_cb)(Box::new(StringList::from(conf_strings)))
117157
}
158+
&Menu::Current => {
159+
let _ = self.tx.send(UserSelection::GoTo(self.previous_menu().unwrap_or(Menu::Main)));
160+
}
118161
other => {
119162
panic!("Menu '{:?}' cannot be used with Entries. Please, consider opening a bug \
120163
to the developers.",
@@ -130,6 +173,7 @@ impl Editor for AndroidImpl {
130173
UserSelection::GoTo(Menu::Main)
131174
}
132175
};
176+
self.update_internal_state(&usin);
133177
debug!("Proceeding after receiving User Input from {:?}", menu);
134178
usin
135179
}

rust/src/lib.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -203,23 +203,23 @@ pub extern "C" fn execute(show_menu_cb: StringCallback,
203203
edit_configuration_cb: StringListCallback,
204204
log_cb: LogCallback,
205205
cert_file_path_java_string: *const c_char) {
206-
207-
let cert_file_path = to_rust_string(cert_file_path_java_string);
206+
let cert_file_path = to_rust_string(cert_file_path_java_string);
208207
::std::env::set_var("SSL_CERT_FILE", cert_file_path);
209208
let (tx, rx): (Sender<UserSelection>, Receiver<UserSelection>) = mpsc::channel();
210209
// Release the lock before calling the execute.
211210
// Execute will not return for the whole lifetime of the app, so the lock would be for ever acquired if was not explicitly released using the brackets.
212211
{
213212
let mut tx_opt = TX.lock().unwrap();
214-
*tx_opt = Some(tx);
213+
*tx_opt = Some(tx.clone());
215214
}
216215
let editor = android_editor::new(show_menu_cb,
217216
show_entry_cb,
218217
show_entries_set_cb,
219218
show_message_cb,
220219
edit_configuration_cb,
221220
log_cb,
222-
rx);
221+
rx,
222+
tx);
223223
debug!("TX Mutex initialized. Executing native rust_keylock!");
224224
rust_keylock::execute(&editor)
225225
}
@@ -372,8 +372,8 @@ pub extern "C" fn user_option_selected(label: *const c_char,
372372
};
373373

374374
tx.send(UserSelection::UserOption(UserOption::from((to_rust_string(label),
375-
to_rust_string(value),
376-
to_rust_string(short_label)))))
375+
to_rust_string(value),
376+
to_rust_string(short_label)))))
377377
.unwrap();
378378
debug!("user_option_selected sent UserSelection to the TX");
379379
}
@@ -408,6 +408,17 @@ pub extern "C" fn set_configuration(string_list: &StringList) {
408408
debug!("set_configuration sent UserSelection to the TX");
409409
}
410410

411+
#[no_mangle]
412+
pub extern "C" fn copy(data: *const c_char) {
413+
debug!("copying");
414+
let tx = {
415+
TX.lock().unwrap().as_ref().unwrap().clone()
416+
};
417+
tx.send(UserSelection::AddToClipboard(to_rust_string(data)))
418+
.unwrap();
419+
debug!("copy sent UserSelection to the TX");
420+
}
421+
411422
#[no_mangle]
412423
pub extern "C" fn drop_java_entry(_: Box<JavaEntry>) {
413424
// Do nothing here. Because we own the JavaEntry here (we're using a Box) and we're not

0 commit comments

Comments
 (0)