package org.julianfamily.keepassgo; import android.app.Activity; import android.content.ClipData; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.provider.OpenableColumns; import android.util.Log; import java.io.File; import java.io.FileOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; public final class SharedVaultImportActivity extends Activity { private static final String TAG = "KeePassGOImport"; private static final String DEFAULT_NAME = "shared-vault.kdbx"; private static final String PENDING_SHARED_VAULT = "pending-shared-vault.kdbx"; private static final String PENDING_SHARED_VAULT_NAME = "pending-shared-vault-name.txt"; private static final String PENDING_SHARED_LOOKUP = "pending-shared-lookup.txt"; @Override protected void onCreate(Bundle state) { super.onCreate(state); handleIntent(getIntent()); launchMainActivity(); finish(); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); setIntent(intent); handleIntent(intent); launchMainActivity(); finish(); } private void handleIntent(Intent intent) { logIntent(intent); String sharedLookup = resolveSharedLookup(intent); if (!sharedLookup.isEmpty()) { try { persistPendingLookup(sharedLookup); Log.i(TAG, "queued shared lookup target"); } catch (IOException | RuntimeException err) { Log.e(TAG, "failed to queue shared lookup target", err); } return; } Uri uri = resolveSharedUri(intent); if (uri == null) { Log.i(TAG, "no shared vault URI on intent"); return; } try { persistPendingImport(uri); Log.i(TAG, "queued shared vault import from " + uri); } catch (IOException | RuntimeException err) { Log.e(TAG, "failed to queue shared vault import", err); } } private Uri resolveSharedUri(Intent intent) { if (intent == null) { return null; } String action = intent.getAction(); if (Intent.ACTION_SEND.equals(action)) { Uri extraStream = intent.getParcelableExtra(Intent.EXTRA_STREAM); if (extraStream != null) { return extraStream; } } if (Intent.ACTION_SEND_MULTIPLE.equals(action)) { ArrayList streams = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); if (streams != null && !streams.isEmpty()) { return streams.get(0); } } if (Intent.ACTION_VIEW.equals(action)) { Uri data = intent.getData(); if (data != null) { return data; } } ClipData clipData = intent.getClipData(); if (clipData != null && clipData.getItemCount() > 0) { Uri clipUri = clipData.getItemAt(0).getUri(); if (clipUri != null) { return clipUri; } } return null; } private String resolveSharedLookup(Intent intent) { if (intent == null) { return ""; } String action = intent.getAction(); if (Intent.ACTION_SEND.equals(action) && "text/plain".equalsIgnoreCase(intent.getType())) { CharSequence extraText = intent.getCharSequenceExtra(Intent.EXTRA_TEXT); if (extraText != null) { return extraText.toString().trim(); } } if (Intent.ACTION_VIEW.equals(action)) { Uri data = intent.getData(); if (data != null) { String scheme = data.getScheme(); if ("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme)) { return data.toString(); } } } return ""; } private void persistPendingImport(Uri uri) throws IOException { File dir = new File(getFilesDir(), "keepassgo"); if (!dir.exists() && !dir.mkdirs()) { throw new IOException("failed to create " + dir.getAbsolutePath()); } File pendingFile = new File(dir, PENDING_SHARED_VAULT); try (InputStream in = openSharedInputStream(uri)) { if (in == null) { throw new IOException("failed to open shared vault stream"); } try (FileOutputStream out = new FileOutputStream(pendingFile, false)) { byte[] buffer = new byte[8192]; int count; while ((count = in.read(buffer)) >= 0) { out.write(buffer, 0, count); } } } File nameFile = new File(dir, PENDING_SHARED_VAULT_NAME); try (FileOutputStream out = new FileOutputStream(nameFile, false)) { out.write(resolveDisplayName(uri).getBytes(StandardCharsets.UTF_8)); } } private void persistPendingLookup(String lookup) throws IOException { File dir = new File(getFilesDir(), "keepassgo"); if (!dir.exists() && !dir.mkdirs()) { throw new IOException("failed to create " + dir.getAbsolutePath()); } File pendingFile = new File(dir, PENDING_SHARED_LOOKUP); try (FileOutputStream out = new FileOutputStream(pendingFile, false)) { out.write(lookup.getBytes(StandardCharsets.UTF_8)); } } private InputStream openSharedInputStream(Uri uri) throws IOException { if ("file".equalsIgnoreCase(uri.getScheme())) { String path = uri.getPath(); if (path == null || path.trim().isEmpty()) { throw new IOException("file URI is missing a path"); } return new FileInputStream(new File(path)); } return getContentResolver().openInputStream(uri); } private String resolveDisplayName(Uri uri) { String displayName = queryDisplayName(uri); if (!displayName.isEmpty()) { return displayName; } String lastSegment = uri.getLastPathSegment(); if (lastSegment != null && !lastSegment.trim().isEmpty()) { return lastSegment.trim(); } return DEFAULT_NAME; } private String queryDisplayName(Uri uri) { Cursor cursor = null; try { cursor = getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null); if (cursor != null && cursor.moveToFirst()) { int index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); if (index >= 0) { String value = cursor.getString(index); if (value != null) { return value.trim(); } } } } catch (RuntimeException err) { Log.w(TAG, "failed to query display name", err); } finally { if (cursor != null) { cursor.close(); } } return ""; } private void logIntent(Intent intent) { if (intent == null) { return; } Log.i(TAG, "intent action=" + intent.getAction() + " type=" + intent.getType() + " data=" + intent.getData() + " flags=0x" + Integer.toHexString(intent.getFlags())); ClipData clipData = intent.getClipData(); if (clipData != null) { Log.i(TAG, "intent clip items=" + clipData.getItemCount()); } } private void launchMainActivity() { Intent launch = new Intent(); launch.setClassName(this, "org.gioui.GioActivity"); startActivity(launch); } }