Added query and open functionality to PluginSDK

First working version of QR Plugin
This commit is contained in:
Philipp Crocoll 2014-05-21 06:44:42 +02:00
parent f0a2f9a038
commit b2baa66b71
9 changed files with 286 additions and 56 deletions

View File

@ -0,0 +1,75 @@
package keepass2android.pluginsdk;
import java.util.ArrayList;
import java.util.HashMap;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Intent;
import android.text.TextUtils;
public class Kp2aControl {
/**
* Creates and returns an intent to launch Keepass2Android for adding an entry with the given fields.
* @param fields Key/Value pairs of the field values. See KeepassDefs for standard keys.
* @param protectedFields List of keys of the protected fields.
* @return Intent to start Keepass2Android.
* @throws JSONException
*/
public static Intent getAddEntryIntent(HashMap<String, String> fields, ArrayList<String> protectedFields)
{
return getAddEntryIntent(new JSONObject(fields).toString(), protectedFields);
}
public static Intent getAddEntryIntent(String outputData, ArrayList<String> protectedFields)
{
Intent startKp2aIntent = new Intent(Strings.ACTION_START_WITH_TASK);
startKp2aIntent.addCategory(Intent.CATEGORY_DEFAULT);
startKp2aIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startKp2aIntent.putExtra("KP2A_APPTASK", "CreateEntryThenCloseTask");
startKp2aIntent.putExtra("ShowUserNotifications", "false"); //KP2A expects a StringExtra
startKp2aIntent.putExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA, outputData);
if (protectedFields != null)
startKp2aIntent.putStringArrayListExtra(Strings.EXTRA_PROTECTED_FIELDS_LIST, protectedFields);
return startKp2aIntent;
}
/**
* Creates an intent to open a Password Entry matching searchText
* @param searchText queryString
* @param showUserNotifications if true, the notifications (copy to clipboard, keyboard) are displayed
* @param closeAfterOpen if true, the entry is opened and KP2A is immediately closed
* @return Intent to start KP2A with
*/
public static Intent getOpenEntryIntent(String searchText, boolean showUserNotifications, boolean closeAfterOpen)
{
Intent startKp2aIntent = new Intent(Strings.ACTION_START_WITH_TASK);
startKp2aIntent.addCategory(Intent.CATEGORY_DEFAULT);
startKp2aIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startKp2aIntent.putExtra("KP2A_APPTASK", "SearchUrlTask");
startKp2aIntent.putExtra("ShowUserNotifications", String.valueOf(showUserNotifications));
startKp2aIntent.putExtra("CloseAfterCreate", String.valueOf(closeAfterOpen));
startKp2aIntent.putExtra("UrlToSearch", searchText);
return startKp2aIntent;
}
/**
* Creates an intent to query a password entry from KP2A. The credentials are returned as Activity result.
* @param searchText Text to search for. Should be a URL or "androidapp://com.my.package."
* @return an Intent to start KP2A with
*/
public static Intent getQueryEntryIntent(String searchText)
{
Intent i = new Intent(Strings.ACTION_QUERY_CREDENTIALS);
if (!TextUtils.isEmpty(searchText))
i.putExtra(Strings.EXTRA_QUERY_STRING, searchText);
return i;
}
}

View File

@ -10,6 +10,16 @@ public class Strings {
*/
public static final String SCOPE_CURRENT_ENTRY = "keepass2android.SCOPE_CURRENT_ENTRY";
/**
* Plugin may query credentials for its own package
*/
public static final String SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE = "keepass2android.SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE";
/**
* Plugin may query credentials for a deliberate package
*/
public static final String SCOPE_QUERY_CREDENTIALS = "keepass2android.SCOPE_QUERY_CREDENTIALS";
/**
* Extra key to transfer a (json serialized) list of scopes
*/
@ -30,6 +40,11 @@ public class Strings {
*/
public static final String EXTRA_REQUEST_TOKEN = "keepass2android.EXTRA_REQUEST_TOKEN";
/**
* Action to start KP2A with an AppTask
*/
public static final String ACTION_START_WITH_TASK = "keepass2android.ACTION_START_WITH_TASK";
/**
* Action sent from KP2A to the plugin to indicate that the plugin should request
* access (sending it's scopes)
@ -125,6 +140,24 @@ public class Strings {
*/
public static final String ACTION_ENTRY_ACTION_SELECTED = "keepass2android.ACTION_ENTRY_ACTION_SELECTED";
/**
* Extra key for the string which is used to query the credentials. This should be either a URL for
* a web login (google.com or a full URI) or something in the form "androidapp://com.my.package"
*/
public static final String EXTRA_QUERY_STRING = "keepass2android.EXTRA_QUERY_STRING";
/**
* Action when plugin wants to query credentials for its own package
*/
public static final String ACTION_QUERY_CREDENTIALS_FOR_OWN_PACKAGE = "keepass2android.ACTION_QUERY_CREDENTIALS_FOR_OWN_PACKAGE";
/**
* Action when plugin wants to query credentials for a deliberate package
* The query string is passed as intent data
*/
public static final String ACTION_QUERY_CREDENTIALS = "keepass2android.ACTION_QUERY_CREDENTIALS";
/**
* Action for an intent from the plugin to KP2A to set (i.e. add or update) a field in the entry.
* May be used to update existing or add new fields at any time while the entry is opened.

View File

@ -53,7 +53,7 @@
<activity
android:name="keepass2android.plugin.qr.MainActivity"
android:label="@string/title_activity_main"
android:label="@string/app_name"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@ -60,7 +60,6 @@ public final class R {
public static final int share_via_barcode=0x7f020003;
}
public static final class id {
public static final int action_settings=0x7f0b0036;
public static final int app_picker_list_item_icon=0x7f0b0008;
public static final int app_picker_list_item_label=0x7f0b0009;
public static final int barcode_image_view=0x7f0b000f;
@ -143,9 +142,8 @@ public final class R {
public static final int beep=0x7f050000;
}
public static final class string {
public static final int action_settings=0x7f070077;
public static final int action_show_qr=0x7f070078;
public static final int all_fields=0x7f07007a;
public static final int action_show_qr=0x7f070077;
public static final int all_fields=0x7f070079;
public static final int app_name=0x7f070000;
public static final int app_picker_name=0x7f070001;
public static final int bookmark_picker_name=0x7f070002;
@ -178,9 +176,9 @@ public final class R {
public static final int contents_phone=0x7f07001d;
public static final int contents_sms=0x7f07001e;
public static final int contents_text=0x7f07001f;
public static final int enable_as_plugin=0x7f070085;
public static final int enabled_as_plugin=0x7f070084;
public static final int hello_world=0x7f07007f;
public static final int create_entry=0x7f070084;
public static final int enable_as_plugin=0x7f070082;
public static final int enabled_as_plugin=0x7f070081;
public static final int history_clear_one_history_text=0x7f070021;
public static final int history_clear_text=0x7f070020;
public static final int history_email_title=0x7f070022;
@ -188,10 +186,10 @@ public final class R {
public static final int history_empty_detail=0x7f070024;
public static final int history_send=0x7f070025;
public static final int history_title=0x7f070026;
public static final int include_label=0x7f070079;
public static final int kp2aplugin_author=0x7f07007d;
public static final int kp2aplugin_shortdesc=0x7f07007c;
public static final int kp2aplugin_title=0x7f07007b;
public static final int include_label=0x7f070078;
public static final int kp2aplugin_author=0x7f07007c;
public static final int kp2aplugin_shortdesc=0x7f07007b;
public static final int kp2aplugin_title=0x7f07007a;
public static final int menu_encode_mecard=0x7f070027;
public static final int menu_encode_vcard=0x7f070028;
public static final int menu_help=0x7f070029;
@ -222,8 +220,8 @@ public final class R {
public static final int msg_share_text=0x7f070042;
public static final int msg_sure=0x7f070043;
public static final int msg_unmount_usb=0x7f070044;
public static final int no_host_app=0x7f070082;
public static final int not_enabled_as_plugin=0x7f070083;
public static final int no_host_app=0x7f07007f;
public static final int not_enabled_as_plugin=0x7f070080;
public static final int preferences_actions_title=0x7f070045;
public static final int preferences_auto_focus_title=0x7f070046;
public static final int preferences_bulk_mode_summary=0x7f070047;
@ -258,6 +256,7 @@ public final class R {
public static final int preferences_try_bsplus=0x7f070074;
public static final int preferences_try_bsplus_summary=0x7f070075;
public static final int preferences_vibrate_title=0x7f070064;
public static final int qr_question=0x7f070083;
public static final int result_address_book=0x7f070065;
public static final int result_calendar=0x7f070066;
public static final int result_email_address=0x7f070067;
@ -270,9 +269,9 @@ public final class R {
public static final int result_uri=0x7f07006e;
public static final int result_wifi=0x7f07006f;
public static final int sbc_name=0x7f070070;
public static final int scan_qr=0x7f070080;
public static final int show_qr=0x7f070081;
public static final int title_activity_main=0x7f07007e;
public static final int scan_qr=0x7f07007d;
public static final int search_entry=0x7f070085;
public static final int show_qr=0x7f07007e;
public static final int title_activity_qr=0x7f070076;
public static final int wifi_changing_network=0x7f070071;
public static final int wifi_ssid_label=0x7f070072;

View File

@ -2,10 +2,4 @@
xmlns:tools="http://schemas.android.com/tools"
tools:context="keepass2android.plugin.qr.MainActivity" >
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:showAsAction="never"
android:title="@string/action_settings"/>
</menu>

View File

@ -1,21 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">QR Plugin for KP2A</string>
<string name="title_activity_qr">QRActivity</string>
<string name="action_settings">Settings</string>
<string name="title_activity_qr">QR Display</string>
<string name="action_show_qr">Show QR Code</string>
<string name="include_label">Include field label</string>
<string name="all_fields">All fields</string>
<string name="kp2aplugin_title">QR Plugin</string>
<string name="kp2aplugin_shortdesc">Displays password entries as QR code</string>
<string name="kp2aplugin_author">Philipp Crocoll</string>
<string name="title_activity_main">MainActivity</string>
<string name="hello_world">Hello world!</string>
<string name="scan_qr">Scan QR code from other device</string>
<string name="show_qr">Select entry to show as QR code</string>
<string name="no_host_app">No host app detected! Please install Keepass2Android from Google Play!</string>
<string name="not_enabled_as_plugin">Not enabled as plugin in host app!</string>
<string name="enabled_as_plugin">Enabled as plugin!</string>
<string name="enable_as_plugin">Enable as plugin</string>
</resources>
<string name="qr_question">Text found on QR code. How do you want to handle this text?</string>
<string name="create_entry">Create new entry</string>
<string name="search_entry">Search and open existing entry</string>
</resources>

View File

@ -11,6 +11,7 @@ public class AccessReceiver extends PluginAccessBroadcastReceiver {
public ArrayList<String> getScopes() {
ArrayList<String> scopes = new ArrayList<String>();
scopes.add(Strings.SCOPE_CURRENT_ENTRY);
scopes.add(Strings.SCOPE_QUERY_CREDENTIALS);
return scopes;
}

View File

@ -1,15 +1,30 @@
package keepass2android.plugin.qr;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import keepass2android.pluginsdk.AccessManager;
import keepass2android.pluginsdk.KeepassDefs;
import keepass2android.pluginsdk.Kp2aControl;
import keepass2android.pluginsdk.Strings;
import com.google.zxing.client.android.CaptureActivity;
import android.app.Activity;
import android.app.ActionBar;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.Fragment;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@ -18,6 +33,7 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import android.os.Build;
public class MainActivity extends Activity {
@ -41,39 +57,134 @@ public class MainActivity extends Activity {
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (requestCode == 0) {
if (resultCode == RESULT_OK) {
String contents = intent.getStringExtra("SCAN_RESULT");
Log.d("QR", contents);
// Toast.makeText(this, contents, Toast.LENGTH_SHORT).show();
} else if (resultCode == RESULT_CANCELED) {
final String contents = intent.getStringExtra("SCAN_RESULT");
if (contents.startsWith("kp2a:"))
{
//received a full entry
String entryText = contents.substring("kp2a:".length());
try
{
JSONObject json = new JSONObject(entryText);
String outputData = json.get("fields").toString();
String protectedFields = null;
if (json.has("p"))
protectedFields = json.get("p").toString();
ArrayList<String> protectedFieldsList = null;
if (!TextUtils.isEmpty(protectedFields))
{
JSONArray protectedFieldsJson = new JSONArray(protectedFields);
protectedFieldsList = new ArrayList<String>();
for (int i=0; i<protectedFieldsJson.length(); i++) {
protectedFieldsList.add( protectedFieldsJson.getString(i) );
}
}
Intent startKp2aIntent = Kp2aControl.getAddEntryIntent(outputData, protectedFieldsList);
startActivity(startKp2aIntent);
}
catch (JSONException e)
{
e.printStackTrace();
Toast.makeText(this, "Error reading entry", Toast.LENGTH_SHORT).show();
}
catch (ActivityNotFoundException e)
{
Toast.makeText(this, getString(R.string.no_host_app), Toast.LENGTH_SHORT).show();
}
}
else
{
//received some text
AlertDialog.Builder b = new Builder(this);
b.setMessage(R.string.qr_question)
.setPositiveButton(R.string.create_entry, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try
{
HashMap<String, String> fields = new HashMap<String, String>();
if ((contents.startsWith("http://")) || (contents.startsWith("https://"))
|| (contents.startsWith("ftp://")))
{
fields.put(KeepassDefs.UrlField, contents);
}
else
{
fields.put(KeepassDefs.PasswordField, contents);
}
Intent startKp2aIntent = Kp2aControl.getAddEntryIntent(fields, null);
startActivity(startKp2aIntent);
}
catch (ActivityNotFoundException e)
{
Toast.makeText(MainActivity.this, R.string.no_host_app, Toast.LENGTH_SHORT).show();
}
}
})
.setNegativeButton(R.string.search_entry, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try
{
Intent startKp2aIntent = Kp2aControl.getOpenEntryIntent(contents, true, false);
startActivity(startKp2aIntent);
}
catch (ActivityNotFoundException e)
{
Toast.makeText(MainActivity.this, R.string.no_host_app, Toast.LENGTH_SHORT).show();
}
}
}).create().show();
}
} else if (resultCode == RESULT_CANCELED) {
// Handle cancel
}
return;
}
if (requestCode == 124) {
if (resultCode == RESULT_OK)
{
if (intent != null)
{
Intent i = new Intent(this, QRActivity.class);
i.putExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA, intent.getStringExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA));
i.putExtra(Strings.EXTRA_PROTECTED_FIELDS_LIST, intent.getStringExtra(Strings.EXTRA_PROTECTED_FIELDS_LIST));
i.putExtra(Strings.EXTRA_SENDER, intent.getStringExtra(Strings.EXTRA_SENDER));
i.putExtra(Strings.EXTRA_ENTRY_ID, intent.getStringExtra(Strings.EXTRA_ENTRY_ID));
startActivity(i);
}
}
else
{
Log.d("QR", "No data received :-(");
}
}
super.onActivityResult(requestCode, resultCode, intent);
}
/**
* A placeholder fragment containing a simple view.
*/
@ -125,7 +236,7 @@ public class MainActivity extends Activity {
@Override
public void onClick(View v) {
//todo
getActivity().startActivityForResult(Kp2aControl.getQueryEntryIntent(null),124);
Log.d("QR", "ShowqR");
}
});

View File

@ -6,6 +6,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@ -64,7 +65,6 @@ public class QRActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if ((getIntent() != null) && (getIntent().getStringExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA)!= null))
Log.d("QR", getIntent().getStringExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA));
setContentView(R.layout.activity_qr);
@ -100,6 +100,12 @@ public class QRActivity extends Activity {
ImageView mImageView;
TextView mErrorView;
HashMap<String, String> mEntryOutput;
//JSON-Array with field keys of the protected strings.
//We don't need that list (so don't deserialize) other than for
//forwarding to KP2A
String mProtectedFieldsList;
ArrayList<String> mFieldList = new ArrayList<String>();
Spinner mSpinner;
String mHostname;
@ -140,6 +146,7 @@ public class QRActivity extends Activity {
mSpinner = (Spinner) rootView.findViewById(R.id.spinner);
mEntryOutput = getEntryFieldsFromIntent(getActivity().getIntent());
mProtectedFieldsList = getProtectedFieldsList(getActivity().getIntent());
ArrayList<String> spinnerItems = new ArrayList<String>();
spinnerItems.add(getActivity().getString(R.string.all_fields));
@ -240,6 +247,12 @@ public class QRActivity extends Activity {
return rootView;
}
private String getProtectedFieldsList(Intent intent) {
if (intent == null)
return null;
return intent.getStringExtra(Strings.EXTRA_PROTECTED_FIELDS_LIST);
}
private void addIfExists(String fieldKey, String resKey,
ArrayList<String> spinnerItems) {
if (!TextUtils.isEmpty(mEntryOutput.get(fieldKey)))
@ -265,21 +278,25 @@ public class QRActivity extends Activity {
if (fieldId == null)
{
res = "kp2a:\n";
for (String k:mFieldList)
{
if (k == null)
continue;
res += QRCodeEncoder.escapeMECARD(k)+":";
res += QRCodeEncoder.escapeMECARD(mEntryOutput.get(k))+";\n";
try {
JSONObject json = new JSONObject();
json.put("fields", new JSONObject(mEntryOutput));
if (!TextUtils.isEmpty(mProtectedFieldsList))
{
json.put("p", new JSONArray(mProtectedFieldsList));
}
res = "kp2a:"+json.toString();
} catch (JSONException e) {
res = "error: " + e.toString();
}
}
else
{
if ((mCbIncludeLabel.isChecked()))
{
res = fieldId+": ";
}
res += mEntryOutput.get(fieldId);
}