mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Better autofill data transfer between each activity,
New Autofill helper, Add consultation mode
This commit is contained in:
@@ -68,7 +68,6 @@ def supportVersion = "26.1.0"
|
||||
def spongycastleVersion = "1.58.0.0"
|
||||
|
||||
dependencies {
|
||||
androidTestImplementation files('libs/junit4.jar')
|
||||
implementation "com.android.support:appcompat-v7:$supportVersion"
|
||||
implementation "com.android.support:design:$supportVersion"
|
||||
implementation "com.android.support:preference-v7:$supportVersion"
|
||||
|
||||
@@ -24,13 +24,12 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.RequiresApi;
|
||||
|
||||
import com.keepassdroid.fileselect.FileSelectActivity;
|
||||
import com.kunzisoft.keepass.KeePass;
|
||||
|
||||
import static com.keepassdroid.PasswordActivity.KEY_AUTOFILL_RESPONSE;
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public class AutoFillAuthActivity extends KeePass {
|
||||
|
||||
@@ -41,8 +40,10 @@ public class AutoFillAuthActivity extends KeePass {
|
||||
}
|
||||
|
||||
protected void startFileSelectActivity() {
|
||||
Intent intent = new Intent(this, FileSelectActivity.class);
|
||||
intent.putExtra(KEY_AUTOFILL_RESPONSE, true);
|
||||
startActivityForResult(intent, 0);
|
||||
// Pass extra for Autofill (EXTRA_ASSIST_STRUCTURE)
|
||||
Bundle extras = null;
|
||||
if (getIntent() != null && getIntent().getExtras() != null)
|
||||
extras = getIntent().getExtras();
|
||||
FileSelectActivity.launch(this, extras);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
@@ -34,9 +35,8 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
|
||||
import com.kunzisoft.keepass.KeePass;
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.autofill.AutofillHelper;
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.PwDatabaseV3;
|
||||
import com.keepassdroid.database.PwDatabaseV4;
|
||||
@@ -50,6 +50,8 @@ import com.keepassdroid.view.ClickView;
|
||||
import com.keepassdroid.view.GroupAddEntryView;
|
||||
import com.keepassdroid.view.GroupRootView;
|
||||
import com.keepassdroid.view.GroupViewOnlyView;
|
||||
import com.kunzisoft.keepass.KeePass;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
public abstract class GroupActivity extends GroupBaseActivity
|
||||
implements GroupEditFragment.CreateGroupListener, IconPickerFragment.IconPickerListener {
|
||||
@@ -64,35 +66,44 @@ public abstract class GroupActivity extends GroupBaseActivity
|
||||
private static final String TAG = "Group Activity:";
|
||||
|
||||
public static void Launch(Activity act) {
|
||||
Launch(act, null);
|
||||
Launch(act, null, null);
|
||||
}
|
||||
|
||||
public static void Launch(Activity act, Bundle extras) {
|
||||
Launch(act, null, extras);
|
||||
}
|
||||
|
||||
public static void Launch(Activity act, PwGroup group) {
|
||||
Intent i;
|
||||
Launch(act, group, null);
|
||||
}
|
||||
|
||||
public static void Launch(Activity act, PwGroup group, Bundle extras) {
|
||||
Intent intent;
|
||||
|
||||
// Need to use PwDatabase since tree may be null
|
||||
PwDatabase db = App.getDB().pm;
|
||||
if ( db instanceof PwDatabaseV3 ) {
|
||||
i = new Intent(act, GroupActivityV3.class);
|
||||
intent = new Intent(act, GroupActivityV3.class);
|
||||
|
||||
if ( group != null ) {
|
||||
PwGroupV3 g = (PwGroupV3) group;
|
||||
i.putExtra(KEY_ENTRY, g.groupId);
|
||||
intent.putExtra(KEY_ENTRY, g.groupId);
|
||||
}
|
||||
} else if ( db instanceof PwDatabaseV4 ) {
|
||||
i = new Intent(act, GroupActivityV4.class);
|
||||
intent = new Intent(act, GroupActivityV4.class);
|
||||
|
||||
if ( group != null ) {
|
||||
PwGroupV4 g = (PwGroupV4) group;
|
||||
i.putExtra(KEY_ENTRY, g.uuid.toString());
|
||||
intent.putExtra(KEY_ENTRY, g.uuid.toString());
|
||||
}
|
||||
} else {
|
||||
// Reached if db is null
|
||||
Log.d(TAG, "Tried to launch with null db");
|
||||
return;
|
||||
}
|
||||
|
||||
act.startActivityForResult(i,0);
|
||||
if (extras != null)
|
||||
intent.putExtras(extras);
|
||||
act.startActivityForResult(intent,0);
|
||||
}
|
||||
|
||||
protected abstract PwGroupId retrieveGroupId(Intent i);
|
||||
@@ -117,7 +128,10 @@ public abstract class GroupActivity extends GroupBaseActivity
|
||||
PwGroupId id = retrieveGroupId(intent);
|
||||
|
||||
Database db = App.getDB();
|
||||
readOnly = db.readOnly;
|
||||
|
||||
// Force readonly if it's autofill
|
||||
readOnly = AutofillHelper.isIntentContainsAutofillAuthKey(getIntent()) || db.readOnly;
|
||||
|
||||
PwGroup root = db.pm.rootGroup;
|
||||
if (id == null) {
|
||||
mGroup = root;
|
||||
@@ -139,6 +153,8 @@ public abstract class GroupActivity extends GroupBaseActivity
|
||||
setContentView(new GroupAddEntryView(this));
|
||||
} else if (addGroupEnabled) {
|
||||
setContentView(new GroupRootView(this));
|
||||
View addEntry = findViewById(R.id.add_entry);
|
||||
addEntry.setVisibility(View.GONE);
|
||||
} else if (addEntryEnabled) {
|
||||
setContentView(new GroupAddEntryView(this));
|
||||
View addGroup = findViewById(R.id.add_group);
|
||||
|
||||
@@ -20,12 +20,9 @@
|
||||
package com.keepassdroid;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
@@ -38,7 +35,6 @@ import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@@ -77,7 +73,6 @@ public class PasswordActivity extends LockingActivity
|
||||
|
||||
public static final String KEY_DEFAULT_FILENAME = "defaultFileName";
|
||||
private static final String KEY_PASSWORD = "password";
|
||||
public static final String KEY_AUTOFILL_RESPONSE = "KEY_AUTOFILL_RESPONSE";
|
||||
private static final String KEY_LAUNCH_IMMEDIATELY = "launchImmediately";
|
||||
|
||||
private Uri mDbUri = null;
|
||||
@@ -107,20 +102,24 @@ public class PasswordActivity extends LockingActivity
|
||||
|
||||
private KeyFileHelper keyFileHelper;
|
||||
|
||||
private Intent mReplyIntent;
|
||||
public static void Launch(
|
||||
Activity act,
|
||||
String fileName) throws FileNotFoundException {
|
||||
Launch(act, fileName, "", null);
|
||||
}
|
||||
|
||||
public static void Launch(
|
||||
Activity act,
|
||||
String fileName,
|
||||
boolean autoFillResponse) throws FileNotFoundException {
|
||||
Launch(act, fileName, "", autoFillResponse);
|
||||
Bundle extra) throws FileNotFoundException {
|
||||
Launch(act, fileName, "", extra);
|
||||
}
|
||||
|
||||
public static void Launch(
|
||||
Activity act,
|
||||
String fileName,
|
||||
String keyFile,
|
||||
boolean autoFillResponse) throws FileNotFoundException {
|
||||
Bundle extras) throws FileNotFoundException {
|
||||
if (EmptyUtils.isNullOrEmpty(fileName)) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
@@ -137,15 +136,12 @@ public class PasswordActivity extends LockingActivity
|
||||
}
|
||||
|
||||
Intent i = new Intent(act, PasswordActivity.class);
|
||||
i.putExtra(KEY_AUTOFILL_RESPONSE, autoFillResponse);
|
||||
i.putExtra(UriIntentInitTask.KEY_FILENAME, fileName);
|
||||
i.putExtra(UriIntentInitTask.KEY_KEYFILE, keyFile);
|
||||
if (extras != null)
|
||||
i.putExtras(extras);
|
||||
|
||||
act.startActivityForResult(i, 0);
|
||||
|
||||
if(autoFillResponse)
|
||||
act.finish();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -683,15 +679,7 @@ public class PasswordActivity extends LockingActivity
|
||||
App.clearShutdown();
|
||||
|
||||
Handler handler = new Handler();
|
||||
AfterLoad afterLoad;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
||||
&& getIntent().getExtras() != null
|
||||
&& getIntent().getExtras().containsKey(KEY_AUTOFILL_RESPONSE)
|
||||
&& getIntent().getBooleanExtra(KEY_AUTOFILL_RESPONSE, false)) {
|
||||
afterLoad = new AfterLoadAutofill(handler, db);
|
||||
} else {
|
||||
afterLoad = new AfterLoad(handler, db);
|
||||
}
|
||||
AfterLoad afterLoad = new AfterLoad(handler, db);
|
||||
|
||||
LoadDB task = new LoadDB(db, PasswordActivity.this, mDbUri, pass, keyfile, afterLoad);
|
||||
ProgressTask pt = new ProgressTask(PasswordActivity.this, task, R.string.loading_database);
|
||||
@@ -742,36 +730,18 @@ public class PasswordActivity extends LockingActivity
|
||||
public void onClick(
|
||||
DialogInterface dialog,
|
||||
int which) {
|
||||
GroupActivity.Launch(PasswordActivity.this);
|
||||
GroupActivity.Launch(PasswordActivity.this, getIntent().getExtras());
|
||||
}
|
||||
|
||||
});
|
||||
} else if (mSuccess) {
|
||||
GroupActivity.Launch(PasswordActivity.this);
|
||||
GroupActivity.Launch(PasswordActivity.this, getIntent().getExtras());
|
||||
} else {
|
||||
displayMessage(PasswordActivity.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private class AfterLoadAutofill extends AfterLoad {
|
||||
|
||||
public AfterLoadAutofill(Handler handler, Database db) {
|
||||
super(handler, db);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (mSuccess) {
|
||||
onAutofillResponseSuccess();
|
||||
} else {
|
||||
onAutofillResponseFailure();
|
||||
}
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private static class UriIntentInitTask extends AsyncTask<Intent, Void, Integer> {
|
||||
|
||||
static final String KEY_FILENAME = "fileName";
|
||||
@@ -848,51 +818,4 @@ public class PasswordActivity extends LockingActivity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public static IntentSender getAuthIntentSenderForResponse(Context context) {
|
||||
final Intent intent = new Intent(context, AutoFillAuthActivity.class);
|
||||
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)
|
||||
.getIntentSender();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
if (mReplyIntent != null) {
|
||||
setResult(RESULT_OK, mReplyIntent);
|
||||
} else {
|
||||
setResult(RESULT_CANCELED);
|
||||
}
|
||||
super.finish();
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private void onAutofillResponseFailure() {
|
||||
Log.w(getClass().getName(), "Failed Autofill auth.");
|
||||
mReplyIntent = null;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private void onAutofillResponseSuccess() {
|
||||
/*
|
||||
Intent intent = getIntent();
|
||||
Bundle clientState = intent.getBundleExtra(AutofillManager.EXTRA_CLIENT_STATE);
|
||||
AssistStructure structure = intent.getParcelableExtra(EXTRA_ASSIST_STRUCTURE);
|
||||
StructureParser parser = new StructureParser(getApplicationContext(), structure);
|
||||
parser.parseForFill();
|
||||
AutofillFieldMetadataCollection autofillFields = parser.getAutofillFields();
|
||||
*/
|
||||
mReplyIntent = new Intent();
|
||||
/*
|
||||
HashMap<String, FilledAutofillFieldCollection> clientFormDataMap =
|
||||
SharedPrefsAutofillRepository.getInstance().getFilledAutofillFieldCollection
|
||||
(this, autofillFields.getFocusedHints(), autofillFields.getAllHints());
|
||||
|
||||
// TODO Add success results
|
||||
mReplyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, AutofillHelper.newResponse
|
||||
(this, clientState, false, autofillFields, clientFormDataMap));
|
||||
*/
|
||||
mReplyIntent.putExtra(android.view.autofill.AutofillManager.EXTRA_AUTHENTICATION_RESULT, "");
|
||||
}
|
||||
}
|
||||
|
||||
173
app/src/main/java/com/keepassdroid/autofill/AutofillHelper.java
Normal file
173
app/src/main/java/com/keepassdroid/autofill/AutofillHelper.java
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, 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 2 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.keepassdroid.autofill;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.assist.AssistStructure;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.service.autofill.Dataset;
|
||||
import android.service.autofill.FillResponse;
|
||||
import android.service.autofill.SaveInfo;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.util.Log;
|
||||
import android.view.autofill.AutofillId;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import com.keepassdroid.autofill.dataSource.SharedPrefsAutofillRepository;
|
||||
import com.keepassdroid.model.FilledAutofillFieldCollection;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
public class AutofillHelper {
|
||||
|
||||
private static final int AUTOFILL_RESPONSE_REQUEST_CODE = 81653;
|
||||
|
||||
/**
|
||||
* Define if EXTRA_AUTHENTICATION_RESULT is an extra bundle key present in the Intent
|
||||
*/
|
||||
public static boolean isIntentContainsAutofillAuthKey(Intent intent) {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
||||
&& (intent != null
|
||||
&& intent.getExtras() != null
|
||||
&& intent.getExtras().containsKey(android.view.autofill.AutofillManager.EXTRA_ASSIST_STRUCTURE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to hit when right key is selected
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private static void onAutofillResponse(Activity activity) {
|
||||
// TODO Connect this method in each item in GroupActivity
|
||||
Intent mReplyIntent = null;
|
||||
Intent intent = activity.getIntent();
|
||||
if (isIntentContainsAutofillAuthKey(intent)) {
|
||||
AssistStructure structure = intent.getParcelableExtra(
|
||||
android.view.autofill.AutofillManager.EXTRA_ASSIST_STRUCTURE);
|
||||
StructureParser parser = new StructureParser(activity, structure);
|
||||
parser.parseForFill();
|
||||
AutofillFieldMetadataCollection autofillFields = parser.getAutofillFields();
|
||||
mReplyIntent = new Intent();
|
||||
HashMap<String, FilledAutofillFieldCollection> clientFormDataMap =
|
||||
SharedPrefsAutofillRepository.getInstance().getFilledAutofillFieldCollection
|
||||
(activity, autofillFields.getFocusedHints(), autofillFields.getAllHints());
|
||||
|
||||
Log.d(activity.getClass().getName(), "Successed Autofill auth.");
|
||||
mReplyIntent.putExtra(
|
||||
android.view.autofill.AutofillManager.EXTRA_AUTHENTICATION_RESULT,
|
||||
AutofillHelper.newResponse
|
||||
(activity, autofillFields, clientFormDataMap));
|
||||
activity.setResult(Activity.RESULT_OK, mReplyIntent);
|
||||
} else {
|
||||
Log.w(activity.getClass().getName(), "Failed Autofill auth.");
|
||||
activity.setResult(Activity.RESULT_CANCELED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to loop and close each activity with return data
|
||||
*/
|
||||
public static void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == AUTOFILL_RESPONSE_REQUEST_CODE) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
if (data != null) {
|
||||
activity.setResult(Activity.RESULT_OK, data);
|
||||
} else {
|
||||
activity.setResult(Activity.RESULT_CANCELED);
|
||||
}
|
||||
} else
|
||||
activity.setResult(Activity.RESULT_CANCELED);
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps autofill data in a LoginCredential Dataset object which can then be sent back to the
|
||||
* client View.
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public static Dataset newDataset(Context context,
|
||||
AutofillFieldMetadataCollection autofillFields,
|
||||
FilledAutofillFieldCollection filledAutofillFieldCollection) {
|
||||
String datasetName = filledAutofillFieldCollection.getDatasetName();
|
||||
if (datasetName != null) {
|
||||
Dataset.Builder datasetBuilder;
|
||||
datasetBuilder = new Dataset.Builder
|
||||
(newRemoteViews(context.getPackageName(), datasetName));
|
||||
boolean setValueAtLeastOnce =
|
||||
filledAutofillFieldCollection.applyToFields(autofillFields, datasetBuilder);
|
||||
if (setValueAtLeastOnce) {
|
||||
return datasetBuilder.build();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public static RemoteViews newRemoteViews(String packageName, String remoteViewsText) {
|
||||
RemoteViews presentation =
|
||||
new RemoteViews(packageName, R.layout.autofill_service_list_item);
|
||||
presentation.setTextViewText(R.id.text, remoteViewsText);
|
||||
return presentation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps autofill data in a Response object (essentially a series of Datasets) which can then
|
||||
* be sent back to the client View.
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public static FillResponse newResponse(Context context,
|
||||
AutofillFieldMetadataCollection autofillFields,
|
||||
HashMap<String, FilledAutofillFieldCollection> clientFormDataMap) {
|
||||
FillResponse.Builder responseBuilder = new FillResponse.Builder();
|
||||
if (clientFormDataMap != null) {
|
||||
Set<String> datasetNames = clientFormDataMap.keySet();
|
||||
for (String datasetName : datasetNames) {
|
||||
FilledAutofillFieldCollection filledAutofillFieldCollection =
|
||||
clientFormDataMap.get(datasetName);
|
||||
if (filledAutofillFieldCollection != null) {
|
||||
Dataset dataset = newDataset(context, autofillFields,
|
||||
filledAutofillFieldCollection);
|
||||
if (dataset != null) {
|
||||
responseBuilder.addDataset(dataset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
int saveType = autofillFields.getSaveType();
|
||||
if (saveType != 0) {
|
||||
setFullSaveInfo(responseBuilder, saveType, autofillFields);
|
||||
return responseBuilder.build();
|
||||
} else {
|
||||
Log.d(AutofillHelper.class.getName(), "These fields are not meant to be saved by autofill.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private static void setFullSaveInfo(FillResponse.Builder responseBuilder, int saveType,
|
||||
AutofillFieldMetadataCollection autofillFields) {
|
||||
AutofillId[] autofillIds = autofillFields.getAutofillIds();
|
||||
responseBuilder.setSaveInfo(new SaveInfo.Builder(saveType, autofillIds).build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.keepassdroid.autofill.dataSource;
|
||||
|
||||
import android.content.Context;
|
||||
import com.keepassdroid.model.FilledAutofillFieldCollection;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public interface AutofillDataSource {
|
||||
|
||||
/**
|
||||
* Gets saved FilledAutofillFieldCollection that contains some objects that can autofill fields
|
||||
* with these {@code autofillHints}.
|
||||
*/
|
||||
HashMap<String, FilledAutofillFieldCollection> getFilledAutofillFieldCollection(Context context,
|
||||
List<String> focusedAutofillHints, List<String> allAutofillHints);
|
||||
|
||||
/**
|
||||
* Stores a collection of Autofill fields.
|
||||
*/
|
||||
void saveFilledAutofillFieldCollection(Context context,
|
||||
FilledAutofillFieldCollection filledAutofillFieldCollection);
|
||||
|
||||
/**
|
||||
* Clears all data.
|
||||
*/
|
||||
void clear(Context context);
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.keepassdroid.autofill.dataSource;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.util.ArraySet;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.keepassdroid.model.FilledAutofillFieldCollection;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Singleton autofill data repository that stores autofill fields to SharedPreferences.
|
||||
* <p>
|
||||
* <p><b>Disclaimer</b>: you should not store sensitive fields like user data unencrypted.
|
||||
* This is done here only for simplicity and learning purposes.
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public class SharedPrefsAutofillRepository implements AutofillDataSource {
|
||||
private static final String SHARED_PREF_KEY = "com.example.android.autofill"
|
||||
+ ".service.datasource.AutofillDataSource";
|
||||
private static final String CLIENT_FORM_DATA_KEY = "loginCredentialDatasets";
|
||||
private static final String DATASET_NUMBER_KEY = "datasetNumber";
|
||||
private static SharedPrefsAutofillRepository sInstance;
|
||||
|
||||
private SharedPrefsAutofillRepository() {
|
||||
}
|
||||
|
||||
public static SharedPrefsAutofillRepository getInstance() {
|
||||
if (sInstance == null) {
|
||||
sInstance = new SharedPrefsAutofillRepository();
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<String, FilledAutofillFieldCollection> getFilledAutofillFieldCollection(
|
||||
Context context, List<String> focusedAutofillHints, List<String> allAutofillHints) {
|
||||
boolean hasDataForFocusedAutofillHints = false;
|
||||
HashMap<String, FilledAutofillFieldCollection> clientFormDataMap = new HashMap<>();
|
||||
Set<String> clientFormDataStringSet = getAllAutofillDataStringSet(context);
|
||||
for (String clientFormDataString : clientFormDataStringSet) {
|
||||
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
|
||||
FilledAutofillFieldCollection filledAutofillFieldCollection =
|
||||
gson.fromJson(clientFormDataString, FilledAutofillFieldCollection.class);
|
||||
if (filledAutofillFieldCollection != null) {
|
||||
if (filledAutofillFieldCollection.helpsWithHints(focusedAutofillHints)) {
|
||||
// Saved data has data relevant to at least 1 of the hints associated with the
|
||||
// View in focus.
|
||||
hasDataForFocusedAutofillHints = true;
|
||||
}
|
||||
if (filledAutofillFieldCollection.helpsWithHints(allAutofillHints)) {
|
||||
// Saved data has data relevant to at least 1 of these hints associated with any
|
||||
// of the Views in the hierarchy.
|
||||
clientFormDataMap.put(filledAutofillFieldCollection.getDatasetName(),
|
||||
filledAutofillFieldCollection);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasDataForFocusedAutofillHints) {
|
||||
return clientFormDataMap;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveFilledAutofillFieldCollection(Context context,
|
||||
FilledAutofillFieldCollection filledAutofillFieldCollection) {
|
||||
String datasetName = "dataset-" + getDatasetNumber(context);
|
||||
filledAutofillFieldCollection.setDatasetName(datasetName);
|
||||
Set<String> allAutofillData = getAllAutofillDataStringSet(context);
|
||||
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
|
||||
allAutofillData.add(gson.toJson(filledAutofillFieldCollection));
|
||||
saveAllAutofillDataStringSet(context, allAutofillData);
|
||||
incrementDatasetNumber(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(Context context) {
|
||||
context.getApplicationContext()
|
||||
.getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.remove(CLIENT_FORM_DATA_KEY)
|
||||
.remove(DATASET_NUMBER_KEY)
|
||||
.apply();
|
||||
}
|
||||
|
||||
private Set<String> getAllAutofillDataStringSet(Context context) {
|
||||
return context.getApplicationContext()
|
||||
.getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE)
|
||||
.getStringSet(CLIENT_FORM_DATA_KEY, new ArraySet<String>());
|
||||
}
|
||||
|
||||
private void saveAllAutofillDataStringSet(Context context,
|
||||
Set<String> allAutofillDataStringSet) {
|
||||
context.getApplicationContext()
|
||||
.getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.putStringSet(CLIENT_FORM_DATA_KEY, allAutofillDataStringSet)
|
||||
.apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* For simplicity, datasets will be named in the form "dataset-X" where X means
|
||||
* this was the Xth dataset saved.
|
||||
*/
|
||||
private int getDatasetNumber(Context context) {
|
||||
return context.getApplicationContext()
|
||||
.getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE)
|
||||
.getInt(DATASET_NUMBER_KEY, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Every time a dataset is saved, this should be called to increment the dataset number.
|
||||
* (only important for this service's dataset naming scheme).
|
||||
*/
|
||||
private void incrementDatasetNumber(Context context) {
|
||||
context.getApplicationContext()
|
||||
.getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.putInt(DATASET_NUMBER_KEY, getDatasetNumber(context) + 1)
|
||||
.apply();
|
||||
}
|
||||
}
|
||||
@@ -20,12 +20,14 @@
|
||||
package com.keepassdroid.fileselect;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.preference.PreferenceManager;
|
||||
@@ -47,6 +49,7 @@ import com.keepassdroid.GroupActivity;
|
||||
import com.keepassdroid.PasswordActivity;
|
||||
import com.keepassdroid.ProgressTask;
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.autofill.AutofillHelper;
|
||||
import com.keepassdroid.compat.ContentResolverCompat;
|
||||
import com.keepassdroid.compat.StorageAF;
|
||||
import com.keepassdroid.database.edit.CreateDB;
|
||||
@@ -88,21 +91,30 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
private RecentFileHistory fileHistory;
|
||||
|
||||
private boolean recentMode = false;
|
||||
private boolean autofillResponse = false;
|
||||
private boolean consultationMode = false;
|
||||
|
||||
private EditText openFileNameView;
|
||||
|
||||
private AssignPasswordHelper assignPasswordHelper;
|
||||
private Uri databaseUri;
|
||||
|
||||
public static void launch(Activity act) {
|
||||
launch(act, null);
|
||||
}
|
||||
|
||||
public static void launch(Activity act, Bundle extra) {
|
||||
Intent intent = new Intent(act, FileSelectActivity.class);
|
||||
if (extra != null)
|
||||
intent.putExtras(extra);
|
||||
act.startActivityForResult(intent, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if(getIntent().getExtras() != null) {
|
||||
autofillResponse = getIntent().getExtras().
|
||||
getBoolean(PasswordActivity.KEY_AUTOFILL_RESPONSE, autofillResponse);
|
||||
}
|
||||
if (AutofillHelper.isIntentContainsAutofillAuthKey(getIntent()))
|
||||
consultationMode = true;
|
||||
|
||||
fileHistory = App.getFileHistory();
|
||||
|
||||
@@ -127,7 +139,9 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
String fileName = Util.getEditText(FileSelectActivity.this,
|
||||
R.id.file_filename);
|
||||
try {
|
||||
PasswordActivity.Launch(FileSelectActivity.this, fileName, autofillResponse);
|
||||
PasswordActivity.Launch(FileSelectActivity.this,
|
||||
fileName,
|
||||
getIntent().getExtras());
|
||||
}
|
||||
catch (ContentFileNotFoundException e) {
|
||||
Toast.makeText(FileSelectActivity.this,
|
||||
@@ -142,12 +156,16 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
|
||||
// Create button
|
||||
View createButton = findViewById(R.id.create_database);
|
||||
if (!consultationMode) {
|
||||
createButton.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
CreateFileDialog createFileDialog = new CreateFileDialog();
|
||||
createFileDialog.show(getSupportFragmentManager(), "createFileDialog");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
createButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
View browseButton = findViewById(R.id.browse_button);
|
||||
browseButton.setOnClickListener(new View.OnClickListener() {
|
||||
@@ -224,7 +242,9 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
|
||||
if (db.exists()) {
|
||||
try {
|
||||
PasswordActivity.Launch(FileSelectActivity.this, path, autofillResponse);
|
||||
PasswordActivity.Launch(FileSelectActivity.this,
|
||||
path,
|
||||
getIntent().getExtras());
|
||||
} catch (Exception e) {
|
||||
// Ignore exception
|
||||
}
|
||||
@@ -232,7 +252,9 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
}
|
||||
else {
|
||||
try {
|
||||
PasswordActivity.Launch(FileSelectActivity.this, dbUri.toString(), autofillResponse);
|
||||
PasswordActivity.Launch(FileSelectActivity.this,
|
||||
dbUri.toString(),
|
||||
getIntent().getExtras());
|
||||
} catch (Exception e) {
|
||||
// Ignore exception
|
||||
}
|
||||
@@ -402,7 +424,7 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
public void afterOpenFile(String fileName, String keyFile) {
|
||||
try {
|
||||
PasswordActivity.Launch(FileSelectActivity.this,
|
||||
fileName, keyFile, autofillResponse);
|
||||
fileName, keyFile, getIntent().getExtras());
|
||||
} catch (ContentFileNotFoundException e) {
|
||||
Toast.makeText(FileSelectActivity.this,
|
||||
R.string.file_not_found_content, Toast.LENGTH_LONG)
|
||||
|
||||
@@ -44,8 +44,7 @@ public class KeePass extends Activity {
|
||||
}
|
||||
|
||||
protected void startFileSelectActivity() {
|
||||
Intent intent = new Intent(this, FileSelectActivity.class);
|
||||
startActivityForResult(intent, 0);
|
||||
FileSelectActivity.launch(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
44
app/src/main/res/layout/autofill_service_list_item.xml
Normal file
44
app/src/main/res/layout/autofill_service_list_item.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/white"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:src="@drawable/ic_key_white_24dp"
|
||||
android:tint="@color/colorTextPrimary"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingRight="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingStart="12dp"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSmall" />
|
||||
</LinearLayout>
|
||||
Reference in New Issue
Block a user