mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Kotlinized Magikeyboard
This commit is contained in:
@@ -407,7 +407,7 @@ class GroupActivity : LockingActivity(),
|
||||
EntryActivity.launch(this@GroupActivity, entry, readOnly)
|
||||
},
|
||||
{
|
||||
MagikIME.setEntryKey(getEntry(entry))
|
||||
MagikIME.entryKey = getEntry(entry)
|
||||
// Show the notification if allowed in Preferences
|
||||
if (PreferencesUtil.enableKeyboardNotificationEntry(this@GroupActivity)) {
|
||||
startService(Intent(
|
||||
@@ -441,7 +441,7 @@ class GroupActivity : LockingActivity(),
|
||||
if (entry.containsCustomFields()) {
|
||||
entry.fields
|
||||
.doActionToAllCustomProtectedField { key, value ->
|
||||
entryModel.addCustomField(
|
||||
entryModel.customFields.add(
|
||||
Field(key, value.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
package com.kunzisoft.keepass.magikeyboard;
|
||||
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v7.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.magikeyboard.receiver.LockBroadcastReceiver;
|
||||
import com.kunzisoft.keepass.magikeyboard.receiver.NotificationDeleteBroadcastReceiver;
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper;
|
||||
|
||||
import static com.kunzisoft.keepass.magikeyboard.receiver.LockBroadcastReceiver.LOCK_ACTION;
|
||||
|
||||
public class KeyboardEntryNotificationService extends Service {
|
||||
|
||||
private static final String TAG = KeyboardEntryNotificationService.class.getName();
|
||||
|
||||
private static final String CHANNEL_ID_KEYBOARD = "com.kunzisoft.keyboard.notification.entry.channel";
|
||||
private static final String CHANNEL_NAME_KEYBOARD = "Magikeyboard notification";
|
||||
|
||||
private NotificationManager notificationManager;
|
||||
private Thread cleanNotificationTimer;
|
||||
private int notificationId = 582;
|
||||
private long notificationTimeoutMilliSecs;
|
||||
|
||||
private LockBroadcastReceiver lockBroadcastReceiver;
|
||||
private PendingIntent pendingDeleteIntent;
|
||||
|
||||
public KeyboardEntryNotificationService() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
|
||||
// Create notification channel for Oreo+
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel(CHANNEL_ID_KEYBOARD,
|
||||
CHANNEL_NAME_KEYBOARD,
|
||||
NotificationManager.IMPORTANCE_LOW);
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
// Register a lock receiver to stop notification service when lock on keyboard is performed
|
||||
lockBroadcastReceiver = new LockBroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceiveLock(Context context, Intent intent) {
|
||||
context.stopService(new Intent(context, KeyboardEntryNotificationService.class));
|
||||
}
|
||||
};
|
||||
registerReceiver(lockBroadcastReceiver, new IntentFilter(LOCK_ACTION));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (intent == null) {
|
||||
Log.w(TAG, "null intent");
|
||||
} else {
|
||||
newNotification();
|
||||
|
||||
}
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
private void newNotification() {
|
||||
|
||||
Intent deleteIntent = new Intent(this, NotificationDeleteBroadcastReceiver.class);
|
||||
pendingDeleteIntent =
|
||||
PendingIntent.getBroadcast(getApplicationContext(), 0, deleteIntent, 0);
|
||||
|
||||
if (MagikIME.getEntryKey() != null) {
|
||||
String entryTitle = getString(R.string.keyboard_notification_entry_content_title_text);
|
||||
String entryUsername = "";
|
||||
if (!MagikIME.getEntryKey().getTitle().isEmpty())
|
||||
entryTitle = MagikIME.getEntryKey().getTitle();
|
||||
if (!MagikIME.getEntryKey().getUsername().isEmpty())
|
||||
entryUsername = MagikIME.getEntryKey().getUsername();
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID_KEYBOARD)
|
||||
.setSmallIcon(R.drawable.ic_vpn_key_white_24dp)
|
||||
.setContentTitle(getString(R.string.keyboard_notification_entry_content_title, entryTitle))
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_SECRET)
|
||||
.setContentText(getString(R.string.keyboard_notification_entry_content_text, entryUsername))
|
||||
.setAutoCancel(false)
|
||||
.setContentIntent(null)
|
||||
.setDeleteIntent(pendingDeleteIntent);
|
||||
|
||||
notificationManager.cancel(notificationId);
|
||||
notificationManager.notify(notificationId, builder.build());
|
||||
|
||||
|
||||
|
||||
// Timeout only if notification clear is available
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
if (sharedPreferences.getBoolean(getString(R.string.keyboard_notification_entry_clear_close_key),
|
||||
getResources().getBoolean(R.bool.keyboard_notification_entry_clear_close_default))) {
|
||||
String keyboardTimeout = sharedPreferences.getString(getString(R.string.keyboard_entry_timeout_key),
|
||||
getString(R.string.timeout_default));
|
||||
try {
|
||||
notificationTimeoutMilliSecs = Long.parseLong(keyboardTimeout);
|
||||
} catch (NumberFormatException e) {
|
||||
notificationTimeoutMilliSecs = TimeoutHelper.DEFAULT_TIMEOUT;
|
||||
}
|
||||
|
||||
if (notificationTimeoutMilliSecs != TimeoutHelper.TIMEOUT_NEVER) {
|
||||
stopTask(cleanNotificationTimer);
|
||||
cleanNotificationTimer = new Thread(() -> {
|
||||
int maxPos = 100;
|
||||
long posDurationMills = notificationTimeoutMilliSecs / maxPos;
|
||||
for (int pos = maxPos; pos > 0; --pos) {
|
||||
builder.setProgress(maxPos, pos, false);
|
||||
notificationManager.notify(notificationId, builder.build());
|
||||
try {
|
||||
Thread.sleep(posDurationMills);
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
try {
|
||||
pendingDeleteIntent.send();
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
Log.e(TAG, "Unable to delete keyboard notification");
|
||||
}
|
||||
});
|
||||
cleanNotificationTimer.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void stopTask(Thread task) {
|
||||
if (task != null && task.isAlive())
|
||||
task.interrupt();
|
||||
}
|
||||
|
||||
private void destroyKeyboardNotification() {
|
||||
stopTask(cleanNotificationTimer);
|
||||
cleanNotificationTimer = null;
|
||||
unregisterReceiver(lockBroadcastReceiver);
|
||||
pendingDeleteIntent.cancel();
|
||||
|
||||
notificationManager.cancel(notificationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
|
||||
destroyKeyboardNotification();
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package com.kunzisoft.keepass.magikeyboard
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import android.support.v7.preference.PreferenceManager
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.magikeyboard.receiver.LockBroadcastReceiver
|
||||
import com.kunzisoft.keepass.magikeyboard.receiver.LockBroadcastReceiver.Companion.LOCK_ACTION
|
||||
import com.kunzisoft.keepass.magikeyboard.receiver.NotificationDeleteBroadcastReceiver
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
|
||||
class KeyboardEntryNotificationService : Service() {
|
||||
|
||||
private var notificationManager: NotificationManager? = null
|
||||
private var cleanNotificationTimer: Thread? = null
|
||||
private val notificationId = 582
|
||||
private var notificationTimeoutMilliSecs: Long = 0
|
||||
|
||||
private var lockBroadcastReceiver: LockBroadcastReceiver? = null
|
||||
private var pendingDeleteIntent: PendingIntent? = null
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
// Create notification channel for Oreo+
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(CHANNEL_ID_KEYBOARD,
|
||||
CHANNEL_NAME_KEYBOARD,
|
||||
NotificationManager.IMPORTANCE_LOW)
|
||||
notificationManager?.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
// Register a lock receiver to stop notification service when lock on keyboard is performed
|
||||
lockBroadcastReceiver = object : LockBroadcastReceiver() {
|
||||
override fun onReceiveLock(context: Context, intent: Intent) {
|
||||
context.stopService(Intent(context, KeyboardEntryNotificationService::class.java))
|
||||
}
|
||||
}
|
||||
registerReceiver(lockBroadcastReceiver, IntentFilter(LOCK_ACTION))
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (intent == null) {
|
||||
Log.w(TAG, "null intent")
|
||||
} else {
|
||||
newNotification()
|
||||
}
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
private fun newNotification() {
|
||||
|
||||
val deleteIntent = Intent(this, NotificationDeleteBroadcastReceiver::class.java)
|
||||
pendingDeleteIntent = PendingIntent.getBroadcast(applicationContext, 0, deleteIntent, 0)
|
||||
|
||||
if (MagikIME.entryKey != null) {
|
||||
var entryTitle: String? = getString(R.string.keyboard_notification_entry_content_title_text)
|
||||
var entryUsername: String? = ""
|
||||
if (MagikIME.entryKey!!.title.isNotEmpty())
|
||||
entryTitle = MagikIME.entryKey!!.title
|
||||
if (MagikIME.entryKey!!.username.isNotEmpty())
|
||||
entryUsername = MagikIME.entryKey!!.username
|
||||
|
||||
val builder = NotificationCompat.Builder(this, CHANNEL_ID_KEYBOARD)
|
||||
.setSmallIcon(R.drawable.ic_vpn_key_white_24dp)
|
||||
.setContentTitle(getString(R.string.keyboard_notification_entry_content_title, entryTitle))
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_SECRET)
|
||||
.setContentText(getString(R.string.keyboard_notification_entry_content_text, entryUsername))
|
||||
.setAutoCancel(false)
|
||||
.setContentIntent(null)
|
||||
.setDeleteIntent(pendingDeleteIntent)
|
||||
|
||||
notificationManager?.cancel(notificationId)
|
||||
notificationManager?.notify(notificationId, builder.build())
|
||||
|
||||
|
||||
// Timeout only if notification clear is available
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
if (sharedPreferences.getBoolean(getString(R.string.keyboard_notification_entry_clear_close_key),
|
||||
resources.getBoolean(R.bool.keyboard_notification_entry_clear_close_default))) {
|
||||
val keyboardTimeout = sharedPreferences.getString(getString(R.string.keyboard_entry_timeout_key),
|
||||
getString(R.string.timeout_default))
|
||||
notificationTimeoutMilliSecs = try {
|
||||
java.lang.Long.parseLong(keyboardTimeout)
|
||||
} catch (e: NumberFormatException) {
|
||||
TimeoutHelper.DEFAULT_TIMEOUT
|
||||
}
|
||||
|
||||
if (notificationTimeoutMilliSecs != TimeoutHelper.TIMEOUT_NEVER) {
|
||||
stopTask(cleanNotificationTimer)
|
||||
cleanNotificationTimer = Thread {
|
||||
val maxPos = 100
|
||||
val posDurationMills = notificationTimeoutMilliSecs / maxPos
|
||||
for (pos in maxPos downTo 1) {
|
||||
builder.setProgress(maxPos, pos, false)
|
||||
notificationManager?.notify(notificationId, builder.build())
|
||||
try {
|
||||
Thread.sleep(posDurationMills)
|
||||
} catch (e: InterruptedException) {
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
try {
|
||||
pendingDeleteIntent?.send()
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
Log.e(TAG, "Unable to delete keyboard notification")
|
||||
}
|
||||
}
|
||||
cleanNotificationTimer?.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopTask(task: Thread?) {
|
||||
if (task != null && task.isAlive)
|
||||
task.interrupt()
|
||||
}
|
||||
|
||||
private fun destroyKeyboardNotification() {
|
||||
stopTask(cleanNotificationTimer)
|
||||
cleanNotificationTimer = null
|
||||
unregisterReceiver(lockBroadcastReceiver)
|
||||
pendingDeleteIntent?.cancel()
|
||||
|
||||
notificationManager?.cancel(notificationId)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
||||
destroyKeyboardNotification()
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = KeyboardEntryNotificationService::class.java.name
|
||||
|
||||
private const val CHANNEL_ID_KEYBOARD = "com.kunzisoft.keyboard.notification.entry.channel"
|
||||
private const val CHANNEL_NAME_KEYBOARD = "Magikeyboard notification"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,313 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.magikeyboard;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
import android.inputmethodservice.Keyboard;
|
||||
import android.inputmethodservice.KeyboardView;
|
||||
import android.media.AudioManager;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.PopupWindow;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.magikeyboard.adapter.FieldsAdapter;
|
||||
import com.kunzisoft.keepass.magikeyboard.receiver.LockBroadcastReceiver;
|
||||
import com.kunzisoft.keepass.magikeyboard.view.MagikeyboardView;
|
||||
import com.kunzisoft.keepass.model.Entry;
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
||||
|
||||
import static com.kunzisoft.keepass.magikeyboard.receiver.LockBroadcastReceiver.LOCK_ACTION;
|
||||
|
||||
public class MagikIME extends InputMethodService
|
||||
implements KeyboardView.OnKeyboardActionListener {
|
||||
private static final String TAG = MagikIME.class.getName();
|
||||
|
||||
public static final int KEY_BACK_KEYBOARD = 600;
|
||||
public static final int KEY_CHANGE_KEYBOARD = 601;
|
||||
private static final int KEY_UNLOCK = 610;
|
||||
private static final int KEY_LOCK = 611;
|
||||
private static final int KEY_ENTRY = 620;
|
||||
private static final int KEY_USERNAME = 500;
|
||||
private static final int KEY_PASSWORD = 510;
|
||||
private static final int KEY_URL = 520;
|
||||
private static final int KEY_FIELDS = 530;
|
||||
|
||||
private static Entry entryKey = null;
|
||||
|
||||
private KeyboardView keyboardView;
|
||||
private Keyboard keyboard;
|
||||
private Keyboard keyboard_entry;
|
||||
private PopupWindow popupCustomKeys;
|
||||
private FieldsAdapter fieldsAdapter;
|
||||
private boolean playSoundDuringCLick;
|
||||
|
||||
private LockBroadcastReceiver lockBroadcastReceiver;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
// Remove the entry and lock the keyboard when the lock signal is receive
|
||||
lockBroadcastReceiver = new LockBroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceiveLock(Context context, Intent intent) {
|
||||
entryKey = null;
|
||||
assignKeyboardView();
|
||||
}
|
||||
};
|
||||
registerReceiver(lockBroadcastReceiver, new IntentFilter(LOCK_ACTION));
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateInputView() {
|
||||
keyboardView = (MagikeyboardView) getLayoutInflater().inflate(R.layout.keyboard_view, null);
|
||||
keyboard = new Keyboard(this, R.xml.keyboard_password);
|
||||
keyboard_entry = new Keyboard(this, R.xml.keyboard_password_entry);
|
||||
|
||||
assignKeyboardView();
|
||||
keyboardView.setOnKeyboardActionListener(this);
|
||||
keyboardView.setPreviewEnabled(false);
|
||||
|
||||
Context context = getBaseContext();
|
||||
View popupFieldsView = LayoutInflater.from(context)
|
||||
.inflate(R.layout.keyboard_popup_fields, new FrameLayout(context));
|
||||
|
||||
if (popupCustomKeys != null)
|
||||
popupCustomKeys.dismiss();
|
||||
|
||||
popupCustomKeys = new PopupWindow(context);
|
||||
popupCustomKeys.setWidth(WindowManager.LayoutParams.WRAP_CONTENT);
|
||||
popupCustomKeys.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
|
||||
popupCustomKeys.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
|
||||
popupCustomKeys.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
|
||||
popupCustomKeys.setContentView(popupFieldsView);
|
||||
|
||||
RecyclerView recyclerView = popupFieldsView.findViewById(R.id.keyboard_popup_fields_list);
|
||||
fieldsAdapter = new FieldsAdapter(this);
|
||||
fieldsAdapter.setOnItemClickListener(item -> getCurrentInputConnection().commitText(item.getValue(), 1));
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, true));
|
||||
recyclerView.setAdapter(fieldsAdapter);
|
||||
|
||||
View closeView = popupFieldsView.findViewById(R.id.keyboard_popup_close);
|
||||
closeView.setOnClickListener(v -> popupCustomKeys.dismiss());
|
||||
|
||||
return keyboardView;
|
||||
}
|
||||
|
||||
private void assignKeyboardView() {
|
||||
if (keyboardView != null) {
|
||||
if (entryKey != null) {
|
||||
if (keyboard_entry != null)
|
||||
keyboardView.setKeyboard(keyboard_entry);
|
||||
} else {
|
||||
if (keyboard != null) {
|
||||
dismissCustomKeys();
|
||||
keyboardView.setKeyboard(keyboard);
|
||||
}
|
||||
}
|
||||
|
||||
// Define preferences
|
||||
keyboardView.setHapticFeedbackEnabled(PreferencesUtil.INSTANCE.enableKeyboardVibration(this));
|
||||
playSoundDuringCLick = PreferencesUtil.INSTANCE.enableKeyboardSound(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartInputView(EditorInfo info, boolean restarting) {
|
||||
super.onStartInputView(info, restarting);
|
||||
assignKeyboardView();
|
||||
}
|
||||
|
||||
public static Entry getEntryKey() {
|
||||
return entryKey;
|
||||
}
|
||||
|
||||
public static void setEntryKey(Entry entry) {
|
||||
entryKey = entry;
|
||||
}
|
||||
|
||||
public static void deleteEntryKey(Context context) {
|
||||
Intent lockIntent = new Intent(LOCK_ACTION);
|
||||
context.sendBroadcast(lockIntent);
|
||||
entryKey = null;
|
||||
}
|
||||
|
||||
private void playVibration(int keyCode){
|
||||
switch(keyCode){
|
||||
case Keyboard.KEYCODE_DELETE:
|
||||
break;
|
||||
default: keyboardView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
private void playClick(int keyCode){
|
||||
AudioManager am = (AudioManager)getSystemService(AUDIO_SERVICE);
|
||||
if (am != null)
|
||||
switch(keyCode){
|
||||
case Keyboard.KEYCODE_DONE:
|
||||
case 10:
|
||||
am.playSoundEffect(AudioManager.FX_KEYPRESS_RETURN);
|
||||
break;
|
||||
case Keyboard.KEYCODE_DELETE:
|
||||
am.playSoundEffect(AudioManager.FX_KEYPRESS_DELETE);
|
||||
break;
|
||||
default: am.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onKey(int primaryCode, int[] keyCodes) {
|
||||
InputConnection inputConnection = getCurrentInputConnection();
|
||||
|
||||
// Vibrate
|
||||
playVibration(primaryCode);
|
||||
// Play a sound
|
||||
if (playSoundDuringCLick)
|
||||
playClick(primaryCode);
|
||||
|
||||
switch(primaryCode){
|
||||
case KEY_BACK_KEYBOARD:
|
||||
try {
|
||||
InputMethodManager imeManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
||||
assert imeManager != null;
|
||||
assert getWindow().getWindow() != null;
|
||||
imeManager.switchToLastInputMethod(getWindow().getWindow().getAttributes().token);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Unable to switch to the previous IME", e);
|
||||
InputMethodManager imeManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
||||
if (imeManager != null)
|
||||
imeManager.showInputMethodPicker();
|
||||
}
|
||||
break;
|
||||
case KEY_CHANGE_KEYBOARD:
|
||||
InputMethodManager imeManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
||||
if (imeManager != null)
|
||||
imeManager.showInputMethodPicker();
|
||||
break;
|
||||
case KEY_UNLOCK:
|
||||
// TODO Unlock key
|
||||
break;
|
||||
case KEY_ENTRY:
|
||||
// Stop current service and reinit entry
|
||||
stopService(new Intent(this, KeyboardEntryNotificationService.class));
|
||||
entryKey = null;
|
||||
Intent intent = new Intent(this, KeyboardLauncherActivity.class);
|
||||
// New task needed because don't launch from an Activity context
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
startActivity(intent);
|
||||
break;
|
||||
case KEY_LOCK:
|
||||
deleteEntryKey(this);
|
||||
dismissCustomKeys();
|
||||
break;
|
||||
case KEY_USERNAME:
|
||||
if (entryKey != null) {
|
||||
inputConnection.commitText(entryKey.getUsername(), 1);
|
||||
}
|
||||
break;
|
||||
case KEY_PASSWORD:
|
||||
if (entryKey != null) {
|
||||
inputConnection.commitText(entryKey.getPassword(), 1);
|
||||
}
|
||||
break;
|
||||
case KEY_URL:
|
||||
if (entryKey != null) {
|
||||
inputConnection.commitText(entryKey.getUrl(), 1);
|
||||
}
|
||||
break;
|
||||
case KEY_FIELDS:
|
||||
fieldsAdapter.setFields(entryKey.getCustomFields());
|
||||
popupCustomKeys.showAtLocation(keyboardView, Gravity.END | Gravity.TOP, 0, 0);
|
||||
break;
|
||||
case Keyboard.KEYCODE_DELETE :
|
||||
inputConnection.deleteSurroundingText(1, 0);
|
||||
break;
|
||||
case Keyboard.KEYCODE_DONE:
|
||||
inputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPress(int primaryCode) {
|
||||
AudioManager am = (AudioManager)getSystemService(AUDIO_SERVICE);
|
||||
if (am != null)
|
||||
switch(primaryCode){
|
||||
case Keyboard.KEYCODE_DELETE:
|
||||
keyboardView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRelease(int primaryCode) {
|
||||
switch(primaryCode){
|
||||
case Keyboard.KEYCODE_DELETE:
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onText(CharSequence text) {}
|
||||
|
||||
@Override
|
||||
public void swipeDown() {}
|
||||
|
||||
@Override
|
||||
public void swipeLeft() {}
|
||||
|
||||
@Override
|
||||
public void swipeRight() {}
|
||||
|
||||
@Override
|
||||
public void swipeUp() {}
|
||||
|
||||
private void dismissCustomKeys() {
|
||||
if (popupCustomKeys != null)
|
||||
popupCustomKeys.dismiss();
|
||||
if (fieldsAdapter != null)
|
||||
fieldsAdapter.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
dismissCustomKeys();
|
||||
unregisterReceiver(lockBroadcastReceiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
273
app/src/main/java/com/kunzisoft/keepass/magikeyboard/MagikIME.kt
Normal file
273
app/src/main/java/com/kunzisoft/keepass/magikeyboard/MagikIME.kt
Normal file
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.magikeyboard
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.inputmethodservice.InputMethodService
|
||||
import android.inputmethodservice.Keyboard
|
||||
import android.inputmethodservice.KeyboardView
|
||||
import android.media.AudioManager
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.PopupWindow
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.magikeyboard.adapter.FieldsAdapter
|
||||
import com.kunzisoft.keepass.magikeyboard.receiver.LockBroadcastReceiver
|
||||
import com.kunzisoft.keepass.magikeyboard.receiver.LockBroadcastReceiver.Companion.LOCK_ACTION
|
||||
import com.kunzisoft.keepass.magikeyboard.view.MagikeyboardView
|
||||
import com.kunzisoft.keepass.model.Entry
|
||||
import com.kunzisoft.keepass.model.Field
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
|
||||
class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
|
||||
|
||||
private var keyboardView: KeyboardView? = null
|
||||
private var keyboard: Keyboard? = null
|
||||
private var keyboardEntry: Keyboard? = null
|
||||
private var popupCustomKeys: PopupWindow? = null
|
||||
private var fieldsAdapter: FieldsAdapter? = null
|
||||
private var playSoundDuringCLick: Boolean = false
|
||||
|
||||
private var lockBroadcastReceiver: LockBroadcastReceiver? = null
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
// Remove the entry and lock the keyboard when the lock signal is receive
|
||||
lockBroadcastReceiver = object : LockBroadcastReceiver() {
|
||||
override fun onReceiveLock(context: Context, intent: Intent) {
|
||||
entryKey = null
|
||||
assignKeyboardView()
|
||||
}
|
||||
}
|
||||
registerReceiver(lockBroadcastReceiver, IntentFilter(LOCK_ACTION))
|
||||
}
|
||||
|
||||
override fun onCreateInputView(): View {
|
||||
keyboardView = layoutInflater.inflate(R.layout.keyboard_view, null) as MagikeyboardView
|
||||
|
||||
if (keyboardView != null) {
|
||||
keyboard = Keyboard(this, R.xml.keyboard_password)
|
||||
keyboardEntry = Keyboard(this, R.xml.keyboard_password_entry)
|
||||
|
||||
assignKeyboardView()
|
||||
keyboardView?.setOnKeyboardActionListener(this)
|
||||
keyboardView?.isPreviewEnabled = false
|
||||
|
||||
val context = baseContext
|
||||
val popupFieldsView = LayoutInflater.from(context)
|
||||
.inflate(R.layout.keyboard_popup_fields, FrameLayout(context))
|
||||
|
||||
popupCustomKeys?.dismiss()
|
||||
|
||||
popupCustomKeys = PopupWindow(context)
|
||||
popupCustomKeys?.width = WindowManager.LayoutParams.WRAP_CONTENT
|
||||
popupCustomKeys?.height = WindowManager.LayoutParams.WRAP_CONTENT
|
||||
popupCustomKeys?.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
|
||||
popupCustomKeys?.inputMethodMode = PopupWindow.INPUT_METHOD_NEEDED
|
||||
popupCustomKeys?.contentView = popupFieldsView
|
||||
|
||||
val recyclerView = popupFieldsView.findViewById<RecyclerView>(R.id.keyboard_popup_fields_list)
|
||||
fieldsAdapter = FieldsAdapter(this)
|
||||
fieldsAdapter?.onItemClickListener = object : FieldsAdapter.OnItemClickListener {
|
||||
override fun onItemClick(item: Field) {
|
||||
currentInputConnection.commitText(item.value, 1)
|
||||
}
|
||||
}
|
||||
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, true)
|
||||
recyclerView.adapter = fieldsAdapter
|
||||
|
||||
val closeView = popupFieldsView.findViewById<View>(R.id.keyboard_popup_close)
|
||||
closeView.setOnClickListener { popupCustomKeys?.dismiss() }
|
||||
|
||||
return keyboardView!!
|
||||
}
|
||||
|
||||
return super.onCreateInputView()
|
||||
}
|
||||
|
||||
private fun assignKeyboardView() {
|
||||
if (keyboardView != null) {
|
||||
if (entryKey != null) {
|
||||
if (keyboardEntry != null)
|
||||
keyboardView?.keyboard = keyboardEntry
|
||||
} else {
|
||||
if (keyboard != null) {
|
||||
dismissCustomKeys()
|
||||
keyboardView?.keyboard = keyboard
|
||||
}
|
||||
}
|
||||
|
||||
// Define preferences
|
||||
keyboardView?.isHapticFeedbackEnabled = PreferencesUtil.enableKeyboardVibration(this)
|
||||
playSoundDuringCLick = PreferencesUtil.enableKeyboardSound(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStartInputView(info: EditorInfo, restarting: Boolean) {
|
||||
super.onStartInputView(info, restarting)
|
||||
assignKeyboardView()
|
||||
}
|
||||
|
||||
private fun playVibration(keyCode: Int) {
|
||||
when (keyCode) {
|
||||
Keyboard.KEYCODE_DELETE -> {}
|
||||
else -> keyboardView?.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
|
||||
}
|
||||
}
|
||||
|
||||
private fun playClick(keyCode: Int) {
|
||||
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager?
|
||||
when (keyCode) {
|
||||
Keyboard.KEYCODE_DONE, 10 -> audioManager?.playSoundEffect(AudioManager.FX_KEYPRESS_RETURN)
|
||||
Keyboard.KEYCODE_DELETE -> audioManager?.playSoundEffect(AudioManager.FX_KEYPRESS_DELETE)
|
||||
else -> audioManager?.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onKey(primaryCode: Int, keyCodes: IntArray) {
|
||||
val inputConnection = currentInputConnection
|
||||
|
||||
// Vibrate
|
||||
playVibration(primaryCode)
|
||||
// Play a sound
|
||||
if (playSoundDuringCLick)
|
||||
playClick(primaryCode)
|
||||
|
||||
when (primaryCode) {
|
||||
KEY_BACK_KEYBOARD -> try {
|
||||
val imeManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
if (window.window != null)
|
||||
imeManager.switchToLastInputMethod(window.window!!.attributes.token)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to switch to the previous IME", e)
|
||||
val imeManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imeManager.showInputMethodPicker()
|
||||
}
|
||||
|
||||
KEY_CHANGE_KEYBOARD -> {
|
||||
val imeManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imeManager.showInputMethodPicker()
|
||||
}
|
||||
KEY_UNLOCK -> {
|
||||
}
|
||||
KEY_ENTRY -> {
|
||||
// Stop current service and reinit entry
|
||||
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
|
||||
entryKey = null
|
||||
val intent = Intent(this, KeyboardLauncherActivity::class.java)
|
||||
// New task needed because don't launch from an Activity context
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
startActivity(intent)
|
||||
}
|
||||
KEY_LOCK -> {
|
||||
deleteEntryKey(this)
|
||||
dismissCustomKeys()
|
||||
}
|
||||
KEY_USERNAME -> {
|
||||
if (entryKey != null) {
|
||||
inputConnection.commitText(entryKey!!.username, 1)
|
||||
}
|
||||
}
|
||||
KEY_PASSWORD -> {
|
||||
if (entryKey != null) {
|
||||
inputConnection.commitText(entryKey!!.password, 1)
|
||||
}
|
||||
}
|
||||
KEY_URL -> {
|
||||
if (entryKey != null) {
|
||||
inputConnection.commitText(entryKey!!.url, 1)
|
||||
}
|
||||
}
|
||||
KEY_FIELDS -> {
|
||||
if (entryKey != null) {
|
||||
fieldsAdapter?.fields = entryKey!!.customFields
|
||||
}
|
||||
popupCustomKeys?.showAtLocation(keyboardView, Gravity.END or Gravity.TOP, 0, 0)
|
||||
}
|
||||
Keyboard.KEYCODE_DELETE -> inputConnection.deleteSurroundingText(1, 0)
|
||||
Keyboard.KEYCODE_DONE -> inputConnection.sendKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER))
|
||||
}// TODO Unlock key
|
||||
}
|
||||
|
||||
override fun onPress(primaryCode: Int) {
|
||||
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager?
|
||||
if (audioManager != null)
|
||||
when (primaryCode) {
|
||||
Keyboard.KEYCODE_DELETE -> keyboardView?.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRelease(primaryCode: Int) {
|
||||
when (primaryCode) {
|
||||
Keyboard.KEYCODE_DELETE -> { }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onText(text: CharSequence) {}
|
||||
|
||||
override fun swipeDown() {}
|
||||
|
||||
override fun swipeLeft() {}
|
||||
|
||||
override fun swipeRight() {}
|
||||
|
||||
override fun swipeUp() {}
|
||||
|
||||
private fun dismissCustomKeys() {
|
||||
popupCustomKeys?.dismiss()
|
||||
fieldsAdapter?.clear()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
dismissCustomKeys()
|
||||
unregisterReceiver(lockBroadcastReceiver)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = MagikIME::class.java.name
|
||||
|
||||
const val KEY_BACK_KEYBOARD = 600
|
||||
const val KEY_CHANGE_KEYBOARD = 601
|
||||
private const val KEY_UNLOCK = 610
|
||||
private const val KEY_LOCK = 611
|
||||
private const val KEY_ENTRY = 620
|
||||
private const val KEY_USERNAME = 500
|
||||
private const val KEY_PASSWORD = 510
|
||||
private const val KEY_URL = 520
|
||||
private const val KEY_FIELDS = 530
|
||||
|
||||
var entryKey: Entry? = null
|
||||
|
||||
fun deleteEntryKey(context: Context) {
|
||||
val lockIntent = Intent(LOCK_ACTION)
|
||||
context.sendBroadcast(lockIntent)
|
||||
entryKey = null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package com.kunzisoft.keepass.magikeyboard.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.model.Field;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class FieldsAdapter extends RecyclerView.Adapter<FieldsAdapter.FieldViewHolder> {
|
||||
|
||||
private LayoutInflater inflater;
|
||||
private List<Field> fields;
|
||||
private OnItemClickListener listener;
|
||||
|
||||
public FieldsAdapter(Context context) {
|
||||
this.inflater = LayoutInflater.from(context);
|
||||
this.fields = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void setFields(List<Field> fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
public void setOnItemClickListener(OnItemClickListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public FieldViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = inflater.inflate(R.layout.keyboard_popup_fields_item, parent, false);
|
||||
return new FieldViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull FieldViewHolder holder, int position) {
|
||||
Field field = fields.get(position);
|
||||
holder.name.setText(field.getName());
|
||||
holder.bind(field, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return fields.size();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
fields.clear();
|
||||
}
|
||||
|
||||
public interface OnItemClickListener {
|
||||
void onItemClick(Field item);
|
||||
}
|
||||
|
||||
class FieldViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
TextView name;
|
||||
|
||||
FieldViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
name = itemView.findViewById(R.id.keyboard_popup_field_item_name);
|
||||
}
|
||||
|
||||
void bind(final Field item, final OnItemClickListener listener) {
|
||||
itemView.setOnClickListener(v -> listener.onItemClick(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.kunzisoft.keepass.magikeyboard.adapter
|
||||
|
||||
import android.content.Context
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.model.Field
|
||||
|
||||
import java.util.ArrayList
|
||||
|
||||
class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.FieldViewHolder>() {
|
||||
|
||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||
var fields: MutableList<Field> = ArrayList()
|
||||
var onItemClickListener: OnItemClickListener? = null
|
||||
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FieldViewHolder {
|
||||
val view = inflater.inflate(R.layout.keyboard_popup_fields_item, parent, false)
|
||||
return FieldViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: FieldViewHolder, position: Int) {
|
||||
val field = fields[position]
|
||||
holder.name.text = field.name
|
||||
holder.bind(field, onItemClickListener)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return fields.size
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
fields.clear()
|
||||
}
|
||||
|
||||
interface OnItemClickListener {
|
||||
fun onItemClick(item: Field)
|
||||
}
|
||||
|
||||
inner class FieldViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
|
||||
var name: TextView = itemView.findViewById(R.id.keyboard_popup_field_item_name)
|
||||
|
||||
fun bind(item: Field, listener: OnItemClickListener?) {
|
||||
itemView.setOnClickListener { listener?.onItemClick(item) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package com.kunzisoft.keepass.magikeyboard.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
public abstract class LockBroadcastReceiver extends BroadcastReceiver {
|
||||
|
||||
public static final String LOCK_ACTION = "com.kunzisoft.keepass.LOCK";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action != null
|
||||
&& action.equals(LOCK_ACTION)) {
|
||||
onReceiveLock(context, intent);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void onReceiveLock(Context context, Intent intent);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.kunzisoft.keepass.magikeyboard.receiver
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
|
||||
abstract class LockBroadcastReceiver : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val action = intent.action
|
||||
if (action != null && action == LOCK_ACTION) {
|
||||
onReceiveLock(context, intent)
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun onReceiveLock(context: Context, intent: Intent)
|
||||
|
||||
companion object {
|
||||
|
||||
const val LOCK_ACTION = "com.kunzisoft.keepass.LOCK"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.kunzisoft.keepass.magikeyboard.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.magikeyboard.KeyboardEntryNotificationService;
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikIME;
|
||||
|
||||
public class NotificationDeleteBroadcastReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
// Clear the entry if define in preferences
|
||||
SharedPreferences sharedPreferences = android.preference.PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if (sharedPreferences.getBoolean(context.getString(R.string.keyboard_notification_entry_clear_close_key),
|
||||
context.getResources().getBoolean(R.bool.keyboard_notification_entry_clear_close_default))) {
|
||||
MagikIME.deleteEntryKey(context);
|
||||
}
|
||||
|
||||
// Stop the service in all cases
|
||||
context.stopService(new Intent(context, KeyboardEntryNotificationService.class));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.kunzisoft.keepass.magikeyboard.receiver
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.magikeyboard.KeyboardEntryNotificationService
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
||||
|
||||
class NotificationDeleteBroadcastReceiver : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
// Clear the entry if define in preferences
|
||||
val sharedPreferences = android.preference.PreferenceManager.getDefaultSharedPreferences(context)
|
||||
if (sharedPreferences.getBoolean(context.getString(R.string.keyboard_notification_entry_clear_close_key),
|
||||
context.resources.getBoolean(R.bool.keyboard_notification_entry_clear_close_default))) {
|
||||
MagikIME.deleteEntryKey(context)
|
||||
}
|
||||
|
||||
// Stop the service in all cases
|
||||
context.stopService(Intent(context, KeyboardEntryNotificationService::class.java))
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package com.kunzisoft.keepass.magikeyboard.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.inputmethodservice.Keyboard;
|
||||
import android.inputmethodservice.KeyboardView;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import static com.kunzisoft.keepass.magikeyboard.MagikIME.KEY_BACK_KEYBOARD;
|
||||
import static com.kunzisoft.keepass.magikeyboard.MagikIME.KEY_CHANGE_KEYBOARD;
|
||||
|
||||
public class MagikeyboardView extends KeyboardView {
|
||||
|
||||
public MagikeyboardView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public MagikeyboardView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public MagikeyboardView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onLongPress(Keyboard.Key key) {
|
||||
// TODO Action on long press
|
||||
if (key.codes[0] == KEY_BACK_KEYBOARD) {
|
||||
getOnKeyboardActionListener().onKey(KEY_CHANGE_KEYBOARD, null);
|
||||
return true;
|
||||
} else {
|
||||
//Log.d("LatinKeyboardView", "KEY: " + key.codes[0]);
|
||||
return super.onLongPress(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.kunzisoft.keepass.magikeyboard.view
|
||||
|
||||
import android.content.Context
|
||||
import android.inputmethodservice.Keyboard
|
||||
import android.inputmethodservice.KeyboardView
|
||||
import android.os.Build
|
||||
import android.support.annotation.RequiresApi
|
||||
import android.util.AttributeSet
|
||||
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikIME.Companion.KEY_BACK_KEYBOARD
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikIME.Companion.KEY_CHANGE_KEYBOARD
|
||||
|
||||
class MagikeyboardView : KeyboardView {
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
|
||||
|
||||
override fun onLongPress(key: Keyboard.Key): Boolean {
|
||||
// TODO Action on long press
|
||||
if (key.codes[0] == KEY_BACK_KEYBOARD) {
|
||||
onKeyboardActionListener.onKey(KEY_CHANGE_KEYBOARD, null)
|
||||
return true
|
||||
} else {
|
||||
//Log.d("LatinKeyboardView", "KEY: " + key.codes[0]);
|
||||
return super.onLongPress(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
package com.kunzisoft.keepass.model;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Entry implements Parcelable {
|
||||
|
||||
private String title;
|
||||
private String username;
|
||||
private String password;
|
||||
private String url;
|
||||
private List<Field> customFields;
|
||||
|
||||
public Entry() {
|
||||
this.title = "";
|
||||
this.username = "";
|
||||
this.password = "";
|
||||
this.url = "";
|
||||
this.customFields = new ArrayList<>();
|
||||
}
|
||||
|
||||
protected Entry(Parcel in) {
|
||||
title = in.readString();
|
||||
username = in.readString();
|
||||
password = in.readString();
|
||||
url = in.readString();
|
||||
//noinspection unchecked
|
||||
customFields = in.readArrayList(Field.class.getClassLoader());
|
||||
}
|
||||
|
||||
public static final Creator<Entry> CREATOR = new Creator<Entry>() {
|
||||
@Override
|
||||
public Entry createFromParcel(Parcel in) {
|
||||
return new Entry(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry[] newArray(int size) {
|
||||
return new Entry[size];
|
||||
}
|
||||
};
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public List<Field> getCustomFields() {
|
||||
return customFields;
|
||||
}
|
||||
|
||||
public void addCustomField(Field field) {
|
||||
this.customFields.add(field);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int i) {
|
||||
parcel.writeString(title);
|
||||
parcel.writeString(username);
|
||||
parcel.writeString(password);
|
||||
parcel.writeString(url);
|
||||
parcel.writeArray(customFields.toArray());
|
||||
}
|
||||
}
|
||||
51
app/src/main/java/com/kunzisoft/keepass/model/Entry.kt
Normal file
51
app/src/main/java/com/kunzisoft/keepass/model/Entry.kt
Normal file
@@ -0,0 +1,51 @@
|
||||
package com.kunzisoft.keepass.model
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
import java.util.ArrayList
|
||||
|
||||
class Entry : Parcelable {
|
||||
|
||||
var title: String = ""
|
||||
var username: String = ""
|
||||
var password: String = ""
|
||||
var url: String = ""
|
||||
var customFields: MutableList<Field> = ArrayList()
|
||||
|
||||
constructor()
|
||||
|
||||
private constructor(parcel: Parcel) {
|
||||
title = parcel.readString()
|
||||
username = parcel.readString()
|
||||
password = parcel.readString()
|
||||
url = parcel.readString()
|
||||
parcel.readList(customFields, Field::class.java.classLoader)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, i: Int) {
|
||||
parcel.writeString(title)
|
||||
parcel.writeString(username)
|
||||
parcel.writeString(password)
|
||||
parcel.writeString(url)
|
||||
parcel.writeArray(customFields.toTypedArray())
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<Entry> = object : Parcelable.Creator<Entry> {
|
||||
override fun createFromParcel(parcel: Parcel): Entry {
|
||||
return Entry(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<Entry?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package com.kunzisoft.keepass.model;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
public class Field implements Parcelable {
|
||||
|
||||
private String name;
|
||||
private String value;
|
||||
|
||||
public Field(String name, String value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Field(Parcel in) {
|
||||
this.name = in.readString();
|
||||
this.value = in.readString();
|
||||
}
|
||||
|
||||
public static final Creator<Field> CREATOR = new Creator<Field>() {
|
||||
@Override
|
||||
public Field createFromParcel(Parcel in) {
|
||||
return new Field(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Field[] newArray(int size) {
|
||||
return new Field[size];
|
||||
}
|
||||
};
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(name);
|
||||
dest.writeString(value);
|
||||
}
|
||||
}
|
||||
43
app/src/main/java/com/kunzisoft/keepass/model/Field.kt
Normal file
43
app/src/main/java/com/kunzisoft/keepass/model/Field.kt
Normal file
@@ -0,0 +1,43 @@
|
||||
package com.kunzisoft.keepass.model
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
class Field : Parcelable {
|
||||
|
||||
var name: String? = null
|
||||
var value: String? = null
|
||||
|
||||
constructor(name: String, value: String) {
|
||||
this.name = name
|
||||
this.value = value
|
||||
}
|
||||
|
||||
constructor(parcel: Parcel) {
|
||||
this.name = parcel.readString()
|
||||
this.value = parcel.readString()
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeString(name)
|
||||
dest.writeString(value)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<Field> = object : Parcelable.Creator<Field> {
|
||||
override fun createFromParcel(parcel: Parcel): Field {
|
||||
return Field(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<Field?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user