diff options
author | Douglas Sigelbaum <sigelbaum@google.com> | 2017-05-12 20:39:49 -0700 |
---|---|---|
committer | Douglas Sigelbaum <sigelbaum@google.com> | 2017-05-16 18:06:57 -0700 |
commit | 956f35b47d5bb8fb4686bda5b44bc5371361001f (patch) | |
tree | 0d6d7d16166625ae0a2dfea8675aac61f8c32ecb | |
parent | 5525a586f56baeb2310dbacfe34a96afd3dd3f0f (diff) | |
download | android-956f35b47d5bb8fb4686bda5b44bc5371361001f.tar.gz |
Implemented autofill saving.
Also added basic support for autofilling a client's CC info.
Refactored a bit to make this possible.
Test: manual
Bug: 38182790
Change-Id: I7c70ea6a962bf816a7c527012b3fc6918cdbde91
14 files changed, 420 insertions, 157 deletions
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/CustomVirtualView.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/CustomVirtualView.java index 6b01039e..61197efe 100644 --- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/CustomVirtualView.java +++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/CustomVirtualView.java @@ -43,7 +43,6 @@ import static com.example.android.autofillframework.CommonUtil.bundleToString; /** * Custom View with virtual child views for Username/Password text fields. */ - public class CustomVirtualView extends View { private static final String TAG = "CustomView"; diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/LoginActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/LoginActivity.java index cb7192eb..051572a4 100644 --- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/LoginActivity.java +++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/LoginActivity.java @@ -20,7 +20,6 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.view.View; -import android.view.autofill.AutofillManager; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; @@ -49,10 +48,6 @@ public class LoginActivity extends Activity { mClearButton = findViewById(R.id.clear); mUsernameEditText = findViewById(R.id.usernameField); mPasswordEditText = findViewById(R.id.passwordField); - mUsernameEditText.setAutofillHints(View.AUTOFILL_HINT_USERNAME); - mPasswordEditText.setAutofillHints(View.AUTOFILL_HINT_PASSWORD); - mUsernameEditText.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_YES); - mPasswordEditText.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_YES); mLoginButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/AuthActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/AuthActivity.java index 356fa678..0dd3a923 100644 --- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/AuthActivity.java +++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/AuthActivity.java @@ -35,11 +35,11 @@ import android.widget.Toast; import com.example.android.autofillframework.R; import com.example.android.autofillframework.service.datasource.LocalAutofillRepository; -import com.example.android.autofillframework.service.model.AutofillField; +import com.example.android.autofillframework.service.model.AutofillFieldsCollection; +import com.example.android.autofillframework.service.model.CreditCardInfo; import com.example.android.autofillframework.service.model.LoginCredential; import java.util.HashMap; -import java.util.List; import static android.view.autofill.AutofillManager.EXTRA_ASSIST_STRUCTURE; import static android.view.autofill.AutofillManager.EXTRA_AUTHENTICATION_RESULT; @@ -83,9 +83,9 @@ public class AuthActivity extends Activity { super.onCreate(savedInstanceState); setContentView(R.layout.auth_activity); - mCancel = (Button) findViewById(R.id.cancel); - mLogin = (Button) findViewById(R.id.login); - mMasterPassword = (EditText) findViewById(R.id.master_password); + mCancel = findViewById(R.id.cancel); + mLogin = findViewById(R.id.login); + mMasterPassword = findViewById(R.id.master_password); mLogin.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -104,6 +104,7 @@ public class AuthActivity extends Activity { } private void login() { + // TODO set master username/password to Settings. Editable password = mMasterPassword.getText(); Log.d(TAG, "login: " + password); if (password.length() == 0) { @@ -136,32 +137,50 @@ public class AuthActivity extends Activity { AssistStructure structure = intent.getParcelableExtra(EXTRA_ASSIST_STRUCTURE); StructureParser parser = new StructureParser(structure); parser.parse(); - List<AutofillField> autofillFields = parser.getAutofillFields(); - HashMap<String, LoginCredential> loginCredentialMap = - LocalAutofillRepository.getInstance(this).getLoginCredentials(); - int saveType = parser.getSaveTypes(); - if (loginCredentialMap == null || loginCredentialMap.isEmpty()) { - Log.d(TAG, "No Autofill data found for this Activity."); - return; - } + AutofillFieldsCollection autofillFields = parser.getAutofillFields(); + int saveTypes = parser.getSaveTypes(); mReplyIntent = new Intent(); - if (forResponse) { - // The response protected by auth, so we can send the entire response since we - // passed auth. - FillResponse response = AutofillHelper - .newCredentialResponse(this, false, autofillFields, saveType, loginCredentialMap); - if (response != null) { - mReplyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, response); - } - } else { - // The dataset selected by the user was protected by auth, so we can send that dataset. - String datasetName = intent.getStringExtra(EXTRA_DATASET_NAME); - if (loginCredentialMap.containsKey(datasetName)) { - LoginCredential credential = loginCredentialMap.get(datasetName); - Dataset dataset = AutofillHelper - .newCredentialDataset(this, autofillFields, credential); - mReplyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, dataset); - } + switch (parser.getClientPageType()) { + case StructureParser.CLIENT_PAGE_TYPE_LOGIN: + HashMap<String, LoginCredential> loginCredentialMap = + LocalAutofillRepository.getInstance(this).getLoginCredentials(); + if (loginCredentialMap == null || loginCredentialMap.isEmpty()) { + Log.d(TAG, "No Autofill data found for this Activity."); + return; + } + if (forResponse) { + setResponseIntent(AutofillHelper.newResponse + (this, false, autofillFields, saveTypes, loginCredentialMap)); + } else { + String datasetName = intent.getStringExtra(EXTRA_DATASET_NAME); + setDatasetIntent(AutofillHelper.newDataset + (this, autofillFields, loginCredentialMap.get(datasetName))); + } + break; + case StructureParser.CLIENT_PAGE_TYPE_CREDIT_CARD_INFO: + HashMap<String, CreditCardInfo> creditCardInfoMap = + LocalAutofillRepository.getInstance(this).getCreditCardInfo(); + if (creditCardInfoMap == null || creditCardInfoMap.isEmpty()) { + Log.d(TAG, "No Autofill data found for this Activity."); + return; + } + if (forResponse) { + setResponseIntent(AutofillHelper.newResponse + (this, false, autofillFields, saveTypes, creditCardInfoMap)); + } else { + String datasetName = intent.getStringExtra(EXTRA_DATASET_NAME); + setDatasetIntent(AutofillHelper.newDataset + (this, autofillFields, creditCardInfoMap.get(datasetName))); + } + break; } } + + private void setResponseIntent(FillResponse fillResponse) { + mReplyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, fillResponse); + } + + private void setDatasetIntent(Dataset dataset) { + mReplyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, dataset); + } } diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/AutofillHelper.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/AutofillHelper.java index 8087843e..38095d31 100644 --- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/AutofillHelper.java +++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/AutofillHelper.java @@ -21,17 +21,14 @@ import android.service.autofill.Dataset; import android.service.autofill.FillResponse; import android.service.autofill.SaveInfo; import android.util.Log; -import android.view.View; import android.view.autofill.AutofillId; -import android.view.autofill.AutofillValue; import android.widget.RemoteViews; import com.example.android.autofillframework.R; -import com.example.android.autofillframework.service.model.AutofillField; -import com.example.android.autofillframework.service.model.LoginCredential; +import com.example.android.autofillframework.service.model.AutofillFieldsCollection; +import com.example.android.autofillframework.service.model.DatasetModel; import java.util.HashMap; -import java.util.List; import java.util.Set; import static com.example.android.autofillframework.CommonUtil.TAG; @@ -45,29 +42,11 @@ public final class AutofillHelper { * Wraps autofill data in a LoginCredential Dataset object which can then be sent back to the * client View. */ - public static Dataset newCredentialDataset(Context context, List<AutofillField> autofillFields, LoginCredential loginCredential) { + public static Dataset newDataset(Context context, + AutofillFieldsCollection autofillFields, DatasetModel datasetModel) { Dataset.Builder datasetBuilder = new Dataset.Builder - (newRemoteViews(context.getPackageName(), loginCredential.getDatasetName())); - // Not using enhanced for loop on Collection to prevent extra iterator allocation. - for (int i = 0; i < autofillFields.size(); i++) { - AutofillField field = autofillFields.get(i); - boolean shouldBreak = false; - for (String autofillHint : field.getHints()) { - switch (autofillHint) { - case View.AUTOFILL_HINT_USERNAME: - datasetBuilder.setValue(field.getId(), AutofillValue.forText(loginCredential.getUsername())); - shouldBreak = true; - break; - case View.AUTOFILL_HINT_PASSWORD: - datasetBuilder.setValue(field.getId(), AutofillValue.forText(loginCredential.getPassword())); - shouldBreak = true; - break; - } - if (shouldBreak) { - break; - } - } - } + (newRemoteViews(context.getPackageName(), datasetModel.getDatasetName())); + datasetModel.applyToFields(autofillFields, datasetBuilder); return datasetBuilder.build(); } @@ -81,40 +60,33 @@ public final class AutofillHelper { * Wraps autofill data in a Response object (essentially a series of Datasets) which can then * be sent back to the client View. */ - public static FillResponse newCredentialResponse(Context context, boolean datasetAuth, - List<AutofillField> autofillFields, int saveType, - HashMap<String, LoginCredential> loginCredentialMap) { + public static <T extends DatasetModel> FillResponse newResponse(Context context, + boolean datasetAuth, AutofillFieldsCollection autofillFields, int saveType, + HashMap<String, T> datasetModelMap) { FillResponse.Builder responseBuilder = new FillResponse.Builder(); - Set<String> datasetNames = loginCredentialMap.keySet(); + Set<String> datasetNames = datasetModelMap.keySet(); for (String datasetName : datasetNames) { - LoginCredential loginCredential = loginCredentialMap.get(datasetName); + T datasetModel = datasetModelMap.get(datasetName); if (datasetAuth) { Dataset.Builder datasetBuilder = - new Dataset.Builder(newRemoteViews(context.getPackageName(), loginCredential.getDatasetName())); - IntentSender sender = - AuthActivity.getAuthIntentSenderForDataset(context, loginCredential.getDatasetName()); + new Dataset.Builder(newRemoteViews + (context.getPackageName(), datasetModel.getDatasetName())); + IntentSender sender = AuthActivity + .getAuthIntentSenderForDataset(context, datasetModel.getDatasetName()); datasetBuilder.setAuthentication(sender); responseBuilder.addDataset(datasetBuilder.build()); } else { - Dataset dataset = newCredentialDataset(context, autofillFields, loginCredential); + Dataset dataset = newDataset(context, autofillFields, datasetModel); responseBuilder.addDataset(dataset); } } if (saveType != 0) { - AutofillId[] autofillIds = getAutofillIds(autofillFields); + AutofillId[] autofillIds = autofillFields.getAutofillIds(); responseBuilder.setSaveInfo(new SaveInfo.Builder(saveType, autofillIds).build()); return responseBuilder.build(); } else { - Log.d(TAG, "No Autofill data found."); + Log.d(TAG, "These fields are not meant to be saved by autofill."); return null; } } - - public static AutofillId[] getAutofillIds(List<AutofillField> autofillFields) { - AutofillId[] autofillIds = new AutofillId[autofillFields.size()]; - for (int i = 0; i < autofillFields.size(); i++) { - autofillIds[i] = autofillFields.get(i).getId(); - } - return autofillIds; - } } diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/MyAutofillService.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/MyAutofillService.java index aa0d38e3..c259cb0b 100644 --- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/MyAutofillService.java +++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/MyAutofillService.java @@ -27,18 +27,18 @@ import android.service.autofill.FillResponse; import android.service.autofill.SaveCallback; import android.service.autofill.SaveRequest; import android.util.Log; -import android.view.autofill.AutofillId; import android.widget.RemoteViews; import com.example.android.autofillframework.R; import com.example.android.autofillframework.service.datasource.LocalAutofillRepository; -import com.example.android.autofillframework.service.model.AutofillField; +import com.example.android.autofillframework.service.model.AutofillFieldsCollection; +import com.example.android.autofillframework.service.model.CreditCardInfo; +import com.example.android.autofillframework.service.model.DatasetModel; import com.example.android.autofillframework.service.model.LoginCredential; import com.example.android.autofillframework.service.settings.MyPreferences; import java.util.HashMap; import java.util.List; -import java.util.Set; import static com.example.android.autofillframework.CommonUtil.TAG; import static com.example.android.autofillframework.CommonUtil.bundleToString; @@ -80,32 +80,38 @@ public class MyAutofillService extends AutofillService { // Parse AutoFill data in Activity StructureParser parser = new StructureParser(structure); parser.parse(); - List<AutofillField> autofillFields = parser.getAutofillFields(); - HashMap<String, LoginCredential> loginCredentialMap = - LocalAutofillRepository.getInstance(this).getLoginCredentials(); - AutofillId[] autofillIds = AutofillHelper.getAutofillIds(autofillFields); + AutofillFieldsCollection autofillFields = parser.getAutofillFields(); int saveTypes = parser.getSaveTypes(); FillResponse.Builder responseBuilder = new FillResponse.Builder(); - // Check user's settings for authenticating Responses and Datasets + // Check user's settings for authenticating Responses and Datasets. boolean responseAuth = MyPreferences.getInstance(this).isResponseAuth(); if (responseAuth) { // If the entire Autofill Response is authenticated, AuthActivity is used - // to generate Response + // to generate Response. IntentSender sender = AuthActivity.getAuthIntentSenderForResponse(this); RemoteViews presentation = AutofillHelper .newRemoteViews(getPackageName(), getString(R.string.autofill_sign_in_prompt)); - responseBuilder.setAuthentication - (autofillIds, sender, presentation); + responseBuilder + .setAuthentication(autofillFields.getAutofillIds(), sender, presentation); callback.onSuccess(responseBuilder.build()); } else { boolean datasetAuth = MyPreferences.getInstance(this).isDatasetAuth(); FillResponse response = null; - if (parser.isLoginPage()) { - response = AutofillHelper.newCredentialResponse( - this, datasetAuth, autofillFields, saveTypes, loginCredentialMap); + switch (parser.getClientPageType()) { + case StructureParser.CLIENT_PAGE_TYPE_LOGIN: + HashMap<String, LoginCredential> loginCredentialMap = + LocalAutofillRepository.getInstance(this).getLoginCredentials(); + response = AutofillHelper.newResponse + (this, datasetAuth, autofillFields, saveTypes, loginCredentialMap); + break; + case StructureParser.CLIENT_PAGE_TYPE_CREDIT_CARD_INFO: + HashMap<String, CreditCardInfo> creditCardInfoMap = + LocalAutofillRepository.getInstance(this).getCreditCardInfo(); + response = AutofillHelper.newResponse + (this, datasetAuth, autofillFields, saveTypes, creditCardInfoMap); + break; } - callback.onSuccess(response); } } @@ -118,7 +124,15 @@ public class MyAutofillService extends AutofillService { Log.d(TAG, "onSaveRequest(): data=" + bundleToString(data)); StructureParser parser = new StructureParser(structure); parser.parse(); - List<AutofillField> autofillFields = parser.getAutofillFields(); + DatasetModel datasetModel = parser.getDatasetModel(); + switch (parser.getClientPageType()) { + case StructureParser.CLIENT_PAGE_TYPE_LOGIN: + LocalAutofillRepository.getInstance(this).saveLoginCredential(datasetModel); + break; + case StructureParser.CLIENT_PAGE_TYPE_CREDIT_CARD_INFO: + LocalAutofillRepository.getInstance(this).saveCreditCardInfo(datasetModel); + break; + } } @Override diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/StructureParser.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/StructureParser.java index ea3f7630..b9a1becc 100644 --- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/StructureParser.java +++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/StructureParser.java @@ -21,12 +21,11 @@ import android.app.assist.AssistStructure.WindowNode; import android.util.Log; import android.view.View; -import com.example.android.autofillframework.service.datasource.AutofillRepository; import com.example.android.autofillframework.service.model.AutofillField; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; +import com.example.android.autofillframework.service.model.AutofillFieldsCollection; +import com.example.android.autofillframework.service.model.CreditCardInfo; +import com.example.android.autofillframework.service.model.DatasetModel; +import com.example.android.autofillframework.service.model.LoginCredential; import static com.example.android.autofillframework.CommonUtil.TAG; @@ -38,13 +37,17 @@ import static com.example.android.autofillframework.CommonUtil.TAG; */ final class StructureParser { - private AssistStructure structure; - private int mSaveTypes = 0; - private List<AutofillField> mAutofillFields; + public static final int CLIENT_PAGE_TYPE_UNRECOGNIZED = 0; + public static final int CLIENT_PAGE_TYPE_LOGIN = 1; + public static final int CLIENT_PAGE_TYPE_CREDIT_CARD_INFO = 2; + + private final AutofillFieldsCollection mAutofillFields = new AutofillFieldsCollection(); + private final AssistStructure structure; + private int mClientPageType = CLIENT_PAGE_TYPE_UNRECOGNIZED; + private DatasetModel mDatasetModel; StructureParser(AssistStructure structure) { this.structure = structure; - mAutofillFields = new ArrayList<>(); } /** @@ -59,15 +62,7 @@ final class StructureParser { ViewNode view = node.getRootViewNode(); parseLocked(view); } - updateSaveTypes(); - } - - private void updateSaveTypes() { - mSaveTypes = 0; - for (int i = 0; i < mAutofillFields.size(); i++) { - AutofillField field = mAutofillFields.get(i); - mSaveTypes |= field.getSaveType(); - } + updateClientPageType(); } private void parseLocked(ViewNode view) { @@ -84,19 +79,38 @@ final class StructureParser { } } - public List<AutofillField> getAutofillFields() { + public AutofillFieldsCollection getAutofillFields() { return mAutofillFields; } public int getSaveTypes() { - return mSaveTypes; + return mAutofillFields.getSaveType(); } - /** - * Is this View structure - */ - public boolean isLoginPage() { - // TODO remove this. - return true; + private void updateClientPageType() { + if (mAutofillFields.containsHint(View.AUTOFILL_HINT_USERNAME) && + mAutofillFields.containsHint(View.AUTOFILL_HINT_PASSWORD)) { + mClientPageType = CLIENT_PAGE_TYPE_LOGIN; + // This only takes the value from one view with USERNAME hint. + // In a real service we would save all of the username values. + String username = mAutofillFields.getFieldsForHint + (View.AUTOFILL_HINT_USERNAME).get(0).getValue(); + String password = mAutofillFields.getFieldsForHint + (View.AUTOFILL_HINT_PASSWORD).get(0).getValue(); + mDatasetModel = new LoginCredential(username, password); + } else if (mAutofillFields.containsHint(View.AUTOFILL_HINT_CREDIT_CARD_NUMBER)) { + mClientPageType = CLIENT_PAGE_TYPE_CREDIT_CARD_INFO; + String creditCardNumber = mAutofillFields.getFieldsForHint + (View.AUTOFILL_HINT_CREDIT_CARD_NUMBER).get(0).getValue(); + mDatasetModel = new CreditCardInfo(creditCardNumber); + } + } + + public int getClientPageType() { + return mClientPageType; + } + + public DatasetModel getDatasetModel() { + return mDatasetModel; } } diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/datasource/AutofillRepository.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/datasource/AutofillRepository.java index b10bc8ef..9c9bcfc7 100644 --- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/datasource/AutofillRepository.java +++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/datasource/AutofillRepository.java @@ -15,6 +15,8 @@ */ package com.example.android.autofillframework.service.datasource; +import com.example.android.autofillframework.service.model.CreditCardInfo; +import com.example.android.autofillframework.service.model.DatasetModel; import com.example.android.autofillframework.service.model.LoginCredential; import java.util.HashMap; @@ -29,5 +31,9 @@ public interface AutofillRepository { /** * Saves LoginCredential under this datasetName. */ - void saveLoginCredential(LoginCredential loginCredential); + void saveLoginCredential(DatasetModel loginCredential); + + HashMap<String, CreditCardInfo> getCreditCardInfo(); + + void saveCreditCardInfo(DatasetModel creditCardInfo); } diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/datasource/LocalAutofillRepository.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/datasource/LocalAutofillRepository.java index 6c00c81f..789d2824 100644 --- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/datasource/LocalAutofillRepository.java +++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/datasource/LocalAutofillRepository.java @@ -4,6 +4,8 @@ import android.content.Context; import android.content.SharedPreferences; import android.util.ArraySet; +import com.example.android.autofillframework.service.model.CreditCardInfo; +import com.example.android.autofillframework.service.model.DatasetModel; import com.example.android.autofillframework.service.model.LoginCredential; import org.json.JSONException; @@ -19,15 +21,17 @@ import java.util.Set; */ public class LocalAutofillRepository implements AutofillRepository { private static final String SHARED_PREF_KEY = "com.example.android.autofillframework.service"; - private static final String LOGIN_CREDENTIAL_DATASETS_KEY = "loginCredentialDatasetNames"; + private static final String LOGIN_CREDENTIAL_DATASETS_KEY = "loginCredentialDatasets"; + private static final String CREDIT_CARD_INFO_DATASETS_KEY = "creditCardInfoDatasets"; + private static final String DATASET_NUMBER_KEY = "datasetNumber"; private static LocalAutofillRepository sInstance; private final SharedPreferences mPrefs; - private int datasetNumber = 0; + // TODO prepend with autofill data set in Settings. private LocalAutofillRepository(Context context) { - mPrefs = context.getSharedPreferences(SHARED_PREF_KEY, + mPrefs = context.getApplicationContext().getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE); } @@ -58,12 +62,57 @@ public class LocalAutofillRepository implements AutofillRepository { } @Override - public void saveLoginCredential(LoginCredential loginCredential) { - String datasetName = "dataset-" + datasetNumber++; - loginCredential.setDatasetName(datasetName); - Set<String> loginCredentialStringSet = - mPrefs.getStringSet(LOGIN_CREDENTIAL_DATASETS_KEY, new ArraySet<String>()); - loginCredentialStringSet.add(loginCredential.toJson().toString()); - mPrefs.edit().putStringSet(LOGIN_CREDENTIAL_DATASETS_KEY, loginCredentialStringSet).apply(); + public HashMap<String, CreditCardInfo> getCreditCardInfo() { + try { + HashMap<String, CreditCardInfo> creditCardInfoMap = new HashMap<>(); + Set<String> creditCardInfoStringSet = + mPrefs.getStringSet(CREDIT_CARD_INFO_DATASETS_KEY, new ArraySet<String>()); + for (String creditCardInfoString : creditCardInfoStringSet) { + CreditCardInfo creditCardInfo = CreditCardInfo + .fromJson(new JSONObject(creditCardInfoString)); + if (creditCardInfo != null) { + creditCardInfoMap.put(creditCardInfo.getDatasetName(), creditCardInfo); + } + } + return creditCardInfoMap; + } catch (JSONException e) { + return null; + } + } + + @Override + public void saveLoginCredential(DatasetModel loginCredential) { + saveDatasetModel(loginCredential, LOGIN_CREDENTIAL_DATASETS_KEY); + } + + @Override + public void saveCreditCardInfo(DatasetModel creditCardInfo) { + saveDatasetModel(creditCardInfo, CREDIT_CARD_INFO_DATASETS_KEY); + } + + private void saveDatasetModel(DatasetModel datasetModel, String key) { + String datasetName = "dataset-" + getDatasetNumber(); + datasetModel.setDatasetName(datasetName); + Set<String> datasetModelStringSet = + mPrefs.getStringSet(key, new ArraySet<String>()); + datasetModelStringSet.add(datasetModel.toJson().toString()); + mPrefs.edit().putStringSet(key, datasetModelStringSet).apply(); + incrementDatasetNumber(); + } + + /** + * For simplicity, datasets will be named in the form "dataset-X" where X means + * this was the Xth dataset saved. + */ + private int getDatasetNumber() { + return mPrefs.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() { + mPrefs.edit().putInt(DATASET_NUMBER_KEY, getDatasetNumber() + 1).apply(); } }
\ No newline at end of file diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/model/AutofillField.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/model/AutofillField.java index 648eb797..af676a6c 100644 --- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/model/AutofillField.java +++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/model/AutofillField.java @@ -30,7 +30,6 @@ public class AutofillField { private String[] hints; private AutofillId id; - // For simplicity, we will only support text values. // TODO support multiple value types. private String value; @@ -39,6 +38,8 @@ public class AutofillField { public void setFrom(AssistStructure.ViewNode view) { id = view.getAutofillId(); + // TODO support multiple value types. + value = view.getText().toString(); setHints(view.getAutofillHints()); } @@ -78,7 +79,7 @@ public class AutofillField { private void updateSaveTypeFromHints() { saveType = 0; - if(hints == null) { + if (hints == null) { return; } for (String hint : hints) { diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/model/AutofillFieldsCollection.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/model/AutofillFieldsCollection.java new file mode 100644 index 00000000..d03dd717 --- /dev/null +++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/model/AutofillFieldsCollection.java @@ -0,0 +1,58 @@ +/* + * 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.example.android.autofillframework.service.model; + +import android.view.autofill.AutofillId; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public final class AutofillFieldsCollection { + + private final List<AutofillId> mAutofillIds = new ArrayList<>(); + private final HashMap<String, List<AutofillField>> mAutofillHintsToFieldsMap = new HashMap<>(); + private int size = 0; + private int mSaveType = 0; + + public void add(AutofillField autofillField) { + mSaveType |= autofillField.getSaveType(); + size++; + mAutofillIds.add(autofillField.getId()); + for (String hint : autofillField.getHints()) { + if (mAutofillHintsToFieldsMap.get(hint) == null) { + mAutofillHintsToFieldsMap.put(hint, new ArrayList<AutofillField>()); + } + mAutofillHintsToFieldsMap.get(hint).add(autofillField); + } + } + + public int getSaveType() { + return mSaveType; + } + + public AutofillId[] getAutofillIds() { + return mAutofillIds.toArray(new AutofillId[size]); + } + + public List<AutofillField> getFieldsForHint(String hint) { + return mAutofillHintsToFieldsMap.get(hint); + } + + public boolean containsHint(String hint) { + return !getFieldsForHint(hint).isEmpty(); + } +} diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/model/CreditCardInfo.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/model/CreditCardInfo.java new file mode 100644 index 00000000..db7a616e --- /dev/null +++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/model/CreditCardInfo.java @@ -0,0 +1,86 @@ +/* + * 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.example.android.autofillframework.service.model; + +import android.service.autofill.Dataset; +import android.view.View; +import android.view.autofill.AutofillValue; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.List; + +/** + * Predefined model of data on an autofillable credit card info page. + */ +public class CreditCardInfo implements DatasetModel { + private String creditCardNumber; + private String datasetName; + + public CreditCardInfo(String creditCardNumber) { + this(null, creditCardNumber); + } + + public CreditCardInfo(String datasetName, String creditCardNumber) { + this.datasetName = datasetName; + this.creditCardNumber = creditCardNumber; + } + + public static CreditCardInfo fromJson(JSONObject jsonObject) { + try { + String creditCardNumber = jsonObject.getString("creditCardNumber"); + String datasetName = jsonObject.getString("datasetName"); + return new CreditCardInfo(datasetName, creditCardNumber); + } catch (JSONException e) { + return null; + } + } + + public JSONObject toJson() { + JSONObject jsonObject = new JSONObject(); + try { + jsonObject.put("creditCardNumber", creditCardNumber); + jsonObject.put("datasetName", datasetName); + } catch (JSONException e) { + return null; + } + return jsonObject; + } + + @Override + public String getDatasetName() { + return datasetName; + } + + @Override + public void setDatasetName(String datasetName) { + this.datasetName = datasetName; + } + + @Override + public void applyToFields(AutofillFieldsCollection autofillFieldsCollection, + Dataset.Builder datasetBuilder) { + List<AutofillField> creditCardNumberFields = + autofillFieldsCollection.getFieldsForHint(View.AUTOFILL_HINT_CREDIT_CARD_NUMBER); + + for (int i = 0; i < creditCardNumberFields.size(); i++) { + AutofillField field = creditCardNumberFields.get(i); + datasetBuilder.setValue(field.getId(), + AutofillValue.forText(creditCardNumber)); + } + } +} diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/model/DatasetModel.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/model/DatasetModel.java index 218e916b..1ece3c1c 100644 --- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/model/DatasetModel.java +++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/model/DatasetModel.java @@ -15,11 +15,33 @@ */ package com.example.android.autofillframework.service.model; +import android.service.autofill.Dataset; +import android.view.autofill.AutofillId; + +import org.json.JSONObject; + /** - * Blueprint for Objects that are the underlying data model for Autofill Datasets. In this basic - * sample, they are only required to have a Dataset name that displays in the list of Autofill - * suggestions that the user picks from. + * Blueprint for Objects that are the underlying data model for Autofill Datasets. + * Implementations should contain values that are meant to populate autofillable fields. */ public interface DatasetModel { + + /** + * Returns the name of the {@link Dataset}. + */ String getDatasetName(); + + /** + * Sets the {@link Dataset} name. + */ + void setDatasetName(String datasetName); + + /** + * Populates a {@link Dataset.Builder} with appropriate values for each {@link AutofillId} + * in a {@code AutofillFieldsCollection}. + */ + void applyToFields(AutofillFieldsCollection autofillFieldsCollection, + Dataset.Builder datasetBuilder); + + JSONObject toJson(); } diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/model/LoginCredential.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/model/LoginCredential.java index 742c7582..83057157 100644 --- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/model/LoginCredential.java +++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/service/model/LoginCredential.java @@ -16,49 +16,71 @@ package com.example.android.autofillframework.service.model; +import android.service.autofill.Dataset; +import android.view.View; +import android.view.autofill.AutofillValue; + import org.json.JSONException; import org.json.JSONObject; +import java.util.List; + +/** + * Predefined model of data on an autofillable login page. + */ public class LoginCredential implements DatasetModel { private final String username; private final String password; private String datasetName; - LoginCredential(String username, String password) { + public LoginCredential(String username, String password) { this(username, password, null); } - LoginCredential(String username, String password, String datasetName) { + public LoginCredential(String username, String password, String datasetName) { this.username = username; this.password = password; + this.datasetName = datasetName; } public static LoginCredential fromJson(JSONObject jsonObject) { - LoginCredential loginCredential = null; try { - loginCredential = new LoginCredential(jsonObject.getString("username"), - jsonObject.getString("password")); + return new LoginCredential(jsonObject.getString("username"), + jsonObject.getString("password"), + jsonObject.getString("datasetName")); } catch (JSONException e) { return null; } - return loginCredential; } @Override public String getDatasetName() { - return username; + return datasetName; } public void setDatasetName(String datasetName) { this.datasetName = datasetName; } - public String getUsername() { - return username; - } + @Override + public void applyToFields(AutofillFieldsCollection autofillFieldsCollection, + Dataset.Builder datasetBuilder) { + List<AutofillField> usernameFields = + autofillFieldsCollection.getFieldsForHint(View.AUTOFILL_HINT_USERNAME); + List<AutofillField> passwordFields = + autofillFieldsCollection.getFieldsForHint(View.AUTOFILL_HINT_PASSWORD); + + for (int i = 0; i < usernameFields.size(); i++) { + AutofillField field = usernameFields.get(i); + datasetBuilder.setValue(field.getId(), + AutofillValue.forText(username)); + } - public String getPassword() { - return password; + for (int i = 0; i < passwordFields.size(); i++) { + AutofillField field = passwordFields.get(i); + datasetBuilder.setValue(field.getId(), + AutofillValue.forText(password)); + } } public JSONObject toJson() { @@ -66,6 +88,7 @@ public class LoginCredential implements DatasetModel { try { jsonObject.put("username", username); jsonObject.put("password", password); + jsonObject.put("datasetName", datasetName); } catch (JSONException e) { return null; } @@ -81,7 +104,8 @@ public class LoginCredential implements DatasetModel { if (!username.equals(that.username)) return false; if (!password.equals(that.password)) return false; - return datasetName != null ? datasetName.equals(that.datasetName) : that.datasetName == null; + return datasetName != null ? + datasetName.equals(that.datasetName) : that.datasetName == null; } diff --git a/input/autofill/AutofillFramework/Application/src/main/res/layout/login_activity.xml b/input/autofill/AutofillFramework/Application/src/main/res/layout/login_activity.xml index abd1aa33..08a7b557 100644 --- a/input/autofill/AutofillFramework/Application/src/main/res/layout/login_activity.xml +++ b/input/autofill/AutofillFramework/Application/src/main/res/layout/login_activity.xml @@ -37,7 +37,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="20dp" - android:text="@string/username_label" /> + android:text="@string/username_label" + android:importantForAutofill="no"/> <EditText android:id="@+id/usernameField" @@ -45,7 +46,8 @@ android:layout_height="wrap_content" android:inputType="textPersonName" android:layout_alignBaseline="@+id/usernameLabel" - android:layout_toEndOf="@id/usernameLabel"/> + android:layout_toEndOf="@id/usernameLabel" + android:autofillHints="username"/> <TextView android:id="@+id/passwordLabel" @@ -54,7 +56,8 @@ android:text="@string/password_label" android:layout_below="@+id/usernameLabel" android:layout_alignStart="@+id/usernameLabel" - android:layout_marginTop="20dp"/> + android:layout_marginTop="20dp" + android:importantForAutofill="no"/> <EditText android:id="@+id/passwordField" @@ -62,7 +65,8 @@ android:layout_height="wrap_content" android:inputType="textPassword" android:layout_alignBaseline="@+id/passwordLabel" - android:layout_alignStart="@+id/usernameField"/> + android:layout_alignStart="@+id/usernameField" + android:autofillHints="password"/> </RelativeLayout> <LinearLayout |