diff options
author | Douglas Sigelbaum <sigelbaum@google.com> | 2017-11-20 21:31:01 -0800 |
---|---|---|
committer | Douglas Sigelbaum <sigelbaum@google.com> | 2017-12-12 11:33:12 -0800 |
commit | acb2cfd0dc0c23cb503cf62e610522862e887a5e (patch) | |
tree | a5a8d45d510436be76803717b445469a5d8a07e4 /input | |
parent | 1d98b1ebf59343c868a430be5fc68020c2b985d4 (diff) | |
download | android-acb2cfd0dc0c23cb503cf62e610522862e887a5e.tar.gz |
Refactoring AssistStructure parsing and models.
Bug: 69441672
Test: still manual
Change-Id: I0d7b098f7e3e8bbb728e72016bf26b33dd439293
Diffstat (limited to 'input')
40 files changed, 1633 insertions, 1392 deletions
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/MultiplePartitionsActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/MultiplePartitionsActivity.java index 23131602..3f4031ce 100644 --- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/MultiplePartitionsActivity.java +++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofill/app/edgecases/MultiplePartitionsActivity.java @@ -15,7 +15,6 @@ */ package com.example.android.autofill.app.edgecases; -import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; @@ -104,13 +103,10 @@ public class MultiplePartitionsActivity extends AppCompatActivity { getString(R.string.credit_card_security_code_label), " ", true, View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE); - findViewById(R.id.clear).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - resetFields(); - mCustomVirtualView.resetPositions(); - mAutofillManager.cancel(); - } + findViewById(R.id.clear).setOnClickListener((view) -> { + resetFields(); + mCustomVirtualView.resetPositions(); + mAutofillManager.cancel(); }); mAutofillManager = getSystemService(AutofillManager.class); } diff --git a/input/autofill/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/datasource/local/AutofillDaoTest.java b/input/autofill/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/data/source/local/AutofillDaoTest.java index c24e2720..dc62a298 100644 --- a/input/autofill/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/datasource/local/AutofillDaoTest.java +++ b/input/autofill/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/data/source/local/AutofillDaoTest.java @@ -14,16 +14,16 @@ * limitations under the License. */ -package com.example.android.autofill.service.datasource.local; +package com.example.android.autofill.service.data.source.local; import android.arch.persistence.room.Room; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.view.View; -import com.example.android.autofill.service.datasource.local.dao.AutofillDao; -import com.example.android.autofill.service.datasource.local.db.AutofillDatabase; +import com.example.android.autofill.service.data.source.local.db.AutofillDatabase; import com.example.android.autofill.service.model.AutofillDataset; +import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields; import com.example.android.autofill.service.model.FilledAutofillField; import com.google.common.collect.ImmutableList; @@ -32,13 +32,12 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.ArrayList; -import java.util.Comparator; import java.util.List; import java.util.UUID; -import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.hasSize; @RunWith(AndroidJUnit4.class) public class AutofillDaoTest { @@ -49,15 +48,10 @@ public class AutofillDaoTest { private final FilledAutofillField mPasswordField = new FilledAutofillField(mDataset.getId(), View.AUTOFILL_HINT_PASSWORD, "password"); - private List<FilledAutofillField> mAutofillFields; private AutofillDatabase mDatabase; @Before public void setup() { - mAutofillFields = new ArrayList<>(); - mAutofillFields.add(mUsernameField); - mAutofillFields.add(mPasswordField); - // using an in-memory database because the information stored here disappears when the // process is killed mDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(), @@ -72,50 +66,23 @@ public class AutofillDaoTest { @Test public void insertFilledAutofillFieldAndGet() { + DatasetWithFilledAutofillFields datasetWithFilledAutofillFields = + new DatasetWithFilledAutofillFields(); + datasetWithFilledAutofillFields.autofillDataset = mDataset; + datasetWithFilledAutofillFields.filledAutofillFields = + ImmutableList.of(mUsernameField, mPasswordField); + // When inserting a page's autofill fields. mDatabase.autofillDao().saveAutofillDataset(mDataset); - mDatabase.autofillDao().saveFilledAutofillFields(mAutofillFields); + mDatabase.autofillDao().saveFilledAutofillFields( + datasetWithFilledAutofillFields.filledAutofillFields); // Represents all hints of all fields on page. List<String> allHints = ImmutableList.of(View.AUTOFILL_HINT_USERNAME, View.AUTOFILL_HINT_PASSWORD); - - // When loading the autofill fields we just saved, while focused on 'username'. - List<String> usernameFocusedHints = ImmutableList.of(View.AUTOFILL_HINT_USERNAME); - List<AutofillDao.AutofillDatasetField> loadedOnUsername = mDatabase.autofillDao() - .getFilledAutofillFields(usernameFocusedHints, allHints); - - // When loading the autofill fields, while focused on 'password'. - List<String> passwordFocusedHints = ImmutableList.of(View.AUTOFILL_HINT_PASSWORD); - - List<AutofillDao.AutofillDatasetField> loadedOnPassword = mDatabase.autofillDao() - .getFilledAutofillFields(passwordFocusedHints, allHints); - - AutofillDatasetFieldComparator comparator = new AutofillDatasetFieldComparator(); - loadedOnPassword.sort(comparator); - loadedOnUsername.sort(comparator); - assertThat(loadedOnUsername, is(loadedOnPassword)); - } - - private class AutofillDatasetFieldComparator implements - Comparator<AutofillDao.AutofillDatasetField> { - @Override - public int compare(AutofillDao.AutofillDatasetField o1, - AutofillDao.AutofillDatasetField o2) { - if (o1 == null) { - return -1; - } else if (o2 == null) { - return 1; - } - - int first = o1.filledAutofillField.getHint().compareTo( - o2.filledAutofillField.getHint()); - if (first == 0) { - return o1.filledAutofillField.getDatasetId().compareTo( - o2.filledAutofillField.getDatasetId()); - } else { - return first; - } - } + List<DatasetWithFilledAutofillFields> loadedDatasets = mDatabase.autofillDao() + .getFilledAutofillFields(allHints); + assertThat(loadedDatasets, contains(datasetWithFilledAutofillFields)); + assertThat(loadedDatasets, hasSize(1)); } } diff --git a/input/autofill/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/data/source/local/LocalDataSourceTest.java b/input/autofill/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/data/source/local/LocalDataSourceTest.java new file mode 100644 index 00000000..2f445c54 --- /dev/null +++ b/input/autofill/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/data/source/local/LocalDataSourceTest.java @@ -0,0 +1,64 @@ +/* + * 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.autofill.service.data.source.local; + +import android.arch.persistence.room.Room; +import android.content.Context; +import android.content.SharedPreferences; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; + +import com.example.android.autofill.service.data.source.local.dao.AutofillDao; +import com.example.android.autofill.service.data.source.local.db.AutofillDatabase; +import com.example.android.autofill.service.util.SingleExecutors; + +import org.junit.After; +import org.junit.Before; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class LocalDataSourceTest { + + private LocalAutofillDataSource mLocalDataSource; + private AutofillDatabase mDatabase; + + @Before + public void setup() { + // using an in-memory database for testing, since it doesn't survive killing the process + mDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(), + AutofillDatabase.class) + .build(); + AutofillDao tasksDao = mDatabase.autofillDao(); + SharedPreferences sharedPreferences = InstrumentationRegistry.getContext() + .getSharedPreferences(LocalAutofillDataSource.SHARED_PREF_KEY, Context.MODE_PRIVATE); + // Make sure that we're not keeping a reference to the wrong instance. + LocalAutofillDataSource.clearInstance(); + mLocalDataSource = LocalAutofillDataSource.getInstance(sharedPreferences, + tasksDao, new SingleExecutors()); + } + + @After + public void cleanUp() { + try { + mDatabase.close(); + } finally { + LocalAutofillDataSource.clearInstance(); + } + } +} diff --git a/input/autofill/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/util/SingleExecutors.java b/input/autofill/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/util/SingleExecutors.java new file mode 100644 index 00000000..baac2b19 --- /dev/null +++ b/input/autofill/AutofillFramework/afservice/src/androidTest/java/com/example/android/autofill/service/util/SingleExecutors.java @@ -0,0 +1,30 @@ +/* + * 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.autofill.service.util; + +import java.util.concurrent.Executor; + +/** + * Allow instant execution of tasks. + */ +public final class SingleExecutors extends AppExecutors { + private static Executor sInstance = Runnable::run; + + public SingleExecutors() { + super(sInstance, sInstance, sInstance); + } +}
\ No newline at end of file diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AuthActivity.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AuthActivity.java index 9f526fb3..710bb42f 100644 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AuthActivity.java +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AuthActivity.java @@ -20,6 +20,7 @@ import android.app.assist.AssistStructure; import android.content.Context; import android.content.Intent; import android.content.IntentSender; +import android.content.SharedPreferences; import android.os.Bundle; import android.service.autofill.Dataset; import android.service.autofill.FillResponse; @@ -28,16 +29,22 @@ import android.support.v7.app.AppCompatActivity; import android.text.Editable; import android.view.autofill.AutofillManager; import android.widget.EditText; +import android.widget.RemoteViews; import android.widget.Toast; -import com.example.android.autofill.service.datasource.DataCallback; -import com.example.android.autofill.service.datasource.local.DigitalAssetLinksRepository; -import com.example.android.autofill.service.datasource.local.LocalAutofillDataSource; -import com.example.android.autofill.service.model.FilledAutofillFieldCollection; +import com.example.android.autofill.service.data.DataCallback; +import com.example.android.autofill.service.data.adapter.DatasetAdapter; +import com.example.android.autofill.service.data.adapter.ResponseAdapter; +import com.example.android.autofill.service.data.ClientViewMetadata; +import com.example.android.autofill.service.data.source.local.DigitalAssetLinksRepository; +import com.example.android.autofill.service.data.source.local.LocalAutofillDataSource; +import com.example.android.autofill.service.data.source.local.dao.AutofillDao; +import com.example.android.autofill.service.data.source.local.db.AutofillDatabase; +import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields; import com.example.android.autofill.service.settings.MyPreferences; import com.example.android.autofill.service.util.AppExecutors; -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; @@ -59,19 +66,24 @@ public class AuthActivity extends AppCompatActivity { private LocalAutofillDataSource mLocalAutofillDataSource; private DigitalAssetLinksRepository mDalRepository; private EditText mMasterPassword; + private DatasetAdapter mDatasetAdapter; + private ResponseAdapter mResponseAdapter; + private ClientViewMetadata mClientViewMetadata; + private String mPackageName; private Intent mReplyIntent; - static IntentSender getAuthIntentSenderForResponse(Context context) { + public static IntentSender getAuthIntentSenderForResponse(Context context) { final Intent intent = new Intent(context, AuthActivity.class); - return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT) - .getIntentSender(); + return PendingIntent.getActivity(context, 0, intent, + PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender(); } - static IntentSender getAuthIntentSenderForDataset(Context context, String datasetName) { - final Intent intent = new Intent(context, AuthActivity.class); + public static IntentSender getAuthIntentSenderForDataset(Context originContext, + String datasetName) { + Intent intent = new Intent(originContext, AuthActivity.class); intent.putExtra(EXTRA_DATASET_NAME, datasetName); intent.putExtra(EXTRA_FOR_RESPONSE, false); - return PendingIntent.getActivity(context, ++sDatasetPendingIntentId, intent, + return PendingIntent.getActivity(originContext, ++sDatasetPendingIntentId, intent, PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender(); } @@ -79,10 +91,14 @@ public class AuthActivity extends AppCompatActivity { protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.multidataset_service_auth_activity); - mLocalAutofillDataSource = LocalAutofillDataSource.getInstance(this, - new AppExecutors()); - mDalRepository = DigitalAssetLinksRepository.getInstance(this); + SharedPreferences sharedPreferences = + getSharedPreferences(LocalAutofillDataSource.SHARED_PREF_KEY, Context.MODE_PRIVATE); + AutofillDao autofillDao = AutofillDatabase.getInstance(this).autofillDao(); + mLocalAutofillDataSource = LocalAutofillDataSource.getInstance(sharedPreferences, + autofillDao, new AppExecutors()); + mDalRepository = DigitalAssetLinksRepository.getInstance(getPackageManager()); mMasterPassword = findViewById(R.id.master_password); + mPackageName = getPackageName(); findViewById(R.id.login).setOnClickListener((view) -> login()); findViewById(R.id.cancel).setOnClickListener((view) -> { onFailure(); @@ -92,8 +108,8 @@ public class AuthActivity extends AppCompatActivity { private void login() { Editable password = mMasterPassword.getText(); - if (password.toString() - .equals(MyPreferences.getInstance(AuthActivity.this).getMasterPassword())) { + String correctPassword = MyPreferences.getInstance(AuthActivity.this).getMasterPassword(); + if (password.toString().equals(correctPassword)) { onSuccess(); } else { Toast.makeText(this, "Password incorrect", Toast.LENGTH_SHORT).show(); @@ -122,29 +138,45 @@ public class AuthActivity extends AppCompatActivity { boolean forResponse = intent.getBooleanExtra(EXTRA_FOR_RESPONSE, true); Bundle clientState = intent.getBundleExtra(AutofillManager.EXTRA_CLIENT_STATE); AssistStructure structure = intent.getParcelableExtra(EXTRA_ASSIST_STRUCTURE); - StructureParser parser = new StructureParser(getApplicationContext(), structure, - mLocalAutofillDataSource, mDalRepository); - parser.parseForFill(); - AutofillFieldMetadataCollection autofillFields = parser.getAutofillFields(); - int saveTypes = autofillFields.getSaveType(); + StructureParser structureParser = new StructureParser(structure); + mClientViewMetadata = new ClientViewMetadata(structureParser); + mDatasetAdapter = new DatasetAdapter(structureParser); + mResponseAdapter = new ResponseAdapter(this, mClientViewMetadata, mPackageName, + mDatasetAdapter, clientState); mReplyIntent = new Intent(); + if (forResponse) { + fetchAllDatasetsAndSetIntent(); + } else { + String datasetName = intent.getStringExtra(EXTRA_DATASET_NAME); + fetchDatasetAndSetIntent(datasetName); + } + } + + private void fetchDatasetAndSetIntent(String datasetName) { + mLocalAutofillDataSource.getAutofillDataset(mClientViewMetadata.getAllHints(), + datasetName, new DataCallback<DatasetWithFilledAutofillFields>() { + @Override + public void onLoaded(DatasetWithFilledAutofillFields dataset) { + String datasetName = dataset.autofillDataset.getDatasetName(); + RemoteViews remoteViews = RemoteViewsHelper.viewsWithNoAuth( + mPackageName, datasetName); + setDatasetIntent(mDatasetAdapter.buildDataset(dataset, remoteViews)); + } + + @Override + public void onDataNotAvailable(String msg, Object... params) { + logw(msg, params); + } + }); + } - mLocalAutofillDataSource.getFilledAutofillFieldCollection( - autofillFields.getFocusedHints(), autofillFields.getAllHints(), - new DataCallback<HashMap<String, FilledAutofillFieldCollection>>() { + private void fetchAllDatasetsAndSetIntent() { + mLocalAutofillDataSource.getAutofillDatasets(mClientViewMetadata.getAllHints(), + new DataCallback<List<DatasetWithFilledAutofillFields>>() { @Override - public void onLoaded(HashMap<String, FilledAutofillFieldCollection> - clientFormDataMap) { - if (forResponse) { - setResponseIntent(AutofillHelper.newResponse - (AuthActivity.this, clientState, false, - autofillFields, clientFormDataMap)); - } else { - String datasetName = intent.getStringExtra(EXTRA_DATASET_NAME); - setDatasetIntent(AutofillHelper.newDataset - (AuthActivity.this, autofillFields, - clientFormDataMap.get(datasetName), false)); - } + public void onLoaded(List<DatasetWithFilledAutofillFields> datasets) { + FillResponse fillResponse = mResponseAdapter.buildResponse(datasets, false); + setResponseIntent(fillResponse); } @Override diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AutofillFieldMetadata.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AutofillFieldMetadata.java deleted file mode 100644 index ab15e3f1..00000000 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AutofillFieldMetadata.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.autofill.service; - -import android.app.assist.AssistStructure.ViewNode; -import android.view.autofill.AutofillId; - -/** - * A stripped down version of a {@link ViewNode} that contains only autofill-relevant metadata. It - * also contains a {@code mSaveType} flag that is calculated based on the {@link ViewNode}]'s - * autofill hints. - */ -public class AutofillFieldMetadata { - private int mSaveType = 0; - private String[] mAutofillHints; - private AutofillId mAutofillId; - private int mAutofillType; - private CharSequence[] mAutofillOptions; - private boolean mFocused; - - public AutofillFieldMetadata(ViewNode view) { - mAutofillId = view.getAutofillId(); - mAutofillType = view.getAutofillType(); - mAutofillOptions = view.getAutofillOptions(); - mFocused = view.isFocused(); - String[] hints = AutofillHints.filterForSupportedHints(view.getAutofillHints()); - if (hints != null) { - AutofillHints.convertToStoredHintNames(hints); - setHints(hints); - } - } - - public String[] getHints() { - return mAutofillHints; - } - - public void setHints(String[] hints) { - mAutofillHints = hints; - mSaveType = AutofillHints.getSaveTypeForHints(hints); - } - - public int getSaveType() { - return mSaveType; - } - - public AutofillId getId() { - return mAutofillId; - } - - public int getAutofillType() { - return mAutofillType; - } - - /** - * When the {@link ViewNode} is a list that the user needs to choose a string from (i.e. a - * spinner), this is called to return the index of a specific item in the list. - */ - public int getAutofillOptionIndex(String value) { - for (int i = 0; i < mAutofillOptions.length; i++) { - if (mAutofillOptions[i].toString().equals(value)) { - return i; - } - } - return -1; - } - - public boolean isFocused() { - return mFocused; - } -} diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AutofillFieldMetadataCollection.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AutofillFieldMetadataCollection.java deleted file mode 100644 index d343ff2f..00000000 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AutofillFieldMetadataCollection.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.autofill.service; - -import android.view.autofill.AutofillId; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; - -/** - * Data structure that stores a collection of {@code AutofillFieldMetadata}s. Contains all of the - * client's {@code View} hierarchy autofill-relevant metadata. - */ -public final class AutofillFieldMetadataCollection { - - private final List<AutofillId> mAutofillIds = new ArrayList<>(); - private final HashMap<String, List<AutofillFieldMetadata>> mAutofillHintsToFieldsMap = new HashMap<>(); - private final List<String> mAllAutofillHints = new ArrayList<>(); - private final List<String> mFocusedAutofillHints = new ArrayList<>(); - private int mSize = 0; - private int mSaveType = 0; - - public void add(AutofillFieldMetadata autofillFieldMetadata) { - mSaveType |= autofillFieldMetadata.getSaveType(); - mSize++; - mAutofillIds.add(autofillFieldMetadata.getId()); - List<String> hintsList = Arrays.asList(autofillFieldMetadata.getHints()); - mAllAutofillHints.addAll(hintsList); - if (autofillFieldMetadata.isFocused()) { - mFocusedAutofillHints.addAll(hintsList); - } - for (String hint : autofillFieldMetadata.getHints()) { - if (!mAutofillHintsToFieldsMap.containsKey(hint)) { - mAutofillHintsToFieldsMap.put(hint, new ArrayList<>()); - } - mAutofillHintsToFieldsMap.get(hint).add(autofillFieldMetadata); - } - } - - public int getSaveType() { - return mSaveType; - } - - public AutofillId[] getAutofillIds() { - return mAutofillIds.toArray(new AutofillId[mSize]); - } - - public List<AutofillFieldMetadata> getFieldsForHint(String hint) { - return mAutofillHintsToFieldsMap.get(hint); - } - - public List<String> getFocusedHints() { - return mFocusedAutofillHints; - } - - public List<String> getAllHints() { - return mAllAutofillHints; - } -} diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AutofillHelper.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AutofillHelper.java deleted file mode 100644 index 6005aff4..00000000 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AutofillHelper.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * 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.autofill.service; - -import android.content.Context; -import android.content.IntentSender; -import android.os.Bundle; -import android.service.autofill.Dataset; -import android.service.autofill.FillResponse; -import android.service.autofill.SaveInfo; -import android.support.annotation.DrawableRes; -import android.view.View; -import android.view.autofill.AutofillId; -import android.widget.RemoteViews; - -import com.example.android.autofill.service.model.FilledAutofillFieldCollection; -import com.example.android.autofill.service.util.Util; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Set; - -import static com.example.android.autofill.service.util.Util.bundleToString; -import static com.example.android.autofill.service.util.Util.getSaveTypeAsString; -import static com.example.android.autofill.service.util.Util.logd; - -/** - * This is a class containing helper methods for building Autofill Datasets and Responses. - */ -public final class AutofillHelper { - - static final String CLIENT_STATE_PARTIAL_ID_TEMPLATE = "partial-%s"; - // TODO: move to settings activity and document it - private static final boolean SUPPORT_MULTIPLE_STEPS = true; - - private AutofillHelper() { - throw new UnsupportedOperationException("provide static methods only"); - } - - /** - * Wraps autofill data in a LoginCredential Dataset object which can then be sent back to the - * client View. - */ - public static Dataset newDataset(Context context, - AutofillFieldMetadataCollection autofillFields, - FilledAutofillFieldCollection filledAutofillFieldCollection, boolean datasetAuth) { - String datasetName = filledAutofillFieldCollection.getDataset().getDatasetName(); - Dataset.Builder datasetBuilder; - if (datasetAuth) { - datasetBuilder = new Dataset.Builder - (newRemoteViews(context.getPackageName(), datasetName, - R.drawable.ic_lock_black_24dp)); - IntentSender sender = - AuthActivity.getAuthIntentSenderForDataset(context, datasetName); - datasetBuilder.setAuthentication(sender); - } else { - datasetBuilder = new Dataset.Builder - (newRemoteViews(context.getPackageName(), datasetName, - R.drawable.ic_person_black_24dp)); - } - boolean setValueAtLeastOnce = - filledAutofillFieldCollection.applyToFields(autofillFields, datasetBuilder); - if (setValueAtLeastOnce) { - return datasetBuilder.build(); - } - return null; - } - - public static RemoteViews newRemoteViews(String packageName, String remoteViewsText, - @DrawableRes int drawableId) { - RemoteViews presentation = - new RemoteViews(packageName, R.layout.multidataset_service_list_item); - presentation.setTextViewText(R.id.text, remoteViewsText); - presentation.setImageViewResource(R.id.icon, drawableId); - return presentation; - } - - /** - * 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 newResponse(Context context, Bundle previousClientState, - boolean datasetAuth, 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, datasetAuth); - if (dataset != null) { - responseBuilder.addDataset(dataset); - } - } - } - } - int saveType = autofillFields.getSaveType(); - if (saveType != 0) { - if (SUPPORT_MULTIPLE_STEPS) { - setPartialSaveInfo(responseBuilder, saveType, autofillFields, previousClientState); - } else { - setFullSaveInfo(responseBuilder, saveType, autofillFields); - } - return responseBuilder.build(); - } else { - logd("These fields are not meant to be saved by autofill."); - return null; - } - } - - private static void setFullSaveInfo(FillResponse.Builder responseBuilder, int saveType, - AutofillFieldMetadataCollection autofillFields) { - AutofillId[] autofillIds = autofillFields.getAutofillIds(); - responseBuilder.setSaveInfo(new SaveInfo.Builder(saveType, autofillIds).build()); - } - - private static void setPartialSaveInfo(FillResponse.Builder responseBuilder, int saveType, - AutofillFieldMetadataCollection autofillFields, - Bundle previousClientState) { - AutofillId[] autofillIds = autofillFields.getAutofillIds(); - List<String> allHints = autofillFields.getAllHints(); - if (Util.logDebugEnabled()) { - logd("setPartialSaveInfo() for type %s: allHints=%s, ids=%s, clientState=%s", - getSaveTypeAsString(saveType), allHints, Arrays.toString(autofillIds), - bundleToString(previousClientState)); - } - - // TODO: this should be more generic, but for now it's hardcode to support just activities - // that have an username and a password in separate steps (like MultipleStepsSigninActivity) - if ((saveType != SaveInfo.SAVE_DATA_TYPE_USERNAME - && saveType != SaveInfo.SAVE_DATA_TYPE_PASSWORD) - || autofillIds.length != 1 || allHints.size() != 1) { - logd("Unsupported activity for partial info; returning full"); - setFullSaveInfo(responseBuilder, saveType, autofillFields); - return; - } - - int previousSaveType; - String previousHint; - if (saveType == SaveInfo.SAVE_DATA_TYPE_PASSWORD) { - previousHint = View.AUTOFILL_HINT_USERNAME; - previousSaveType = SaveInfo.SAVE_DATA_TYPE_USERNAME; - } else { - previousHint = View.AUTOFILL_HINT_PASSWORD; - previousSaveType = SaveInfo.SAVE_DATA_TYPE_PASSWORD; - } - String previousKey = String.format(CLIENT_STATE_PARTIAL_ID_TEMPLATE, previousHint); - - AutofillId previousValue = previousClientState == null - ? null - : previousClientState.getParcelable(previousKey); - logd("previous: %s=%s", previousKey, previousValue); - - Bundle newClientState = new Bundle(); - String key = String.format(CLIENT_STATE_PARTIAL_ID_TEMPLATE, allHints.get(0)); - AutofillId value = autofillIds[0]; - logd("New client state: %s = %s", key, value); - newClientState.putParcelable(key, value); - - if (previousValue != null) { - AutofillId[] newIds = new AutofillId[]{previousValue, value}; - int newSaveType = saveType | previousSaveType; - logd("new values: type=%s, ids=%s", - getSaveTypeAsString(newSaveType), Arrays.toString(newIds)); - newClientState.putAll(previousClientState); - responseBuilder.setSaveInfo - (new SaveInfo.Builder(newSaveType, newIds) - .setFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) - .build()) - .setClientState(newClientState); - - return; - } - - responseBuilder.setClientState(newClientState); - - // TODO: on MR1, creates a new SaveType without required ids - setFullSaveInfo(responseBuilder, saveType, autofillFields); - } -} diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AutofillHintProperties.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AutofillHintProperties.java index d1efcc04..9b593426 100644 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AutofillHintProperties.java +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AutofillHintProperties.java @@ -15,6 +15,8 @@ */ package com.example.android.autofill.service; +import android.view.View; + import com.example.android.autofill.service.model.FilledAutofillField; import java.util.Arrays; @@ -77,8 +79,8 @@ public final class AutofillHintProperties { /** * Sometimes, data for a hint should only be stored as a certain AutofillValue type. For * example, it is recommended that data representing a Credit Card Expiration date, annotated - * with the hint {@link android.view.View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE}, should - * only be stored as {@link android.view.View.AUTOFILL_TYPE_DATE}. + * with the hint {@link View#AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE}, should + * only be stored as {@link View#AUTOFILL_TYPE_DATE}. */ public boolean isValidType(int type) { return mValidTypes.contains(type); diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AutofillHints.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AutofillHints.java index 7cceba51..39b189c7 100644 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AutofillHints.java +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/AutofillHints.java @@ -16,20 +16,24 @@ package com.example.android.autofill.service; import android.service.autofill.SaveInfo; +import android.support.annotation.NonNull; import android.view.View; -import com.example.android.autofill.service.model.AutofillDataset; import com.example.android.autofill.service.model.FilledAutofillField; -import com.example.android.autofill.service.model.FilledAutofillFieldCollection; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import java.util.ArrayList; import java.util.Calendar; -import java.util.UUID; +import java.util.List; +import java.util.Objects; +import static com.example.android.autofill.service.util.Util.logd; import static com.example.android.autofill.service.util.Util.logw; - +import static java.util.stream.Collectors.toList; public final class AutofillHints { + public static final int PARTITION_ALL = -1; public static final int PARTITION_OTHER = 0; public static final int PARTITION_ADDRESS = 1; public static final int PARTITION_EMAIL = 2; @@ -45,45 +49,44 @@ public final class AutofillHints { PARTITION_EMAIL, (seed, datasetId) -> { String textValue = "email" + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( - datasetId, View.AUTOFILL_HINT_EMAIL_ADDRESS, textValue); - return filledAutofillField; + return new FilledAutofillField(datasetId, + View.AUTOFILL_HINT_EMAIL_ADDRESS, textValue); + }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(View.AUTOFILL_HINT_NAME, new AutofillHintProperties( View.AUTOFILL_HINT_NAME, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { String textValue = "name" + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( - datasetId, View.AUTOFILL_HINT_NAME, textValue); - return filledAutofillField; + return new FilledAutofillField(datasetId, View.AUTOFILL_HINT_NAME, + textValue); + }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(View.AUTOFILL_HINT_USERNAME, new AutofillHintProperties( View.AUTOFILL_HINT_USERNAME, SaveInfo.SAVE_DATA_TYPE_USERNAME, PARTITION_OTHER, (seed, datasetId) -> { String textValue = "login" + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( - datasetId, View.AUTOFILL_HINT_USERNAME, textValue); - return filledAutofillField; + return new FilledAutofillField(datasetId, + View.AUTOFILL_HINT_USERNAME, textValue); + }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(View.AUTOFILL_HINT_PASSWORD, new AutofillHintProperties( View.AUTOFILL_HINT_PASSWORD, SaveInfo.SAVE_DATA_TYPE_PASSWORD, PARTITION_OTHER, (seed, datasetId) -> { String textValue = "login" + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( - datasetId, View.AUTOFILL_HINT_PASSWORD, textValue); - return filledAutofillField; + return new FilledAutofillField(datasetId, + View.AUTOFILL_HINT_PASSWORD, textValue); + }, View.AUTOFILL_TYPE_TEXT)) .put(View.AUTOFILL_HINT_PHONE, new AutofillHintProperties( View.AUTOFILL_HINT_PHONE, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { String textValue = "" + seed + "2345678910"; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, View.AUTOFILL_HINT_PHONE, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(View.AUTOFILL_HINT_POSTAL_ADDRESS, new AutofillHintProperties( View.AUTOFILL_HINT_POSTAL_ADDRESS, SaveInfo.SAVE_DATA_TYPE_ADDRESS, @@ -91,18 +94,16 @@ public final class AutofillHints { (seed, datasetId) -> { String textValue = "" + seed + " Fake Ln, Fake, FA, FAA 10001"; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, View.AUTOFILL_HINT_POSTAL_ADDRESS, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(View.AUTOFILL_HINT_POSTAL_CODE, new AutofillHintProperties( View.AUTOFILL_HINT_POSTAL_CODE, SaveInfo.SAVE_DATA_TYPE_ADDRESS, PARTITION_ADDRESS, (seed, datasetId) -> { String textValue = "1000" + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, View.AUTOFILL_HINT_POSTAL_CODE, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(View.AUTOFILL_HINT_CREDIT_CARD_NUMBER, new AutofillHintProperties( View.AUTOFILL_HINT_CREDIT_CARD_NUMBER, @@ -110,10 +111,9 @@ public final class AutofillHints { PARTITION_CREDIT_CARD, (seed, datasetId) -> { String textValue = "" + seed + "234567"; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, View.AUTOFILL_HINT_CREDIT_CARD_NUMBER, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT)) .put(View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE, new AutofillHintProperties( View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE, @@ -121,9 +121,9 @@ public final class AutofillHints { PARTITION_CREDIT_CARD, (seed, datasetId) -> { String textValue = "" + seed + seed + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField(datasetId, - View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE, textValue); - return filledAutofillField; + return new FilledAutofillField( + datasetId, View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE, + textValue); }, View.AUTOFILL_TYPE_TEXT)) .put(View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE, new AutofillHintProperties( View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE, @@ -132,10 +132,9 @@ public final class AutofillHints { Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR) + seed); Long dateValue = calendar.getTimeInMillis(); - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE, dateValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_DATE)) .put(View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH, new AutofillHintProperties( View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH, @@ -147,10 +146,9 @@ public final class AutofillHints { calendar.set(Calendar.MONTH, month); String textValue = Integer.toString(month); Long dateValue = calendar.getTimeInMillis(); - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH, textValue, dateValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST, View.AUTOFILL_TYPE_DATE)) .put(View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR, new AutofillHintProperties( @@ -162,10 +160,9 @@ public final class AutofillHints { calendar.set(Calendar.YEAR, expYear); Long dateValue = calendar.getTimeInMillis(); String textValue = Integer.toString(expYear); - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR, textValue, dateValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST, View.AUTOFILL_TYPE_DATE)) .put(View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY, new AutofillHintProperties( @@ -178,10 +175,9 @@ public final class AutofillHints { calendar.set(Calendar.DATE, day); String textValue = Integer.toString(day); Long dateValue = calendar.getTimeInMillis(); - FilledAutofillField filledAutofillField = new FilledAutofillField(datasetId, - View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY, - textValue, dateValue); - return filledAutofillField; + return new FilledAutofillField(datasetId, + View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY, + textValue, dateValue); }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST, View.AUTOFILL_TYPE_DATE)) .put(W3cHints.HONORIFIC_PREFIX, new AutofillHintProperties( @@ -192,36 +188,32 @@ public final class AutofillHints { "Sr.", "Dr.", "Lady", "Lord"}; String textValueFromList = examplePrefixes[seed % examplePrefixes.length].toString(); - FilledAutofillField filledAutofillField = new FilledAutofillField(datasetId, - W3cHints.HONORIFIC_PREFIX, textValueFromList); - return filledAutofillField; + return new FilledAutofillField( + datasetId, W3cHints.HONORIFIC_PREFIX, textValueFromList); }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.GIVEN_NAME, new AutofillHintProperties(W3cHints.GIVEN_NAME, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { String textValue = "name" + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.GIVEN_NAME, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT)) .put(W3cHints.ADDITIONAL_NAME, new AutofillHintProperties( W3cHints.ADDITIONAL_NAME, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { String textValue = "addtlname" + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.ADDITIONAL_NAME, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT)) .put(W3cHints.FAMILY_NAME, new AutofillHintProperties( W3cHints.FAMILY_NAME, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { String textValue = "famname" + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.FAMILY_NAME, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT)) .put(W3cHints.HONORIFIC_SUFFIX, new AutofillHintProperties( W3cHints.HONORIFIC_SUFFIX, SaveInfo.SAVE_DATA_TYPE_GENERIC, @@ -230,45 +222,40 @@ public final class AutofillHints { CharSequence[] exampleSuffixes = {"san", "kun", "chan", "sama"}; String textValueFromListValue = exampleSuffixes[seed % exampleSuffixes.length].toString(); - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.HONORIFIC_SUFFIX, textValueFromListValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.NEW_PASSWORD, new AutofillHintProperties( W3cHints.NEW_PASSWORD, SaveInfo.SAVE_DATA_TYPE_PASSWORD, PARTITION_OTHER, (seed, datasetId) -> { String textValue = "login" + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.NEW_PASSWORD, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT)) .put(W3cHints.CURRENT_PASSWORD, new AutofillHintProperties( View.AUTOFILL_HINT_PASSWORD, SaveInfo.SAVE_DATA_TYPE_PASSWORD, PARTITION_OTHER, (seed, datasetId) -> { String textValue = "login" + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, View.AUTOFILL_HINT_PASSWORD, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT)) .put(W3cHints.ORGANIZATION_TITLE, new AutofillHintProperties( W3cHints.ORGANIZATION_TITLE, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { String textValue = "org" + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.ORGANIZATION_TITLE, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.ORGANIZATION, new AutofillHintProperties(W3cHints.ORGANIZATION, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { String textValue = "org" + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.ORGANIZATION, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.STREET_ADDRESS, new AutofillHintProperties( W3cHints.STREET_ADDRESS, SaveInfo.SAVE_DATA_TYPE_ADDRESS, @@ -276,78 +263,69 @@ public final class AutofillHints { (seed, datasetId) -> { String textValue = "" + seed + " Fake Ln, Fake, FA, FAA 10001"; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.STREET_ADDRESS, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT)) .put(W3cHints.ADDRESS_LINE1, new AutofillHintProperties(W3cHints.ADDRESS_LINE1, SaveInfo.SAVE_DATA_TYPE_ADDRESS, PARTITION_ADDRESS, (seed, datasetId) -> { String textValue = "" + seed + " Fake Ln"; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.ADDRESS_LINE1, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT)) .put(W3cHints.ADDRESS_LINE2, new AutofillHintProperties(W3cHints.ADDRESS_LINE2, SaveInfo.SAVE_DATA_TYPE_ADDRESS, PARTITION_ADDRESS, (seed, datasetId) -> { String textValue = "Bldg. " + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.ADDRESS_LINE2, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT)) .put(W3cHints.ADDRESS_LINE3, new AutofillHintProperties(W3cHints.ADDRESS_LINE3, SaveInfo.SAVE_DATA_TYPE_ADDRESS, PARTITION_ADDRESS, (seed, datasetId) -> { String textValue = "Suite " + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.ADDRESS_LINE3, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT)) .put(W3cHints.ADDRESS_LEVEL4, new AutofillHintProperties( W3cHints.ADDRESS_LEVEL4, SaveInfo.SAVE_DATA_TYPE_ADDRESS, PARTITION_ADDRESS, (seed, datasetId) -> { String textValue = "city " + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.ADDRESS_LEVEL4, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT)) .put(W3cHints.ADDRESS_LEVEL3, new AutofillHintProperties( W3cHints.ADDRESS_LEVEL3, SaveInfo.SAVE_DATA_TYPE_ADDRESS, PARTITION_ADDRESS, (seed, datasetId) -> { String textValue = "county " + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.ADDRESS_LEVEL3, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT)) .put(W3cHints.ADDRESS_LEVEL2, new AutofillHintProperties( W3cHints.ADDRESS_LEVEL2, SaveInfo.SAVE_DATA_TYPE_ADDRESS, PARTITION_ADDRESS, (seed, datasetId) -> { String textValue = "state " + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.ADDRESS_LEVEL2, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT)) .put(W3cHints.ADDRESS_LEVEL1, new AutofillHintProperties( W3cHints.ADDRESS_LEVEL1, SaveInfo.SAVE_DATA_TYPE_ADDRESS, PARTITION_ADDRESS, (seed, datasetId) -> { String textValue = "country " + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.ADDRESS_LEVEL1, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT)) .put(W3cHints.COUNTRY, new AutofillHintProperties(W3cHints.COUNTRY, SaveInfo.SAVE_DATA_TYPE_ADDRESS, PARTITION_ADDRESS, (seed, datasetId) -> { String textValue = "country " + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.COUNTRY, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.COUNTRY_NAME, new AutofillHintProperties(W3cHints.COUNTRY_NAME, SaveInfo.SAVE_DATA_TYPE_ADDRESS, PARTITION_ADDRESS, @@ -355,63 +333,56 @@ public final class AutofillHints { CharSequence[] exampleCountries = {"USA", "Mexico", "Canada"}; String textValue = exampleCountries[seed % exampleCountries.length] .toString(); - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.COUNTRY_NAME, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.POSTAL_CODE, new AutofillHintProperties( View.AUTOFILL_HINT_POSTAL_CODE, SaveInfo.SAVE_DATA_TYPE_ADDRESS, PARTITION_ADDRESS, (seed, datasetId) -> { String textValue = "" + seed + seed + seed + seed + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, View.AUTOFILL_HINT_POSTAL_CODE, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT)) .put(W3cHints.CC_NAME, new AutofillHintProperties(W3cHints.CC_NAME, SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD, PARTITION_CREDIT_CARD, (seed, datasetId) -> { String textValue = "firstname" + seed + "lastname" + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.CC_NAME, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT)) .put(W3cHints.CC_GIVEN_NAME, new AutofillHintProperties(W3cHints.CC_GIVEN_NAME, SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD, PARTITION_CREDIT_CARD, (seed, datasetId) -> { String textValue = "givenname" + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.CC_GIVEN_NAME, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT)) .put(W3cHints.CC_ADDITIONAL_NAME, new AutofillHintProperties( W3cHints.CC_ADDITIONAL_NAME, SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD, PARTITION_CREDIT_CARD, (seed, datasetId) -> { String textValue = "addtlname" + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.CC_ADDITIONAL_NAME, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT)) .put(W3cHints.CC_FAMILY_NAME, new AutofillHintProperties( W3cHints.CC_FAMILY_NAME, SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD, PARTITION_CREDIT_CARD, (seed, datasetId) -> { String textValue = "familyname" + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.CC_FAMILY_NAME, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT)) .put(W3cHints.CC_NUMBER, new AutofillHintProperties( View.AUTOFILL_HINT_CREDIT_CARD_NUMBER, SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD, PARTITION_CREDIT_CARD, (seed, datasetId) -> { String textValue = "" + seed + "234567"; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, View.AUTOFILL_HINT_CREDIT_CARD_NUMBER, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT)) .put(W3cHints.CC_EXPIRATION, new AutofillHintProperties( View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE, @@ -420,10 +391,9 @@ public final class AutofillHints { Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR) + seed); Long dateValue = calendar.getTimeInMillis(); - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE, dateValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_DATE)) .put(W3cHints.CC_EXPIRATION_MONTH, new AutofillHintProperties( View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH, @@ -432,10 +402,9 @@ public final class AutofillHints { CharSequence[] months = monthRange(); String textValueFromListValue = months[seed % months.length] .toString(); - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH, textValueFromListValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.CC_EXPIRATION_YEAR, new AutofillHintProperties( View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR, @@ -446,29 +415,26 @@ public final class AutofillHints { calendar.set(Calendar.YEAR, expYear); Long dateValue = calendar.getTimeInMillis(); String textValue = "" + expYear; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR, textValue, dateValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.CC_CSC, new AutofillHintProperties( View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE, SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD, PARTITION_CREDIT_CARD, (seed, datasetId) -> { String textValue = "" + seed + seed + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE, textValue); - return filledAutofillField; + }, View.AUTOFILL_TYPE_TEXT)) .put(W3cHints.CC_TYPE, new AutofillHintProperties(W3cHints.CC_TYPE, SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD, PARTITION_CREDIT_CARD, (seed, datasetId) -> { String textValue = "type" + seed; - FilledAutofillField filledAutofillField = - new FilledAutofillField(datasetId, W3cHints.CC_TYPE, - textValue); - return filledAutofillField; + return new FilledAutofillField( + datasetId, W3cHints.CC_TYPE, textValue); }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.TRANSACTION_CURRENCY, new AutofillHintProperties( W3cHints.TRANSACTION_CURRENCY, SaveInfo.SAVE_DATA_TYPE_GENERIC, @@ -478,19 +444,17 @@ public final class AutofillHints { String textValueFromListValue = exampleCurrencies[seed % exampleCurrencies.length] .toString(); - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.TRANSACTION_CURRENCY, textValueFromListValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.TRANSACTION_AMOUNT, new AutofillHintProperties( W3cHints.TRANSACTION_AMOUNT, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { String textValue = "" + seed * 100; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.TRANSACTION_AMOUNT, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.LANGUAGE, new AutofillHintProperties(W3cHints.LANGUAGE, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, @@ -499,9 +463,8 @@ public final class AutofillHints { "Danish", "Dutch", "English", "Estonian"}; String textValueFromListValue = exampleLanguages[seed % exampleLanguages.length].toString(); - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.LANGUAGE, textValueFromListValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.BDAY, new AutofillHintProperties(W3cHints.BDAY, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, @@ -511,193 +474,169 @@ public final class AutofillHints { calendar.set(Calendar.MONTH, seed % 12); calendar.set(Calendar.DATE, seed % 27); Long dateValue = calendar.getTimeInMillis(); - FilledAutofillField filledAutofillField = - new FilledAutofillField(datasetId, W3cHints.BDAY, dateValue); - return filledAutofillField; + return new FilledAutofillField(datasetId, W3cHints.BDAY, dateValue); }, View.AUTOFILL_TYPE_DATE)) .put(W3cHints.BDAY_DAY, new AutofillHintProperties(W3cHints.BDAY_DAY, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { String textValue = "" + seed % 27; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.BDAY_DAY, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.BDAY_MONTH, new AutofillHintProperties(W3cHints.BDAY_MONTH, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { String textValue = "" + seed % 12; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.BDAY_MONTH, textValue); - return filledAutofillField; + }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.BDAY_YEAR, new AutofillHintProperties(W3cHints.BDAY_YEAR, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { int year = Calendar.getInstance().get(Calendar.YEAR) - seed * 10; String textValue = "" + year; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.BDAY_YEAR, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.SEX, new AutofillHintProperties(W3cHints.SEX, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { String textValue = "Other"; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.SEX, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.URL, new AutofillHintProperties(W3cHints.URL, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { String textValue = "http://google.com"; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.URL, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT)) .put(W3cHints.PHOTO, new AutofillHintProperties(W3cHints.PHOTO, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { String textValue = "photo" + seed + ".jpg"; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.PHOTO, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.PREFIX_SECTION, new AutofillHintProperties( W3cHints.PREFIX_SECTION, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.PREFIX_SECTION); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.SHIPPING, new AutofillHintProperties(W3cHints.SHIPPING, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_ADDRESS, (seed, datasetId) -> { - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.SHIPPING); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.BILLING, new AutofillHintProperties(W3cHints.BILLING, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_ADDRESS, (seed, datasetId) -> { - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.BILLING); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.PREFIX_HOME, new AutofillHintProperties(W3cHints.PREFIX_HOME, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.PREFIX_HOME); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.PREFIX_WORK, new AutofillHintProperties(W3cHints.PREFIX_WORK, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.PREFIX_WORK); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.PREFIX_FAX, new AutofillHintProperties(W3cHints.PREFIX_FAX, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.PREFIX_FAX); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.PREFIX_PAGER, new AutofillHintProperties(W3cHints.PREFIX_PAGER, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.PREFIX_PAGER); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.TEL, new AutofillHintProperties(W3cHints.TEL, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.TEL); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT)) .put(W3cHints.TEL_COUNTRY_CODE, new AutofillHintProperties( W3cHints.TEL_COUNTRY_CODE, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.TEL_COUNTRY_CODE); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.TEL_NATIONAL, new AutofillHintProperties(W3cHints.TEL_NATIONAL, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.TEL_NATIONAL); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.TEL_AREA_CODE, new AutofillHintProperties( W3cHints.TEL_AREA_CODE, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.TEL_AREA_CODE); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.TEL_LOCAL, new AutofillHintProperties( W3cHints.TEL_LOCAL, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.TEL_LOCAL); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.TEL_LOCAL_PREFIX, new AutofillHintProperties( W3cHints.TEL_LOCAL_PREFIX, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.TEL_LOCAL_PREFIX); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.TEL_LOCAL_SUFFIX, new AutofillHintProperties( W3cHints.TEL_LOCAL_SUFFIX, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.TEL_LOCAL_SUFFIX); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.TEL_EXTENSION, new AutofillHintProperties(W3cHints.TEL_EXTENSION, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_OTHER, (seed, datasetId) -> { - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.TEL_EXTENSION); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .put(W3cHints.EMAIL, new AutofillHintProperties( View.AUTOFILL_HINT_EMAIL_ADDRESS, SaveInfo.SAVE_DATA_TYPE_GENERIC, PARTITION_EMAIL, (seed, datasetId) -> { String textValue = "email" + seed; - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, View.AUTOFILL_HINT_EMAIL_ADDRESS, textValue); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT)) .put(W3cHints.IMPP, new AutofillHintProperties(W3cHints.IMPP, SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS, PARTITION_EMAIL, (seed, datasetId) -> { - FilledAutofillField filledAutofillField = new FilledAutofillField( + return new FilledAutofillField( datasetId, W3cHints.IMPP); - return filledAutofillField; }, View.AUTOFILL_TYPE_TEXT, View.AUTOFILL_TYPE_LIST)) .build(); private AutofillHints() { } - public static boolean isValidTypeForHints(String hint, int type) { - if (hint != null && sValidHints.containsKey(hint)) { + public static boolean isValidTypeForHints(@NonNull String hint, int type) { + if (sValidHints.containsKey(hint)) { boolean valid = sValidHints.get(hint).isValidType(type); if (valid) { return true; @@ -706,49 +645,85 @@ public final class AutofillHints { return false; } - public static boolean isValidHint(String hint) { + public static boolean isValidHint(@NonNull String hint) { return sValidHints.containsKey(hint); } - public static int getSaveTypeForHints(String[] hints) { - int saveType = 0; - if (hints != null) { - for (String hint : hints) { - if (hint != null && sValidHints.containsKey(hint)) { - saveType |= sValidHints.get(hint).getSaveType(); - } - } + public static int getSaveTypeForHint(@NonNull String hint) { + if (sValidHints.containsKey(hint)) { + return sValidHints.get(hint).getSaveType(); + } else { + return 0; } - return saveType; } - public static FilledAutofillField getFakeField(String hint, int seed, String datasetId) { - return sValidHints.get(hint).generateFakeField(seed, datasetId); + public static FilledAutofillField generateFakeField(@NonNull String hint, int seed, + String datasetId) { + if (isValidHint(hint)) { + return sValidHints.get(hint).generateFakeField(seed, datasetId); + } else { + return null; + } } - public static FilledAutofillFieldCollection getFakeFieldCollection(int partition, int seed) { - String datasetName = "dataset-" + seed; - String datasetId = UUID.randomUUID().toString(); - AutofillDataset autofillDataset = new AutofillDataset(datasetId, datasetName); - FilledAutofillFieldCollection filledAutofillFieldCollection = - new FilledAutofillFieldCollection(autofillDataset); - for (String hint : sValidHints.keySet()) { - if (hint != null && sValidHints.get(hint).getPartition() == partition) { - FilledAutofillField fakeField = getFakeField(hint, seed, datasetId); - filledAutofillFieldCollection.add(fakeField); - } - } - return filledAutofillFieldCollection; + public static ImmutableSet<String> getHints() { + return sValidHints.keySet(); + } + + public static List<String> convertToStoredHintNames(@NonNull List<String> hints) { + return convertToStoredHintNames(hints, PARTITION_ALL); + } + + public static List<String> convertToStoredHintNames(@NonNull List<String> hints, int partition) { + return removePrefixes(hints) + .stream() + .filter(sValidHints::containsKey) + .map(sValidHints::get) + .filter(Objects::nonNull) + .filter((properties) -> matchesPartition(properties, partition)) + .map(AutofillHintProperties::getAutofillHint) + .collect(toList()); } - private static String getStoredHintName(String hint) { - return sValidHints.get(hint).getAutofillHint(); + public static boolean matchesPartition(@NonNull String hint, int partition) { + return isValidHint(hint) && matchesPartition(sValidHints.get(hint), partition); } - public static void convertToStoredHintNames(String[] hints) { - for (int i = 0; i < hints.length; i++) { - hints[i] = getStoredHintName(hints[i]); + private static boolean matchesPartition(@NonNull AutofillHintProperties properties, + int partition) { + return partition == PARTITION_ALL || properties.getPartition() == partition; + } + + private static List<String> removePrefixes(@NonNull List<String> hints) { + List<String> hintsWithoutPrefixes = new ArrayList<>(); + String nextHint = null; + for (int i = 0; i < hints.size(); i++) { + String hint = hints.get(i); + if (i < hints.size() - 1) { + nextHint = hints.get(i + 1); + } + // First convert the compound W3C autofill hints + if (isW3cSectionPrefix(hint) && i < hints.size() - 1) { + i++; + hint = hints.get(i); + logd("Hint is a W3C section prefix; using %s instead", hint); + if (i < hints.size() - 1) { + nextHint = hints.get(i + 1); + } + } + if (isW3cTypePrefix(hint) && nextHint != null && isW3cTypeHint(nextHint)) { + hint = nextHint; + i++; + logd("Hint is a W3C type prefix; using %s instead", hint); + } + if (isW3cAddressType(hint) && nextHint != null) { + hint = nextHint; + i++; + logd("Hint is a W3C address prefix; using %s instead", hint); + } + hintsWithoutPrefixes.add(hint); } + return hintsWithoutPrefixes; } private static CharSequence[] dayRange() { @@ -767,29 +742,11 @@ public final class AutofillHints { return months; } - public static String[] filterForSupportedHints(String[] hints) { - String[] filteredHints = new String[hints.length]; - int i = 0; - for (String hint : hints) { - if (AutofillHints.isValidHint(hint)) { - filteredHints[i++] = hint; - } else { - logw("Invalid autofill hint: %s", hint); - } - } - if (i == 0) { - return null; - } - String[] finalFilteredHints = new String[i]; - System.arraycopy(filteredHints, 0, finalFilteredHints, 0, i); - return finalFilteredHints; - } - - public static boolean isW3cSectionPrefix(String hint) { + private static boolean isW3cSectionPrefix(@NonNull String hint) { return hint.startsWith(W3cHints.PREFIX_SECTION); } - public static boolean isW3cAddressType(String hint) { + private static boolean isW3cAddressType(@NonNull String hint) { switch (hint) { case W3cHints.SHIPPING: case W3cHints.BILLING: @@ -798,7 +755,7 @@ public final class AutofillHints { return false; } - public static boolean isW3cTypePrefix(String hint) { + private static boolean isW3cTypePrefix(@NonNull String hint) { switch (hint) { case W3cHints.PREFIX_WORK: case W3cHints.PREFIX_FAX: @@ -809,7 +766,7 @@ public final class AutofillHints { return false; } - public static boolean isW3cTypeHint(String hint) { + private static boolean isW3cTypeHint(@NonNull String hint) { switch (hint) { case W3cHints.TEL: case W3cHints.TEL_COUNTRY_CODE: diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/MyAutofillService.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/MyAutofillService.java index 0bef584e..ba2065d8 100644 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/MyAutofillService.java +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/MyAutofillService.java @@ -16,7 +16,9 @@ package com.example.android.autofill.service; import android.app.assist.AssistStructure; +import android.content.Context; import android.content.IntentSender; +import android.content.SharedPreferences; import android.os.Bundle; import android.os.CancellationSignal; import android.service.autofill.AutofillService; @@ -31,21 +33,28 @@ import android.view.View; import android.view.autofill.AutofillId; import android.widget.RemoteViews; -import com.example.android.autofill.service.datasource.DataCallback; -import com.example.android.autofill.service.datasource.PackageVerificationDataSource; -import com.example.android.autofill.service.datasource.local.LocalAutofillDataSource; -import com.example.android.autofill.service.datasource.local.DigitalAssetLinksRepository; -import com.example.android.autofill.service.datasource.local.SharedPrefsPackageVerificationRepository; -import com.example.android.autofill.service.model.FilledAutofillFieldCollection; +import com.example.android.autofill.service.data.DataCallback; +import com.example.android.autofill.service.data.adapter.DatasetAdapter; +import com.example.android.autofill.service.data.adapter.ResponseAdapter; +import com.example.android.autofill.service.data.AutofillDataBuilder; +import com.example.android.autofill.service.data.ClientAutofillDataBuilder; +import com.example.android.autofill.service.data.ClientViewMetadata; +import com.example.android.autofill.service.data.source.PackageVerificationDataSource; +import com.example.android.autofill.service.data.source.local.DigitalAssetLinksRepository; +import com.example.android.autofill.service.data.source.local.LocalAutofillDataSource; +import com.example.android.autofill.service.data.source.local.SharedPrefsPackageVerificationRepository; +import com.example.android.autofill.service.data.source.local.dao.AutofillDao; +import com.example.android.autofill.service.data.source.local.db.AutofillDatabase; +import com.example.android.autofill.service.model.DalCheck; +import com.example.android.autofill.service.model.DalInfo; +import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields; import com.example.android.autofill.service.settings.MyPreferences; import com.example.android.autofill.service.util.AppExecutors; import com.example.android.autofill.service.util.Util; -import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import static com.example.android.autofill.service.AutofillHelper.CLIENT_STATE_PARTIAL_ID_TEMPLATE; +import static com.example.android.autofill.service.data.adapter.ResponseAdapter.CLIENT_STATE_PARTIAL_ID_TEMPLATE; import static com.example.android.autofill.service.util.Util.AUTOFILL_ID_FILTER; import static com.example.android.autofill.service.util.Util.bundleToString; import static com.example.android.autofill.service.util.Util.dumpStructure; @@ -60,82 +69,74 @@ public class MyAutofillService extends AutofillService { private LocalAutofillDataSource mLocalAutofillDataSource; private DigitalAssetLinksRepository mDalRepository; private PackageVerificationDataSource mPackageVerificationRepository; + private AutofillDataBuilder mAutofillDataBuilder; + private DatasetAdapter mDatasetAdapter; + private ResponseAdapter mResponseAdapter; + private ClientViewMetadata mClientViewMetadata; @Override public void onCreate() { super.onCreate(); Util.setLoggingLevel(MyPreferences.getInstance(this).getLoggingLevel()); - mLocalAutofillDataSource = LocalAutofillDataSource.getInstance(this, new AppExecutors()); - mDalRepository = DigitalAssetLinksRepository.getInstance(this); + SharedPreferences localAfDataSourceSharedPrefs = + getSharedPreferences(LocalAutofillDataSource.SHARED_PREF_KEY, Context.MODE_PRIVATE); + AutofillDao autofillDao = AutofillDatabase.getInstance(this).autofillDao(); + mLocalAutofillDataSource = LocalAutofillDataSource.getInstance(localAfDataSourceSharedPrefs, + autofillDao, new AppExecutors()); + mDalRepository = DigitalAssetLinksRepository.getInstance(getPackageManager()); mPackageVerificationRepository = SharedPrefsPackageVerificationRepository.getInstance(this); } @Override public void onFillRequest(@NonNull FillRequest request, - @NonNull CancellationSignal cancellationSignal, - @NonNull FillCallback callback) { + @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback) { AssistStructure structure = request.getFillContexts() .get(request.getFillContexts().size() - 1).getStructure(); + StructureParser parser = new StructureParser(structure); + mDatasetAdapter = new DatasetAdapter(parser); + mClientViewMetadata = new ClientViewMetadata(parser); + mResponseAdapter = new ResponseAdapter(this, mClientViewMetadata, + getPackageName(), mDatasetAdapter, request.getClientState()); String packageName = structure.getActivityComponent().getPackageName(); if (!mPackageVerificationRepository.putPackageSignatures(packageName)) { - callback.onFailure( - getApplicationContext().getString(R.string.invalid_package_signature)); + callback.onFailure(getString(R.string.invalid_package_signature)); return; } final Bundle clientState = request.getClientState(); if (logVerboseEnabled()) { logv("onFillRequest(): clientState=%s", bundleToString(clientState)); + dumpStructure(structure); } - dumpStructure(structure); - cancellationSignal.setOnCancelListener(() -> logw("Cancel autofill not implemented in this sample.") ); - // Parse AutoFill data in Activity - StructureParser parser = new StructureParser(getApplicationContext(), structure, - mLocalAutofillDataSource, mDalRepository); - // TODO: try / catch on other places (onSave, auth activity, etc...) - try { - parser.parseForFill(); - } catch (SecurityException e) { - // TODO: handle cases where DAL didn't pass by showing a custom UI asking the user - // to confirm the mapping. Might require subclassing SecurityException. - logw(e, "Security exception handling %s", request); - callback.onFailure(e.getMessage()); - return; - } - AutofillFieldMetadataCollection autofillFields = parser.getAutofillFields(); - FillResponse.Builder responseBuilder = new FillResponse.Builder(); // Check user's settings for authenticating Responses and Datasets. boolean responseAuth = MyPreferences.getInstance(this).isResponseAuth(); - AutofillId[] autofillIds = autofillFields.getAutofillIds(); - if (responseAuth && !Arrays.asList(autofillIds).isEmpty()) { + if (responseAuth) { // If the entire Autofill Response is authenticated, AuthActivity is used // to generate Response. IntentSender sender = AuthActivity.getAuthIntentSenderForResponse(this); - RemoteViews presentation = AutofillHelper - .newRemoteViews(getPackageName(), getString(R.string.autofill_sign_in_prompt), - R.drawable.ic_lock_black_24dp); - responseBuilder - .setAuthentication(autofillIds, sender, presentation); - callback.onSuccess(responseBuilder.build()); + RemoteViews remoteViews = RemoteViewsHelper.viewsWithAuth(getPackageName(), + getString(R.string.autofill_sign_in_prompt)); + FillResponse response = mResponseAdapter.buildResponse(sender, remoteViews); + if (response != null) { + callback.onSuccess(response); + } } else { boolean datasetAuth = MyPreferences.getInstance(this).isDatasetAuth(); - mLocalAutofillDataSource.getFilledAutofillFieldCollection( - autofillFields.getFocusedHints(), autofillFields.getAllHints(), - new DataCallback<HashMap<String, FilledAutofillFieldCollection>>() { + mLocalAutofillDataSource.getAutofillDatasets(mClientViewMetadata.getAllHints(), + new DataCallback<List<DatasetWithFilledAutofillFields>>() { @Override - public void onLoaded(HashMap<String, FilledAutofillFieldCollection> - clientFormDataMap) { - FillResponse response = AutofillHelper.newResponse - (MyAutofillService.this, clientState, datasetAuth, - autofillFields, clientFormDataMap); + public void onLoaded(List<DatasetWithFilledAutofillFields> datasets) { + FillResponse response = mResponseAdapter.buildResponse(datasets, + datasetAuth); callback.onSuccess(response); } @Override public void onDataNotAvailable(String msg, Object... params) { logw(msg, params); + callback.onFailure(String.format(msg, params)); } }); } @@ -146,6 +147,9 @@ public class MyAutofillService extends AutofillService { List<FillContext> fillContexts = request.getFillContexts(); int size = fillContexts.size(); AssistStructure structure = fillContexts.get(size - 1).getStructure(); + StructureParser parser = new StructureParser(structure); + mAutofillDataBuilder = new ClientAutofillDataBuilder(parser); + mClientViewMetadata = new ClientViewMetadata(parser); String packageName = structure.getActivityComponent().getPackageName(); if (!mPackageVerificationRepository.putPackageSignatures(packageName)) { callback.onFailure(getApplicationContext().getString(R.string.invalid_package_signature)); @@ -159,13 +163,13 @@ public class MyAutofillService extends AutofillService { // TODO: hardcode check for partial username if (clientState != null) { - String usernameKey = - String.format(CLIENT_STATE_PARTIAL_ID_TEMPLATE, View.AUTOFILL_HINT_USERNAME); + String usernameKey = String.format(CLIENT_STATE_PARTIAL_ID_TEMPLATE, + View.AUTOFILL_HINT_USERNAME); AutofillId usernameId = clientState.getParcelable(usernameKey); logd("client state for %s: %s", usernameKey, usernameId); if (usernameId != null) { - String passwordKey = - String.format(CLIENT_STATE_PARTIAL_ID_TEMPLATE, View.AUTOFILL_HINT_PASSWORD); + String passwordKey = String.format(CLIENT_STATE_PARTIAL_ID_TEMPLATE, + View.AUTOFILL_HINT_PASSWORD); AutofillId passwordId = clientState.getParcelable(passwordKey); logd("Scanning %d contexts for username ID %s and password ID %s.", size, @@ -192,12 +196,50 @@ public class MyAutofillService extends AutofillService { } } } + checkWebDomainAndBuildAutofillData(packageName, callback); + } + + private void checkWebDomainAndBuildAutofillData(String packageName, SaveCallback callback) { + String webDomain; + try { + webDomain = mClientViewMetadata.buildWebDomain(); + } catch(SecurityException e) { + logw(e.getMessage()); + callback.onFailure(e.getMessage()); + return; + } + if (webDomain != null && webDomain.length() > 0) { + mDalRepository.checkValid(new DalInfo(webDomain, packageName), + new DataCallback<DalCheck>() { + @Override + public void onLoaded(DalCheck dalCheck) { + if (dalCheck.linked) { + logd("Domain %s is valid for %s", webDomain, packageName); + buildAndSaveAutofillData(); + } else { + callback.onFailure(String.format( + "Could not associate web domain %s with app %s", webDomain, + packageName)); + } + } + + @Override + public void onDataNotAvailable(String msg, Object... params) { + logw(msg, params); + callback.onFailure(String.format(msg, params)); + } + }); + } else { + logd("no web domain"); + buildAndSaveAutofillData(); + } + } - StructureParser parser = new StructureParser(getApplicationContext(), structure, - mLocalAutofillDataSource, mDalRepository); - parser.parseForSave(); - FilledAutofillFieldCollection filledAutofillFieldCollection = parser.getClientFormData(); - mLocalAutofillDataSource.saveFilledAutofillFieldCollection(filledAutofillFieldCollection); + private void buildAndSaveAutofillData() { + int datasetNumber = mLocalAutofillDataSource.getDatasetNumber(); + List<DatasetWithFilledAutofillFields> datasetsWithFilledAutofillFields = + mAutofillDataBuilder.buildDatasetsByPartition(datasetNumber); + mLocalAutofillDataSource.saveAutofillDatasets(datasetsWithFilledAutofillFields); } @Override diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/RemoteViewsHelper.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/RemoteViewsHelper.java new file mode 100644 index 00000000..b925b95a --- /dev/null +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/RemoteViewsHelper.java @@ -0,0 +1,44 @@ +/* + * 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.autofill.service; + +import android.support.annotation.DrawableRes; +import android.widget.RemoteViews; + +/** + * This is a class containing helper methods for building Autofill Datasets and Responses. + */ +public final class RemoteViewsHelper { + private RemoteViewsHelper() { + } + + public static RemoteViews viewsWithAuth(String packageName, String text) { + return simpleRemoteViews(packageName, text, R.drawable.ic_lock_black_24dp); + } + + public static RemoteViews viewsWithNoAuth(String packageName, String text) { + return simpleRemoteViews(packageName, text, R.drawable.ic_person_black_24dp); + } + + private static RemoteViews simpleRemoteViews(String packageName, String remoteViewsText, + @DrawableRes int drawableId) { + RemoteViews presentation = new RemoteViews(packageName, + R.layout.multidataset_service_list_item); + presentation.setTextViewText(R.id.text, remoteViewsText); + presentation.setImageViewResource(R.id.icon, drawableId); + return presentation; + } +} diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/StructureParser.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/StructureParser.java index 3bfbd96d..54296f36 100644 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/StructureParser.java +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/StructureParser.java @@ -13,167 +13,52 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.example.android.autofill.service; import android.app.assist.AssistStructure; -import android.app.assist.AssistStructure.ViewNode; -import android.app.assist.AssistStructure.WindowNode; -import android.content.Context; -import android.view.autofill.AutofillValue; - -import com.example.android.autofill.service.datasource.DataCallback; -import com.example.android.autofill.service.datasource.local.DigitalAssetLinksRepository; -import com.example.android.autofill.service.datasource.local.LocalAutofillDataSource; -import com.example.android.autofill.service.model.AutofillDataset; -import com.example.android.autofill.service.model.DalCheck; -import com.example.android.autofill.service.model.DalInfo; -import com.example.android.autofill.service.model.FilledAutofillField; -import com.example.android.autofill.service.model.FilledAutofillFieldCollection; +import android.support.annotation.NonNull; -import java.util.List; -import java.util.UUID; +import com.google.common.base.Preconditions; -import static com.example.android.autofill.service.util.Util.logd; -import static com.example.android.autofill.service.util.Util.logw; +import static android.app.assist.AssistStructure.ViewNode; /** - * Parser for an AssistStructure object. This is invoked when the Autofill Service receives an - * AssistStructure from the client Activity, representing its View hierarchy. In this sample, it - * parses the hierarchy and collects autofill metadata from {@link ViewNode}s along the way. + * Wrapper for {@link AssistStructure} to make it easy to parse. */ -final class StructureParser { - private final AutofillFieldMetadataCollection mAutofillFields = - new AutofillFieldMetadataCollection(); - private final LocalAutofillDataSource mLocalAutofillDataSource; - private final DigitalAssetLinksRepository mDalRepository; +public final class StructureParser { private final AssistStructure mStructure; - private FilledAutofillFieldCollection mFilledAutofillFieldCollection; - StructureParser(Context context, AssistStructure structure, - LocalAutofillDataSource localAutofillDataSource, - DigitalAssetLinksRepository dalRepository) { - mLocalAutofillDataSource = localAutofillDataSource; - mDalRepository = dalRepository; + public StructureParser(@NonNull AssistStructure structure) { + Preconditions.checkNotNull(structure); mStructure = structure; } - public void parseForFill() { - parse(true); - } - - public void parseForSave() { - parse(false); - } - /** - * Traverse AssistStructure and add ViewNode metadata to a flat list. + * Traverses through the {@link AssistStructure} and does something at each {@link ViewNode}. + * + * @param processor contains action to be performed on each {@link ViewNode}. */ - private void parse(boolean forFill) { - logd("Parsing structure for %s", mStructure.getActivityComponent()); + public void parse(NodeProcessor processor) { int nodes = mStructure.getWindowNodeCount(); - String datasetName = "dataset-" + mLocalAutofillDataSource.getDatasetNumber(); - String datasetId = UUID.randomUUID().toString(); - AutofillDataset dataset = new AutofillDataset(datasetId, datasetName); - mFilledAutofillFieldCollection = new FilledAutofillFieldCollection(dataset); - StringBuilder webDomain = new StringBuilder(); for (int i = 0; i < nodes; i++) { - WindowNode node = mStructure.getWindowNodeAt(i); - ViewNode view = node.getRootViewNode(); - parseLocked(forFill, view, webDomain); - } - if (webDomain.length() > 0) { - String packageName = mStructure.getActivityComponent().getPackageName(); - mDalRepository.checkValid(new DalInfo(webDomain.toString(), packageName), - new DataCallback<DalCheck>() { - @Override - public void onLoaded(DalCheck dalCheck) { - if (dalCheck.linked) { - logd("Domain %s is valid for %s", webDomain, packageName); - } else { - throw new SecurityException(String.format( - "Could not associate web domain %s with app %s", webDomain, - packageName)); - } - } - - @Override - public void onDataNotAvailable(String msg, Object... params) { - logw(msg, params); - throw new SecurityException(String.format( - "Could not associate web domain %s with app %s", webDomain, - packageName)); - } - }); - } else { - logd("no web domain"); + AssistStructure.ViewNode viewNode = mStructure.getWindowNodeAt(i).getRootViewNode(); + traverseRoot(viewNode, processor); } } - private void parseLocked(boolean forFill, ViewNode viewNode, StringBuilder validWebDomain) { - String webDomain = viewNode.getWebDomain(); - if (webDomain != null) { - logd("child web domain: %s", webDomain); - if (validWebDomain.length() > 0) { - if (!webDomain.equals(validWebDomain.toString())) { - throw new SecurityException("Found multiple web domains: valid= " - + validWebDomain + ", child=" + webDomain); - } - } else { - validWebDomain.append(webDomain); - } - } - - if (viewNode.getAutofillHints() != null) { - String[] filteredHints = AutofillHints.filterForSupportedHints( - viewNode.getAutofillHints()); - if (filteredHints != null && filteredHints.length > 0) { - if (forFill) { - mAutofillFields.add(new AutofillFieldMetadata(viewNode)); - } else { - AutofillValue autofillValue = viewNode.getAutofillValue(); - String textValue = null; - Long dateValue = null; - Boolean toggleValue = null; - CharSequence[] autofillOptions = null; - Integer listIndex = null; - if (autofillValue != null) { - if (autofillValue.isText()) { - // Using toString of AutofillValue.getTextValue in order to save it to - // SharedPreferences. - textValue = autofillValue.getTextValue().toString(); - } else if (autofillValue.isDate()) { - dateValue = autofillValue.getDateValue(); - } else if (autofillValue.isList()) { - autofillOptions = viewNode.getAutofillOptions(); - listIndex = autofillValue.getListValue(); - } else if (autofillValue.isToggle()) { - toggleValue = autofillValue.getToggleValue(); - } - } - List<FilledAutofillField> filledAutofillFields = - FilledAutofillField.build( - mFilledAutofillFieldCollection.getDataset().getId(), - viewNode.getAutofillHints(), - textValue, dateValue, toggleValue, autofillOptions, listIndex); - if (filledAutofillFields != null) { - mFilledAutofillFieldCollection.add(filledAutofillFields); - } - } - } - } + private void traverseRoot(AssistStructure.ViewNode viewNode, NodeProcessor processor) { + processor.processNode(viewNode); int childrenSize = viewNode.getChildCount(); if (childrenSize > 0) { for (int i = 0; i < childrenSize; i++) { - parseLocked(forFill, viewNode.getChildAt(i), validWebDomain); + traverseRoot(viewNode.getChildAt(i), processor); } } } - public AutofillFieldMetadataCollection getAutofillFields() { - return mAutofillFields; - } - public FilledAutofillFieldCollection getClientFormData() { - return mFilledAutofillFieldCollection; + public interface NodeProcessor { + void processNode(ViewNode node); } } diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/AutofillDataBuilder.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/AutofillDataBuilder.java new file mode 100644 index 00000000..0110bdd2 --- /dev/null +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/AutofillDataBuilder.java @@ -0,0 +1,25 @@ +/* + * 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.autofill.service.data; + +import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields; + +import java.util.List; + +public interface AutofillDataBuilder { + List<DatasetWithFilledAutofillFields> buildDatasetsByPartition(int datasetNumber); +} diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientAutofillDataBuilder.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientAutofillDataBuilder.java new file mode 100644 index 00000000..5d18ae0c --- /dev/null +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientAutofillDataBuilder.java @@ -0,0 +1,157 @@ +/* + * 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.autofill.service.data; + +import android.app.assist.AssistStructure; +import android.support.annotation.NonNull; +import android.view.View; +import android.view.autofill.AutofillValue; + +import com.example.android.autofill.service.AutofillHints; +import com.example.android.autofill.service.StructureParser; +import com.example.android.autofill.service.model.AutofillDataset; +import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields; +import com.example.android.autofill.service.model.FilledAutofillField; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +import javax.annotation.Nullable; + +import static com.example.android.autofill.service.AutofillHints.convertToStoredHintNames; +import static com.example.android.autofill.service.util.Util.loge; + +public class ClientAutofillDataBuilder implements AutofillDataBuilder { + private final StructureParser mStructureParser; + + public ClientAutofillDataBuilder(StructureParser structureParser) { + mStructureParser = structureParser; + } + + @Override + public List<DatasetWithFilledAutofillFields> buildDatasetsByPartition(int datasetNumber) { + ImmutableList.Builder<DatasetWithFilledAutofillFields> listBuilder = + new ImmutableList.Builder<>(); + for (int partition : AutofillHints.PARTITIONS) { + AutofillDataset autofillDataset = new AutofillDataset(UUID.randomUUID().toString(), + "dataset-" + datasetNumber + "." + partition); + DatasetWithFilledAutofillFields datasetWithFilledAutofillFields = + buildDatasetForPartition(autofillDataset, partition); + if (datasetWithFilledAutofillFields != null) { + listBuilder.add(datasetWithFilledAutofillFields); + } + } + return listBuilder.build(); + } + + /** + * Parses a client view structure and build a dataset (in the form of a + * {@link DatasetWithFilledAutofillFields}) from the view metadata found. + */ + private DatasetWithFilledAutofillFields buildDatasetForPartition(AutofillDataset dataset, + int partition) { + DatasetWithFilledAutofillFields datasetWithFilledAutofillFields = + new DatasetWithFilledAutofillFields(); + datasetWithFilledAutofillFields.autofillDataset = dataset; + mStructureParser.parse((node) -> + parseAutofillFields(node, datasetWithFilledAutofillFields, partition) + ); + if (datasetWithFilledAutofillFields.filledAutofillFields == null) { + return null; + } else { + return datasetWithFilledAutofillFields; + } + } + + private void parseAutofillFields(AssistStructure.ViewNode viewNode, + DatasetWithFilledAutofillFields datasetWithFilledAutofillFields, int partition) { + String[] hints = viewNode.getAutofillHints(); + if (hints == null) { + return; + } + List<String> filteredHints = convertToStoredHintNames(Arrays.asList(hints), partition); + if (filteredHints == null || filteredHints.size() == 0) { + return; + } + AutofillValue autofillValue = viewNode.getAutofillValue(); + String textValue = null; + Long dateValue = null; + Boolean toggleValue = null; + CharSequence[] autofillOptions = null; + Integer listIndex = null; + if (autofillValue != null) { + if (autofillValue.isText()) { + // Using toString of AutofillValue.getTextValue in order to save it to + // SharedPreferences. + textValue = autofillValue.getTextValue().toString(); + } else if (autofillValue.isDate()) { + dateValue = autofillValue.getDateValue(); + } else if (autofillValue.isList()) { + autofillOptions = viewNode.getAutofillOptions(); + listIndex = autofillValue.getListValue(); + } else if (autofillValue.isToggle()) { + toggleValue = autofillValue.getToggleValue(); + } + } + appendViewMetadata(datasetWithFilledAutofillFields, + filteredHints, textValue, dateValue, toggleValue, + autofillOptions, listIndex); + } + + private void appendViewMetadata(@NonNull DatasetWithFilledAutofillFields + datasetWithFilledAutofillFields, @NonNull List<String> hints, + @Nullable String textValue, @Nullable Long dateValue, @Nullable Boolean toggleValue, + @Nullable CharSequence[] autofillOptions, @Nullable Integer listIndex) { + for (int i = 0; i < hints.size(); i++) { + String hint = hints.get(i); + // Then check if the "actual" hint is supported. + if (AutofillHints.isValidHint(hint)) { + // Only add the field if the hint is supported by the type. + if (textValue != null) { + Preconditions.checkArgument(AutofillHints.isValidTypeForHints(hint, + View.AUTOFILL_TYPE_TEXT), + "Text is invalid type for hint '%s'", hint); + } + if (autofillOptions != null && listIndex != null && + autofillOptions.length > listIndex) { + Preconditions.checkArgument(AutofillHints.isValidTypeForHints(hint, + View.AUTOFILL_TYPE_LIST), + "List is invalid type for hint '%s'", hint); + textValue = autofillOptions[listIndex].toString(); + } + if (dateValue != null) { + Preconditions.checkArgument(AutofillHints.isValidTypeForHints(hint, + View.AUTOFILL_TYPE_DATE), + "Date is invalid type for hint '%s'", hint); + } + if (toggleValue != null) { + Preconditions.checkArgument(AutofillHints.isValidTypeForHints(hint, + View.AUTOFILL_TYPE_TOGGLE), + "Toggle is invalid type for hint '%s'", hint); + } + String datasetId = datasetWithFilledAutofillFields.autofillDataset.getId(); + datasetWithFilledAutofillFields.add(new FilledAutofillField(datasetId, + hint, textValue, dateValue, toggleValue)); + } else { + loge("Invalid hint: %s", hint); + } + } + } +} diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientViewMetadata.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientViewMetadata.java new file mode 100644 index 00000000..91f20ff8 --- /dev/null +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/ClientViewMetadata.java @@ -0,0 +1,155 @@ +/* + * 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.autofill.service.data; + +import android.app.assist.AssistStructure; +import android.service.autofill.SaveInfo; +import android.view.autofill.AutofillId; + +import com.example.android.autofill.service.AutofillHints; +import com.example.android.autofill.service.StructureParser; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static com.example.android.autofill.service.util.Util.logd; + +/** + * In this simple implementation, the only view data we parse from the client are autofill hints + * of the views in the view hierarchy, the hints of views that are focused, the corresponding + * autofill IDs, and the {@link SaveInfo} based on the hints. + * <p> + * Note: this class is not thread safe. + */ +public class ClientViewMetadata { + private final StructureParser mStructureParser; + + private List<String> mCachedAllHints; + private Integer mCachedSaveType; + private SaveInfo mCachedSaveInfo; + private List<AutofillId> mCachedAutofillIds; + + public ClientViewMetadata(StructureParser parser) { + mStructureParser = parser; + } + + public List<String> getAllHints() { + if (mCachedAllHints == null) { + parseHints(); + } + return mCachedAllHints; + } + + private List<AutofillId> getAutofillIds() { + if (mCachedAutofillIds == null) { + AutofillSaveType autofillSaveType = new AutofillSaveType(); + List<AutofillId> autofillIds = new ArrayList<>(); + mStructureParser.parse((node) -> parseSaveTypeAndIds(node, autofillSaveType, autofillIds)); + mCachedSaveType = autofillSaveType.saveType; + mCachedAutofillIds = autofillIds; + } + return mCachedAutofillIds; + } + + public AutofillId[] getAutofillIdsArray() { + List<AutofillId> autofillIds = getAutofillIds(); + if (autofillIds == null || autofillIds.isEmpty()) { + return null; + } + return autofillIds.toArray(new AutofillId[autofillIds.size()]); + } + + public SaveInfo getSaveInfo() { + if (mCachedSaveInfo == null) { + int saveType = getSaveType(); + AutofillId[] autofillIdsArray = getAutofillIdsArray(); + // TODO: on MR1, creates a new SaveType without required ids + mCachedSaveInfo = new SaveInfo.Builder(saveType, autofillIdsArray).build(); + } + return mCachedSaveInfo; + } + + public int getSaveType() { + if (mCachedSaveType == null) { + AutofillSaveType autofillSaveType = new AutofillSaveType(); + List<AutofillId> autofillIds = new ArrayList<>(); + mStructureParser.parse((node) -> parseSaveTypeAndIds(node, autofillSaveType, autofillIds)); + mCachedSaveType = autofillSaveType.saveType; + mCachedAutofillIds = autofillIds; + } + return mCachedSaveType; + } + + public String buildWebDomain() { + StringBuilder webDomainBuilder = new StringBuilder(); + mStructureParser.parse((node) -> parseWebDomain(node, webDomainBuilder)); + return webDomainBuilder.toString(); + } + + private void parseWebDomain(AssistStructure.ViewNode viewNode, StringBuilder validWebDomain) { + String webDomain = viewNode.getWebDomain(); + if (webDomain != null) { + logd("child web domain: %s", webDomain); + if (validWebDomain.length() > 0) { + if (!webDomain.equals(validWebDomain.toString())) { + throw new SecurityException("Found multiple web domains: valid= " + + validWebDomain + ", child=" + webDomain); + } + } else { + validWebDomain.append(webDomain); + } + } + } + + private void parseSaveTypeAndIds(AssistStructure.ViewNode root, + AutofillSaveType autofillSaveType, List<AutofillId> autofillIds) { + String[] hints = root.getAutofillHints(); + if (hints != null) { + for (String hint : hints) { + if (AutofillHints.isValidHint(hint)) { + autofillSaveType.saveType |= AutofillHints.getSaveTypeForHint(hint); + autofillIds.add(root.getAutofillId()); + } + } + } + } + + private void parseHints() { + List<String> allHints = new ArrayList<>(); + mStructureParser.parse((node) -> getHints(node, allHints)); + mCachedAllHints = allHints; + } + + private void getHints(AssistStructure.ViewNode node, List<String> allHints) { + if (node.getAutofillHints() != null) { + String[] hints = node.getAutofillHints(); + Collections.addAll(allHints, hints); + } + } + + public void clearCache() { + mCachedAllHints = null; + mCachedSaveType = null; + mCachedSaveInfo = null; + mCachedAutofillIds = null; + } + + private class AutofillSaveType { + int saveType; + } +} diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/datasource/DataCallback.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/DataCallback.java index 432d8c64..e7656028 100644 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/datasource/DataCallback.java +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/DataCallback.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.example.android.autofill.service.datasource; +package com.example.android.autofill.service.data; public interface DataCallback<T> { void onLoaded(T object); diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/FakeAutofillDataBuilder.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/FakeAutofillDataBuilder.java new file mode 100644 index 00000000..580df901 --- /dev/null +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/FakeAutofillDataBuilder.java @@ -0,0 +1,63 @@ +/* + * 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.autofill.service.data; + +import com.example.android.autofill.service.AutofillHints; +import com.example.android.autofill.service.model.AutofillDataset; +import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields; +import com.example.android.autofill.service.model.FilledAutofillField; +import com.google.common.collect.ImmutableList; + +import java.util.List; +import java.util.UUID; + +public class FakeAutofillDataBuilder implements AutofillDataBuilder { + private final int mSeed; + + public FakeAutofillDataBuilder(int seed) { + mSeed = seed; + } + + @Override + public List<DatasetWithFilledAutofillFields> buildDatasetsByPartition(int datasetNumber) { + ImmutableList.Builder<DatasetWithFilledAutofillFields> listBuilder = + new ImmutableList.Builder<>(); + for (int partition : AutofillHints.PARTITIONS) { + AutofillDataset autofillDataset = new AutofillDataset(UUID.randomUUID().toString(), + "dataset-" + datasetNumber + "." + partition); + DatasetWithFilledAutofillFields datasetWithFilledAutofillFields = + buildCollectionForPartition(autofillDataset, partition); + listBuilder.add(datasetWithFilledAutofillFields); + } + return listBuilder.build(); + } + + private DatasetWithFilledAutofillFields buildCollectionForPartition( + AutofillDataset dataset, int partition) { + DatasetWithFilledAutofillFields datasetWithFilledAutofillFields = + new DatasetWithFilledAutofillFields(); + datasetWithFilledAutofillFields.autofillDataset = dataset; + for (String hint : AutofillHints.getHints()) { + if (AutofillHints.matchesPartition(hint, partition)) { + FilledAutofillField fakeField = AutofillHints.generateFakeField(hint, mSeed, + dataset.getId()); + datasetWithFilledAutofillFields.add(fakeField); + } + } + return datasetWithFilledAutofillFields; + } +} diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/adapter/DatasetAdapter.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/adapter/DatasetAdapter.java new file mode 100644 index 00000000..043f4e36 --- /dev/null +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/adapter/DatasetAdapter.java @@ -0,0 +1,167 @@ +/* + * 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.autofill.service.data.adapter; + +import android.app.assist.AssistStructure; +import android.content.IntentSender; +import android.service.autofill.Dataset; +import android.support.annotation.NonNull; +import android.util.MutableBoolean; +import android.view.View; +import android.view.autofill.AutofillId; +import android.view.autofill.AutofillValue; +import android.widget.RemoteViews; + +import com.example.android.autofill.service.AutofillHints; +import com.example.android.autofill.service.StructureParser; +import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields; +import com.example.android.autofill.service.model.FilledAutofillField; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import static com.example.android.autofill.service.util.Util.logv; +import static com.example.android.autofill.service.util.Util.logw; +import static java.util.stream.Collectors.toMap; + +public class DatasetAdapter { + private final StructureParser mStructureParser; + + public DatasetAdapter(StructureParser structureParser) { + mStructureParser = structureParser; + } + + /** + * Helper method for getting the index of a CharSequence object in an array. + */ + private static int indexOf(@NonNull CharSequence[] array, CharSequence charSequence) { + int index = -1; + if (charSequence == null) { + return index; + } + for (int i = 0; i < array.length; i++) { + if (charSequence.equals(array[i])) { + index = i; + break; + } + } + return index; + } + + /** + * Wraps autofill data in a {@link Dataset} object which can then be sent back to the client. + */ + public Dataset buildDataset(DatasetWithFilledAutofillFields datasetWithFilledAutofillFields, + RemoteViews remoteViews) { + return buildDataset(datasetWithFilledAutofillFields, remoteViews, null); + } + + /** + * Wraps autofill data in a {@link Dataset} object with an IntentSender, which can then be + * sent back to the client. + */ + public Dataset buildDataset(DatasetWithFilledAutofillFields datasetWithFilledAutofillFields, + RemoteViews remoteViews, IntentSender intentSender) { + Dataset.Builder datasetBuilder = new Dataset.Builder(remoteViews); + if (intentSender != null) { + datasetBuilder.setAuthentication(intentSender); + } + boolean setAtLeastOneValue = bindDataset(datasetWithFilledAutofillFields, datasetBuilder); + if (!setAtLeastOneValue) { + return null; + } + return datasetBuilder.build(); + } + + /** + * Build an autofill {@link Dataset} using saved data and the client's AssistStructure. + */ + private boolean bindDataset(DatasetWithFilledAutofillFields datasetWithFilledAutofillFields, + Dataset.Builder datasetBuilder) { + MutableBoolean setValueAtLeastOnce = new MutableBoolean(false); + Map<String, FilledAutofillField> map = datasetWithFilledAutofillFields.filledAutofillFields + .stream().collect(toMap(FilledAutofillField::getHint, Function.identity())); + mStructureParser.parse((node) -> + parseAutofillFields(node, map, datasetBuilder, setValueAtLeastOnce) + ); + return setValueAtLeastOnce.value; + } + + private void parseAutofillFields(AssistStructure.ViewNode viewNode, + Map<String, FilledAutofillField> map, Dataset.Builder builder, + MutableBoolean setValueAtLeastOnce) { + String[] rawHints = viewNode.getAutofillHints(); + if (rawHints == null || rawHints.length == 0) { + logv("No af hints at ViewNode - %s", viewNode.getIdEntry()); + return; + } + List<String> hints = AutofillHints.convertToStoredHintNames(Arrays.asList(rawHints)); + // For simplicity, even if the viewNode has multiple autofill hints, only look at the first + // one. + String autofillHint = hints.get(0); + FilledAutofillField field = map.get(autofillHint); + if (field == null) { + return; + } + AutofillId autofillId = viewNode.getAutofillId(); + if (autofillId == null) { + logw("Autofill ID null for %s", viewNode.toString()); + return; + } + int autofillType = viewNode.getAutofillType(); + switch (autofillType) { + case View.AUTOFILL_TYPE_LIST: + CharSequence[] options = viewNode.getAutofillOptions(); + int listValue = -1; + if (options != null) { + listValue = indexOf(viewNode.getAutofillOptions(), field.getTextValue()); + } + if (listValue != -1) { + builder.setValue(autofillId, AutofillValue.forList(listValue)); + setValueAtLeastOnce.value = true; + } + break; + case View.AUTOFILL_TYPE_DATE: + Long dateValue = field.getDateValue(); + if (dateValue != null) { + builder.setValue(autofillId, AutofillValue.forDate(dateValue)); + setValueAtLeastOnce.value = true; + } + break; + case View.AUTOFILL_TYPE_TEXT: + String textValue = field.getTextValue(); + if (textValue != null) { + builder.setValue(autofillId, AutofillValue.forText(textValue)); + setValueAtLeastOnce.value = true; + } + break; + case View.AUTOFILL_TYPE_TOGGLE: + Boolean toggleValue = field.getToggleValue(); + if (toggleValue != null) { + builder.setValue(autofillId, AutofillValue.forToggle(toggleValue)); + setValueAtLeastOnce.value = true; + } + break; + case View.AUTOFILL_TYPE_NONE: + default: + logw("Invalid autofill type - %d", autofillType); + break; + } + } +} diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/adapter/ResponseAdapter.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/adapter/ResponseAdapter.java new file mode 100644 index 00000000..f74a1999 --- /dev/null +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/adapter/ResponseAdapter.java @@ -0,0 +1,159 @@ +/* + * 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.autofill.service.data.adapter; + +import android.content.Context; +import android.content.IntentSender; +import android.os.Bundle; +import android.service.autofill.Dataset; +import android.service.autofill.FillResponse; +import android.service.autofill.SaveInfo; +import android.view.View; +import android.view.autofill.AutofillId; +import android.widget.RemoteViews; + +import com.example.android.autofill.service.AuthActivity; +import com.example.android.autofill.service.RemoteViewsHelper; +import com.example.android.autofill.service.data.ClientViewMetadata; +import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields; + +import java.util.Arrays; +import java.util.List; + +import static com.example.android.autofill.service.util.Util.bundleToString; +import static com.example.android.autofill.service.util.Util.getSaveTypeAsString; +import static com.example.android.autofill.service.util.Util.logDebugEnabled; +import static com.example.android.autofill.service.util.Util.logd; + +public class ResponseAdapter { + public static final String CLIENT_STATE_PARTIAL_ID_TEMPLATE = "partial-%s"; + // TODO: move to settings activity and document it + private static final boolean SUPPORT_MULTIPLE_STEPS = true; + + private final Context mContext; + private final DatasetAdapter mDatasetAdapter; + private final String mPackageName; + private final ClientViewMetadata mClientViewMetadata; + private final Bundle mPreviousClientState; + + public ResponseAdapter(Context context, ClientViewMetadata clientViewMetadata, + String packageName, DatasetAdapter datasetAdapter, Bundle clientState) { + mContext = context; + mClientViewMetadata = clientViewMetadata; + mDatasetAdapter = datasetAdapter; + mPackageName = packageName; + mPreviousClientState = clientState; + } + + /** + * Wraps autofill data in a Response object (essentially a series of Datasets) which can then + * be sent back to the client View. + */ + public FillResponse buildResponse(List<DatasetWithFilledAutofillFields> datasets, + boolean datasetAuth) { + FillResponse.Builder responseBuilder = new FillResponse.Builder(); + if (datasets != null) { + for (DatasetWithFilledAutofillFields datasetWithFilledAutofillFields : datasets) { + if (datasetWithFilledAutofillFields != null) { + Dataset dataset; + String datasetName = datasetWithFilledAutofillFields.autofillDataset + .getDatasetName(); + if (datasetAuth) { + IntentSender intentSender = AuthActivity.getAuthIntentSenderForDataset( + mContext, datasetName); + RemoteViews remoteViews = RemoteViewsHelper.viewsWithAuth( + mPackageName, datasetName); + dataset = mDatasetAdapter.buildDataset(datasetWithFilledAutofillFields, + remoteViews, intentSender); + } else { + RemoteViews remoteViews = RemoteViewsHelper.viewsWithNoAuth( + mPackageName, datasetName); + dataset = mDatasetAdapter.buildDataset(datasetWithFilledAutofillFields, + remoteViews); + } + if (dataset != null) { + responseBuilder.addDataset(dataset); + } + } + } + } + + int saveType = mClientViewMetadata.getSaveType(); + AutofillId[] autofillIds = mClientViewMetadata.getAutofillIdsArray(); + List<String> allHints = mClientViewMetadata.getAllHints(); + if (logDebugEnabled()) { + logd("setPartialSaveInfo() for type %s: allHints=%s, ids=%s, clientState=%s", + getSaveTypeAsString(saveType), allHints, Arrays.toString(autofillIds), + bundleToString(mPreviousClientState)); + } + // TODO: this should be more generic, but for now it's hardcode to support just activities + // that have an username and a password in separate steps (like MultipleStepsSigninActivity) + if ((saveType != SaveInfo.SAVE_DATA_TYPE_USERNAME + && saveType != SaveInfo.SAVE_DATA_TYPE_PASSWORD) + || autofillIds.length != 1 || allHints.size() != 1) { + logd("Unsupported activity for partial info; returning full"); + responseBuilder.setSaveInfo(mClientViewMetadata.getSaveInfo()); + return responseBuilder.build(); + } + int previousSaveType; + String previousHint; + if (saveType == SaveInfo.SAVE_DATA_TYPE_PASSWORD) { + previousHint = View.AUTOFILL_HINT_USERNAME; + previousSaveType = SaveInfo.SAVE_DATA_TYPE_USERNAME; + } else { + previousHint = View.AUTOFILL_HINT_PASSWORD; + previousSaveType = SaveInfo.SAVE_DATA_TYPE_PASSWORD; + } + String previousKey = String.format(CLIENT_STATE_PARTIAL_ID_TEMPLATE, previousHint); + + AutofillId previousValue = mPreviousClientState == null ? null : mPreviousClientState + .getParcelable(previousKey); + logd("previous: %s=%s", previousKey, previousValue); + + Bundle newClientState = new Bundle(); + String key = String.format(CLIENT_STATE_PARTIAL_ID_TEMPLATE, allHints.get(0)); + AutofillId value = autofillIds[0]; + logd("New client state: %s = %s", key, value); + newClientState.putParcelable(key, value); + + if (previousValue != null) { + AutofillId[] newIds = new AutofillId[]{previousValue, value}; + int newSaveType = saveType | previousSaveType; + logd("new values: type=%s, ids=%s", + getSaveTypeAsString(newSaveType), Arrays.toString(newIds)); + newClientState.putAll(mPreviousClientState); + responseBuilder.setSaveInfo(new SaveInfo.Builder(newSaveType, newIds) + .setFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) + .build()) + .setClientState(newClientState); + + return responseBuilder.build(); + } + responseBuilder.setClientState(newClientState); + responseBuilder.setSaveInfo(mClientViewMetadata.getSaveInfo()); + return responseBuilder.build(); + } + + public FillResponse buildResponse(IntentSender sender, RemoteViews remoteViews) { + FillResponse.Builder responseBuilder = new FillResponse.Builder(); + AutofillId[] autofillIds = mClientViewMetadata.getAutofillIdsArray(); + SaveInfo saveInfo = mClientViewMetadata.getSaveInfo(); + responseBuilder.setAuthentication(autofillIds, sender, remoteViews); + responseBuilder.setSaveInfo(saveInfo); + return responseBuilder.build(); + } +} diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/AutofillDataSource.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/AutofillDataSource.java new file mode 100644 index 00000000..31b30b44 --- /dev/null +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/AutofillDataSource.java @@ -0,0 +1,50 @@ +/* + * 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.autofill.service.data.source; + +import com.example.android.autofill.service.data.DataCallback; +import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields; + +import java.util.List; + +public interface AutofillDataSource { + + /** + * Asynchronously gets saved list of {@link DatasetWithFilledAutofillFields} that contains some + * objects that can autofill fields with these {@code autofillHints}. + */ + void getAutofillDatasets(List<String> allAutofillHints, + DataCallback<List<DatasetWithFilledAutofillFields>> datasetsCallback); + + /** + * Asynchronously gets a saved {@link DatasetWithFilledAutofillFields} for a specific + * {@code datasetName} that contains some objects that can autofill fields with these + * {@code autofillHints}. + */ + void getAutofillDataset(List<String> allAutofillHints, + String datasetName, DataCallback<DatasetWithFilledAutofillFields> datasetsCallback); + + /** + * Stores a collection of Autofill fields. + */ + void saveAutofillDatasets(List<DatasetWithFilledAutofillFields> + datasetsWithFilledAutofillFields); + + /** + * Clears all data. + */ + void clear(); +} diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/datasource/DalService.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/DalService.java index 05b27e3f..b9cf6956 100644 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/datasource/DalService.java +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/DalService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.example.android.autofill.service.datasource; +package com.example.android.autofill.service.data.source; import com.example.android.autofill.service.model.DalCheck; @@ -22,7 +22,6 @@ import retrofit2.Call; import retrofit2.http.GET; import retrofit2.http.Query; - public interface DalService { @GET("/v1/assetlinks:check") Call<DalCheck> check(@Query("source.web.site") String webDomain, diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/datasource/DigitalAssetLinksDataSource.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/DigitalAssetLinksDataSource.java index 2070c53d..c01573b0 100644 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/datasource/DigitalAssetLinksDataSource.java +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/DigitalAssetLinksDataSource.java @@ -13,14 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.example.android.autofill.service.datasource; +package com.example.android.autofill.service.data.source; +import com.example.android.autofill.service.data.DataCallback; import com.example.android.autofill.service.model.DalCheck; import com.example.android.autofill.service.model.DalInfo; /** - * Helper format - * <a href="https://developers.google.com/digital-asset-links/">Digital Asset Links</a> needs. + * Data source for + * <a href="https://developers.google.com/digital-asset-links/">Digital Asset Links</a>. */ public interface DigitalAssetLinksDataSource { diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/datasource/PackageVerificationDataSource.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/PackageVerificationDataSource.java index 10930cfe..7e271e02 100644 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/datasource/PackageVerificationDataSource.java +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/PackageVerificationDataSource.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.example.android.autofill.service.datasource; +package com.example.android.autofill.service.data.source; public interface PackageVerificationDataSource { diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/datasource/local/DigitalAssetLinksRepository.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/DigitalAssetLinksRepository.java index ba5f37e3..dfcecab4 100644 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/datasource/local/DigitalAssetLinksRepository.java +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/DigitalAssetLinksRepository.java @@ -13,17 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.example.android.autofill.service.datasource.local; +package com.example.android.autofill.service.data.source.local; -import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.support.annotation.NonNull; -import com.example.android.autofill.service.SecurityHelper; -import com.example.android.autofill.service.datasource.DalService; -import com.example.android.autofill.service.datasource.DataCallback; -import com.example.android.autofill.service.datasource.DigitalAssetLinksDataSource; +import com.example.android.autofill.service.util.SecurityHelper; +import com.example.android.autofill.service.data.source.DalService; +import com.example.android.autofill.service.data.DataCallback; +import com.example.android.autofill.service.data.source.DigitalAssetLinksDataSource; import com.example.android.autofill.service.model.DalCheck; import com.example.android.autofill.service.model.DalInfo; import com.google.common.net.InternetDomainName; @@ -47,12 +46,13 @@ public class DigitalAssetLinksRepository implements DigitalAssetLinksDataSource private static final String PERMISSION_GET_LOGIN_CREDS = "common.get_login_creds"; private static final String PERMISSION_HANDLE_ALL_URLS = "common.handle_all_urls"; private static DigitalAssetLinksRepository sInstance; - private final Context mContext; + + private final PackageManager mPackageManager; private final DalService mDalService; private final HashMap<DalInfo, DalCheck> mCache; - private DigitalAssetLinksRepository(Context context) { - mContext = context; + private DigitalAssetLinksRepository(PackageManager packageManager) { + mPackageManager = packageManager; mCache = new HashMap<>(); mDalService = new Retrofit.Builder() .baseUrl(DAL_BASE_URL) @@ -60,9 +60,9 @@ public class DigitalAssetLinksRepository implements DigitalAssetLinksDataSource .create(DalService.class); } - public static DigitalAssetLinksRepository getInstance(Context context) { + public static DigitalAssetLinksRepository getInstance(PackageManager packageManager) { if (sInstance == null) { - sInstance = new DigitalAssetLinksRepository(context.getApplicationContext()); + sInstance = new DigitalAssetLinksRepository(packageManager); } return sInstance; } @@ -80,8 +80,7 @@ public class DigitalAssetLinksRepository implements DigitalAssetLinksDataSource mCache.clear(); } - public void checkValid(DalInfo dalInfo, - DataCallback<DalCheck> dalCheckDataCallback) { + public void checkValid(DalInfo dalInfo, DataCallback<DalCheck> dalCheckDataCallback) { DalCheck dalCheck = mCache.get(dalInfo); if (dalCheck != null) { dalCheckDataCallback.onLoaded(dalCheck); @@ -92,8 +91,8 @@ public class DigitalAssetLinksRepository implements DigitalAssetLinksDataSource final String fingerprint; try { - PackageManager pm = mContext.getPackageManager(); - PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); + PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, + PackageManager.GET_SIGNATURES); fingerprint = SecurityHelper.getFingerprint(packageInfo, packageName); } catch (Exception e) { dalCheckDataCallback.onDataNotAvailable("Error getting fingerprint for %s", diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/LocalAutofillDataSource.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/LocalAutofillDataSource.java new file mode 100644 index 00000000..3c326103 --- /dev/null +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/LocalAutofillDataSource.java @@ -0,0 +1,148 @@ +/* + * 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.autofill.service.data.source.local; + +import android.content.SharedPreferences; +import android.service.autofill.Dataset; + +import com.example.android.autofill.service.AutofillHints; +import com.example.android.autofill.service.data.DataCallback; +import com.example.android.autofill.service.data.source.AutofillDataSource; +import com.example.android.autofill.service.data.source.local.dao.AutofillDao; +import com.example.android.autofill.service.model.AutofillDataset; +import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields; +import com.example.android.autofill.service.model.FilledAutofillField; +import com.example.android.autofill.service.util.AppExecutors; + +import java.util.List; + +import static com.example.android.autofill.service.util.Util.logw; + +public class LocalAutofillDataSource implements AutofillDataSource { + public static final String SHARED_PREF_KEY = "com.example.android.autofill" + + ".service.datasource.LocalAutofillDataSource"; + private static final String DATASET_NUMBER_KEY = "datasetNumber"; + private static final Object sLock = new Object(); + + private static LocalAutofillDataSource sInstance; + + private final AutofillDao mAutofillDao; + private final SharedPreferences mSharedPreferences; + private final AppExecutors mAppExecutors; + + private LocalAutofillDataSource(SharedPreferences sharedPreferences, AutofillDao autofillDao, + AppExecutors appExecutors) { + mSharedPreferences = sharedPreferences; + mAutofillDao = autofillDao; + mAppExecutors = appExecutors; + } + + public static LocalAutofillDataSource getInstance(SharedPreferences sharedPreferences, + AutofillDao autofillDao, AppExecutors appExecutors) { + synchronized (sLock) { + if (sInstance == null) { + sInstance = new LocalAutofillDataSource(sharedPreferences, autofillDao, + appExecutors); + } + return sInstance; + } + } + + public static void clearInstance() { + synchronized (sLock) { + sInstance = null; + } + } + + @Override + public void getAutofillDatasets(List<String> allAutofillHints, + DataCallback<List<DatasetWithFilledAutofillFields>> datasetsCallback) { + final List<String> storedAllAutofillHints = + AutofillHints.convertToStoredHintNames(allAutofillHints); + mAppExecutors.diskIO().execute(() -> { + List<DatasetWithFilledAutofillFields> datasetsWithFilledAutofillFields = mAutofillDao + .getFilledAutofillFields(storedAllAutofillHints); + mAppExecutors.mainThread().execute(() -> + datasetsCallback.onLoaded(datasetsWithFilledAutofillFields) + ); + }); + } + + @Override + public void getAutofillDataset(List<String> allAutofillHints, String datasetName, + DataCallback<DatasetWithFilledAutofillFields> datasetsCallback) { + mAppExecutors.diskIO().execute(() -> { + // Room does not support TypeConverters for collections. + List<DatasetWithFilledAutofillFields> autofillDatasetFields = + mAutofillDao.getFilledAutofillFieldsWithName(allAutofillHints, datasetName); + if (autofillDatasetFields != null && !autofillDatasetFields.isEmpty()) { + if (autofillDatasetFields.size() > 1) { + logw("More than 1 dataset with name %s", datasetName); + } + DatasetWithFilledAutofillFields dataset = autofillDatasetFields.get(0); + + mAppExecutors.mainThread().execute(() -> + datasetsCallback.onLoaded(dataset) + ); + } else { + datasetsCallback.onDataNotAvailable("No data found."); + } + }); + } + + + @Override + public void saveAutofillDatasets(List<DatasetWithFilledAutofillFields> + datasetsWithFilledAutofillFields) { + mAppExecutors.diskIO().execute(() -> { + for (DatasetWithFilledAutofillFields datasetWithFilledAutofillFields : + datasetsWithFilledAutofillFields) { + List<FilledAutofillField> filledAutofillFields = + datasetWithFilledAutofillFields.filledAutofillFields; + AutofillDataset autofillDataset = datasetWithFilledAutofillFields.autofillDataset; + mAutofillDao.saveAutofillDataset(autofillDataset); + mAutofillDao.saveFilledAutofillFields(filledAutofillFields); + } + }); + incrementDatasetNumber(); + } + + @Override + public void clear() { + mAppExecutors.diskIO().execute(() -> { + mAutofillDao.clearAll(); + mSharedPreferences.edit().putInt(DATASET_NUMBER_KEY, 0).apply(); + }); + } + + /** + * For simplicity, {@link Dataset}s will be named in the form {@code dataset-X.P} where + * {@code X} means this was the Xth group of datasets saved, and {@code P} refers to the dataset + * partition number. This method returns the appropriate {@code X}. + */ + public int getDatasetNumber() { + return mSharedPreferences.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() { + mSharedPreferences.edit().putInt(DATASET_NUMBER_KEY, getDatasetNumber() + 1).apply(); + } +} diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/datasource/local/SharedPrefsPackageVerificationRepository.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/SharedPrefsPackageVerificationRepository.java index fc2993d3..5c462137 100644 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/datasource/local/SharedPrefsPackageVerificationRepository.java +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/SharedPrefsPackageVerificationRepository.java @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.example.android.autofill.service.datasource.local; +package com.example.android.autofill.service.data.source.local; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import com.example.android.autofill.service.SecurityHelper; -import com.example.android.autofill.service.datasource.PackageVerificationDataSource; +import com.example.android.autofill.service.util.SecurityHelper; +import com.example.android.autofill.service.data.source.PackageVerificationDataSource; import static com.example.android.autofill.service.util.Util.logd; import static com.example.android.autofill.service.util.Util.logw; diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/dao/AutofillDao.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/dao/AutofillDao.java new file mode 100644 index 00000000..8a8c6a92 --- /dev/null +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/dao/AutofillDao.java @@ -0,0 +1,71 @@ +/* + * 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.autofill.service.data.source.local.dao; + +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.OnConflictStrategy; +import android.arch.persistence.room.Query; + +import com.example.android.autofill.service.model.AutofillDataset; +import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields; +import com.example.android.autofill.service.model.FilledAutofillField; + +import java.util.Collection; +import java.util.List; + +@Dao +public interface AutofillDao { + /** + * Fetches a list of datasets associated to autofill fields on the page. + * + * @param allAutofillHints Filtering parameter; represents all of the hints associated with + * all of the views on the page. + */ + @Query("SELECT DISTINCT id, datasetName FROM FilledAutofillField, AutofillDataset" + + " WHERE AutofillDataset.id = FilledAutofillField.datasetId" + + " AND FilledAutofillField.hint IN (:allAutofillHints)") + List<DatasetWithFilledAutofillFields> getFilledAutofillFields(List<String> allAutofillHints); + + /** + * Fetches a list of datasets associated to autofill fields. It should only return a dataset + * if that dataset has an autofill field associate with the view the user is focused on, and + * if that dataset's name matches the name passed in. + * + * @param allAutofillHints Filtering parameter; represents all of the hints associated with + * all of the views on the page. + * @param datasetName Filtering parameter; only return datasets with this name. + */ + @Query("SELECT id, datasetname FROM FilledAutofillField, AutofillDataset" + + " WHERE AutofillDataset.id = FilledAutofillField.datasetId" + + " AND AutofillDataset.datasetName = (:datasetName)" + + " AND FilledAutofillField.hint IN (:allAutofillHints)") + List<DatasetWithFilledAutofillFields> getFilledAutofillFieldsWithName( + List<String> allAutofillHints, String datasetName); + + /** + * @param autofillFields Collection of autofill fields to be saved to the db. + */ + @Insert(onConflict = OnConflictStrategy.REPLACE) + void saveFilledAutofillFields(Collection<FilledAutofillField> autofillFields); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + void saveAutofillDataset(AutofillDataset datasets); + + @Query("DELETE FROM AutofillDataset") + void clearAll(); +}
\ No newline at end of file diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/datasource/local/db/AutofillDatabase.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/db/AutofillDatabase.java index a01e542b..83eda9e6 100644 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/datasource/local/db/AutofillDatabase.java +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/data/source/local/db/AutofillDatabase.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package com.example.android.autofill.service.datasource.local.db; +package com.example.android.autofill.service.data.source.local.db; import android.arch.persistence.room.Database; import android.arch.persistence.room.Room; import android.arch.persistence.room.RoomDatabase; import android.content.Context; -import com.example.android.autofill.service.datasource.local.dao.AutofillDao; +import com.example.android.autofill.service.data.source.local.dao.AutofillDao; import com.example.android.autofill.service.model.AutofillDataset; import com.example.android.autofill.service.model.FilledAutofillField; diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/datasource/AutofillDataSource.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/datasource/AutofillDataSource.java deleted file mode 100644 index 53e24738..00000000 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/datasource/AutofillDataSource.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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.autofill.service.datasource; - -import com.example.android.autofill.service.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}. - */ - void getFilledAutofillFieldCollection( - List<String> focusedAutofillHints, List<String> allAutofillHints, - DataCallback<HashMap<String, FilledAutofillFieldCollection>> datasetsCallback); - - /** - * Stores a collection of Autofill fields. - */ - void saveFilledAutofillFieldCollection(FilledAutofillFieldCollection - filledAutofillFieldCollection); - - /** - * Clears all data. - */ - void clear(); -} diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/datasource/local/LocalAutofillDataSource.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/datasource/local/LocalAutofillDataSource.java deleted file mode 100644 index ce5857ce..00000000 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/datasource/local/LocalAutofillDataSource.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * 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.autofill.service.datasource.local; - -import android.content.Context; -import android.content.SharedPreferences; - -import com.example.android.autofill.service.datasource.AutofillDataSource; -import com.example.android.autofill.service.datasource.DataCallback; -import com.example.android.autofill.service.datasource.local.dao.AutofillDao; -import com.example.android.autofill.service.datasource.local.db.AutofillDatabase; -import com.example.android.autofill.service.model.AutofillDataset; -import com.example.android.autofill.service.model.FilledAutofillField; -import com.example.android.autofill.service.model.FilledAutofillFieldCollection; -import com.example.android.autofill.service.util.AppExecutors; - -import java.util.Collection; -import java.util.HashMap; -import java.util.List; - -public class LocalAutofillDataSource implements AutofillDataSource { - private static final String SHARED_PREF_KEY = "com.example.android.autofill" - + ".service.datasource.LocalAutofillDataSource"; - private static final String DATASET_NUMBER_KEY = "datasetNumber"; - private static final Object sLock = new Object(); - - private static LocalAutofillDataSource sInstance; - - private final AutofillDao mAutofillDao; - private final SharedPreferences mSharedPreferences; - private final AppExecutors mAppExecutors; - - private LocalAutofillDataSource(Context context, AppExecutors appExecutors) { - mSharedPreferences = context.getApplicationContext() - .getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE); - mAutofillDao = AutofillDatabase.getInstance(context).autofillDao(); - mAppExecutors = appExecutors; - } - - public static LocalAutofillDataSource getInstance(Context context, AppExecutors appExecutors) { - synchronized (sLock) { - if (sInstance == null) { - sInstance = new LocalAutofillDataSource(context, appExecutors); - } - return sInstance; - } - } - - @Override - public void getFilledAutofillFieldCollection(List<String> focusedAutofillHints, - List<String> allAutofillHints, - DataCallback<HashMap<String, FilledAutofillFieldCollection>> datasetsCallback) { - mAppExecutors.diskIO().execute(() -> { - List<AutofillDao.AutofillDatasetField> autofillDatasetFields = - mAutofillDao.getFilledAutofillFields(focusedAutofillHints, allAutofillHints); - // Convert to hashmap; Room does not support TypeConverters for list. - HashMap<String, FilledAutofillFieldCollection> map = new HashMap<>(); - for (AutofillDao.AutofillDatasetField autofillDatasetField : autofillDatasetFields) { - String datasetName = autofillDatasetField.dataset.getDatasetName(); - if (!map.containsKey(datasetName)) { - map.put(datasetName, new FilledAutofillFieldCollection(autofillDatasetField.dataset)); - } - map.get(datasetName).add(autofillDatasetField.filledAutofillField); - } - mAppExecutors.mainThread().execute(() -> - datasetsCallback.onLoaded(map) - ); - }); - } - - @Override - public void saveFilledAutofillFieldCollection(FilledAutofillFieldCollection - filledAutofillFieldCollection) { - mAppExecutors.diskIO().execute(() -> { - Collection<FilledAutofillField> filledAutofillFields = - filledAutofillFieldCollection.getAllFields(); - AutofillDataset autofillDataset = filledAutofillFieldCollection.getDataset(); - mAutofillDao.saveAutofillDataset(autofillDataset); - mAutofillDao.saveFilledAutofillFields(filledAutofillFields); - incrementDatasetNumber(); - }); - } - - @Override - public void clear() { - mAppExecutors.diskIO().execute(() -> { - mAutofillDao.clearAll(); - mSharedPreferences.edit().putInt(DATASET_NUMBER_KEY, 0).apply(); - }); - } - - /** - * For simplicity, datasets will be named in the form "dataset-X" where X means - * this was the Xth dataset saved. - */ - public int getDatasetNumber() { - return mSharedPreferences.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() { - mSharedPreferences.edit().putInt(DATASET_NUMBER_KEY, getDatasetNumber() + 1).apply(); - } -} diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/datasource/local/dao/AutofillDao.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/datasource/local/dao/AutofillDao.java deleted file mode 100644 index d68c4cfd..00000000 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/datasource/local/dao/AutofillDao.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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.autofill.service.datasource.local.dao; - -import android.arch.persistence.room.Dao; -import android.arch.persistence.room.Embedded; -import android.arch.persistence.room.Insert; -import android.arch.persistence.room.Query; -import android.support.annotation.NonNull; - -import com.example.android.autofill.service.model.AutofillDataset; -import com.example.android.autofill.service.model.FilledAutofillField; - -import java.util.Collection; -import java.util.List; - -@Dao -public interface AutofillDao { - /** - * Fetches a map of dataset names to associated autofill fields. It should only return a dataset - * if that dataset has an autofill field associate with the view the user is focused on. - * - * @param focusedAutofillHints Filtering parameter; represents the hints associated with the - * view the user is focused on. - * @param allAutofillHints Filtering parameter; represents all of the hints associated with - * all of the views on the page. - * @return Map of dataset names to associated autofill fields. - */ - @Query("SELECT * FROM FilledAutofillField, AutofillDataset" + - " WHERE AutofillDataset.id = FilledAutofillField.datasetId" + - " AND FilledAutofillField.hint IN (:allAutofillHints) AND AutofillDataset.id in " + - "(SELECT datasetId FROM FilledAutofillField WHERE hint in (:focusedAutofillHints))") - List<AutofillDatasetField> getFilledAutofillFields( - List<String> focusedAutofillHints, - List<String> allAutofillHints); - - /** - * @param autofillFields Collection of autofill fields to be saved to the db. - */ - @Insert - void saveFilledAutofillFields( - Collection<FilledAutofillField> autofillFields); - - @Insert - void saveAutofillDataset(AutofillDataset datasets); - - @Query("DELETE FROM AutofillDataset") - void clearAll(); - - /** - * Intermediate POJO class for Room. - */ - class AutofillDatasetField { - @Embedded public final AutofillDataset dataset; - @Embedded public final FilledAutofillField filledAutofillField; - - public AutofillDatasetField(AutofillDataset dataset, - FilledAutofillField filledAutofillField) { - this.dataset = dataset; - this.filledAutofillField = filledAutofillField; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - AutofillDatasetField that = (AutofillDatasetField) o; - - if (dataset != null ? !dataset.equals(that.dataset) : that.dataset != null) - return false; - return filledAutofillField != null ? filledAutofillField.equals( - that.filledAutofillField) : that.filledAutofillField == null; - } - - @Override - public int hashCode() { - int result = dataset != null ? dataset.hashCode() : 0; - result = 31 * result + (filledAutofillField != null ? - filledAutofillField.hashCode() : 0); - return result; - } - } -}
\ No newline at end of file diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/AutofillDataset.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/AutofillDataset.java index 2f36ec85..e7bd3058 100644 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/AutofillDataset.java +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/AutofillDataset.java @@ -35,11 +35,13 @@ public class AutofillDataset { mDatasetName = datasetName; } - @NonNull public String getId() { + @NonNull + public String getId() { return mId; } - @NonNull public String getDatasetName() { + @NonNull + public String getDatasetName() { return mDatasetName; } diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DalInfo.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DalInfo.java index 653983fa..44002ca5 100644 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DalInfo.java +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DalInfo.java @@ -16,7 +16,7 @@ package com.example.android.autofill.service.model; -import static com.example.android.autofill.service.datasource.local.DigitalAssetLinksRepository.getCanonicalDomain; +import static com.example.android.autofill.service.data.source.local.DigitalAssetLinksRepository.getCanonicalDomain; public class DalInfo { private final String mWebDomain; diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DatasetWithFilledAutofillFields.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DatasetWithFilledAutofillFields.java new file mode 100644 index 00000000..06b529fe --- /dev/null +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/DatasetWithFilledAutofillFields.java @@ -0,0 +1,60 @@ +/* + * 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.autofill.service.model; + +import android.arch.persistence.room.Embedded; +import android.arch.persistence.room.Relation; + +import java.util.ArrayList; +import java.util.List; + +public class DatasetWithFilledAutofillFields { + @Embedded + public AutofillDataset autofillDataset; + + @Relation(parentColumn = "id", entityColumn = "datasetId", entity = FilledAutofillField.class) + public List<FilledAutofillField> filledAutofillFields; + + public void add(FilledAutofillField filledAutofillField) { + if (filledAutofillFields == null) { + this.filledAutofillFields = new ArrayList<>(); + } + this.filledAutofillFields.add(filledAutofillField); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + DatasetWithFilledAutofillFields that = (DatasetWithFilledAutofillFields) o; + + if (autofillDataset != null ? !autofillDataset.equals(that.autofillDataset) : + that.autofillDataset != null) + return false; + return filledAutofillFields != null ? + filledAutofillFields.equals(that.filledAutofillFields) : + that.filledAutofillFields == null; + } + + @Override + public int hashCode() { + int result = autofillDataset != null ? autofillDataset.hashCode() : 0; + result = 31 * result + (filledAutofillFields != null ? filledAutofillFields.hashCode() : 0); + return result; + } +} diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FilledAutofillField.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FilledAutofillField.java index 6cb93602..ba474d2e 100644 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FilledAutofillField.java +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FilledAutofillField.java @@ -20,25 +20,9 @@ import android.arch.persistence.room.Entity; import android.arch.persistence.room.ForeignKey; import android.arch.persistence.room.Ignore; import android.support.annotation.NonNull; -import android.view.View; - -import com.example.android.autofill.service.AutofillHints; -import com.google.common.base.Preconditions; - -import java.util.ArrayList; -import java.util.List; import javax.annotation.Nullable; -import static com.example.android.autofill.service.AutofillHints.convertToStoredHintNames; -import static com.example.android.autofill.service.AutofillHints.filterForSupportedHints; -import static com.example.android.autofill.service.AutofillHints.isW3cAddressType; -import static com.example.android.autofill.service.AutofillHints.isW3cSectionPrefix; -import static com.example.android.autofill.service.AutofillHints.isW3cTypeHint; -import static com.example.android.autofill.service.AutofillHints.isW3cTypePrefix; -import static com.example.android.autofill.service.util.Util.logd; -import static com.example.android.autofill.service.util.Util.loge; - @Entity(primaryKeys = {"datasetId", "hint"}, foreignKeys = @ForeignKey( entity = AutofillDataset.class, parentColumns = "id", childColumns = "datasetId", onDelete = ForeignKey.CASCADE)) @@ -102,75 +86,6 @@ public class FilledAutofillField { this(datasetId, hint, null, null, null); } - - @Nullable - public static List<FilledAutofillField> build(String datasetId, String[] hints, - @Nullable String textValue, @Nullable Long dateValue, @Nullable Boolean toggleValue, - @Nullable CharSequence[] autofillOptions, @Nullable Integer listIndex) { - String[] filteredHints = filterForSupportedHints(hints); - convertToStoredHintNames(filteredHints); - List<FilledAutofillField> fields = new ArrayList<>(); - if (filteredHints == null) { - return null; - } - String nextHint = null; - for (int i = 0; i < filteredHints.length; i++) { - String hint = filteredHints[i]; - if (i < filteredHints.length - 1) { - nextHint = filteredHints[i + 1]; - } - // First convert the compound W3C autofill hints - if (isW3cSectionPrefix(hint) && i < filteredHints.length - 1) { - hint = filteredHints[++i]; - logd("Hint is a W3C section prefix; using %s instead", hint); - if (i < filteredHints.length - 1) { - nextHint = filteredHints[i + 1]; - } - } - if (isW3cTypePrefix(hint) && nextHint != null && isW3cTypeHint(nextHint)) { - hint = nextHint; - i++; - logd("Hint is a W3C type prefix; using %s instead", hint); - } - if (isW3cAddressType(hint) && nextHint != null) { - hint = nextHint; - i++; - logd("Hint is a W3C address prefix; using %s instead", hint); - } - // Then check if the "actual" hint is supported. - if (AutofillHints.isValidHint(hint)) { - // Only add the field if the hint is supported by the type. - if (textValue != null) { - Preconditions.checkArgument(AutofillHints.isValidTypeForHints(hint, - View.AUTOFILL_TYPE_TEXT), - "Text is invalid type for hint '%s'", hint); - } - if (autofillOptions != null && listIndex != null && - autofillOptions.length > listIndex) { - Preconditions.checkArgument(AutofillHints.isValidTypeForHints(hint, - View.AUTOFILL_TYPE_LIST), - "List is invalid type for hint '%s'", hint); - textValue = autofillOptions[listIndex].toString(); - } - if (dateValue != null) { - Preconditions.checkArgument(AutofillHints.isValidTypeForHints(hint, - View.AUTOFILL_TYPE_DATE), - "Date is invalid type for hint '%s'", hint); - } - if (toggleValue != null) { - Preconditions.checkArgument(AutofillHints.isValidTypeForHints(hint, - View.AUTOFILL_TYPE_TOGGLE), - "Toggle is invalid type for hint '%s'", hint); - } - fields.add(new FilledAutofillField(datasetId, filteredHints[i], textValue, - dateValue, toggleValue)); - } else { - loge("Invalid hint: %s", hint); - } - } - return fields; - } - @NonNull public String getDatasetId() { return mDatasetId; diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FilledAutofillFieldCollection.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FilledAutofillFieldCollection.java deleted file mode 100644 index 0413653d..00000000 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/model/FilledAutofillFieldCollection.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * 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.autofill.service.model; - -import android.service.autofill.Dataset; -import android.support.annotation.NonNull; -import android.view.View; -import android.view.autofill.AutofillId; -import android.view.autofill.AutofillValue; - -import com.example.android.autofill.service.AutofillFieldMetadata; -import com.example.android.autofill.service.AutofillFieldMetadataCollection; - -import java.util.Collection; -import java.util.HashMap; -import java.util.List; - -import static com.example.android.autofill.service.util.Util.logw; - -/** - * FilledAutofillFieldCollection is the model that holds all of the data on a client app's page, - * plus the dataset name associated with it. - */ -public final class FilledAutofillFieldCollection { - - private final HashMap<String, FilledAutofillField> mHintMap; - private final AutofillDataset mDataset; - - public FilledAutofillFieldCollection(AutofillDataset dataset) { - mDataset = dataset; - mHintMap = new HashMap<>(); - } - - /** - * Adds a {@code FilledAutofillField} to the collection, indexed by all of its hints. - */ - public void add(@NonNull FilledAutofillField filledAutofillField) { - mHintMap.put(filledAutofillField.getHint(), filledAutofillField); - } - - /** - * Adds a {@code List<FilledAutofillField>} to the collection, indexed by all of its hints. - */ - public void add(@NonNull List<FilledAutofillField> filledAutofillFields) { - for (FilledAutofillField field : filledAutofillFields) { - add(field); - } - } - - public Collection<FilledAutofillField> getAllFields() { - return mHintMap.values(); - } - - /** - * Populates a {@link Dataset.Builder} with appropriate values for each {@link AutofillId} - * in a {@code AutofillFieldMetadataCollection}. - * <p> - * In other words, it constructs an autofill - * {@link Dataset.Builder} by applying saved values (from this {@code FilledAutofillFieldCollection}) - * to Views specified in a {@code AutofillFieldMetadataCollection}, which represents the current - * page the user is on. - */ - public boolean applyToFields(AutofillFieldMetadataCollection autofillFieldMetadataCollection, - Dataset.Builder datasetBuilder) { - boolean setValueAtLeastOnce = false; - List<String> allHints = autofillFieldMetadataCollection.getAllHints(); - for (int hintIndex = 0; hintIndex < allHints.size(); hintIndex++) { - String hint = allHints.get(hintIndex); - List<AutofillFieldMetadata> fillableAutofillFields = - autofillFieldMetadataCollection.getFieldsForHint(hint); - if (fillableAutofillFields == null) { - continue; - } - for (int autofillFieldIndex = 0; autofillFieldIndex < fillableAutofillFields.size(); autofillFieldIndex++) { - FilledAutofillField filledAutofillField = mHintMap.get(hint); - if (filledAutofillField == null) { - continue; - } - AutofillFieldMetadata autofillFieldMetadata = fillableAutofillFields.get(autofillFieldIndex); - AutofillId autofillId = autofillFieldMetadata.getId(); - int autofillType = autofillFieldMetadata.getAutofillType(); - switch (autofillType) { - case View.AUTOFILL_TYPE_LIST: - int listValue = autofillFieldMetadata.getAutofillOptionIndex( - filledAutofillField.getTextValue()); - if (listValue != -1) { - datasetBuilder.setValue(autofillId, AutofillValue.forList(listValue)); - setValueAtLeastOnce = true; - } - break; - case View.AUTOFILL_TYPE_DATE: - Long dateValue = filledAutofillField.getDateValue(); - if (dateValue != null) { - datasetBuilder.setValue(autofillId, AutofillValue.forDate(dateValue)); - setValueAtLeastOnce = true; - } - break; - case View.AUTOFILL_TYPE_TEXT: - String textValue = filledAutofillField.getTextValue(); - if (textValue != null) { - datasetBuilder.setValue(autofillId, AutofillValue.forText(textValue)); - setValueAtLeastOnce = true; - } - break; - case View.AUTOFILL_TYPE_TOGGLE: - Boolean toggleValue = filledAutofillField.getToggleValue(); - if (toggleValue != null) { - datasetBuilder.setValue(autofillId, AutofillValue.forToggle(toggleValue)); - setValueAtLeastOnce = true; - } - break; - case View.AUTOFILL_TYPE_NONE: - default: - logw("Invalid autofill type - %d", autofillType); - break; - } - } - } - return setValueAtLeastOnce; - } - - public AutofillDataset getDataset() { - return mDataset; - } -} diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/settings/SettingsActivity.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/settings/SettingsActivity.java index 70b8ae20..5ccc13ae 100644 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/settings/SettingsActivity.java +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/settings/SettingsActivity.java @@ -15,8 +15,10 @@ */ package com.example.android.autofill.service.settings; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; import android.provider.Settings; @@ -35,14 +37,19 @@ import android.widget.RadioGroup; import android.widget.Switch; import android.widget.TextView; -import com.example.android.autofill.service.AutofillHints; import com.example.android.autofill.service.R; -import com.example.android.autofill.service.datasource.PackageVerificationDataSource; +import com.example.android.autofill.service.data.AutofillDataBuilder; +import com.example.android.autofill.service.data.FakeAutofillDataBuilder; +import com.example.android.autofill.service.data.source.PackageVerificationDataSource; +import com.example.android.autofill.service.data.source.local.LocalAutofillDataSource; +import com.example.android.autofill.service.data.source.local.SharedPrefsPackageVerificationRepository; +import com.example.android.autofill.service.data.source.local.dao.AutofillDao; +import com.example.android.autofill.service.data.source.local.db.AutofillDatabase; +import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields; import com.example.android.autofill.service.util.AppExecutors; import com.example.android.autofill.service.util.Util; -import com.example.android.autofill.service.datasource.local.SharedPrefsPackageVerificationRepository; -import com.example.android.autofill.service.datasource.local.LocalAutofillDataSource; -import com.example.android.autofill.service.model.FilledAutofillFieldCollection; + +import java.util.List; import static com.example.android.autofill.service.util.Util.logd; import static com.example.android.autofill.service.util.Util.logw; @@ -58,8 +65,11 @@ public class SettingsActivity extends AppCompatActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.multidataset_service_settings_activity); - mLocalAutofillDataSource = LocalAutofillDataSource.getInstance(this, - new AppExecutors()); + SharedPreferences localAfDataSourceSharedPrefs = + getSharedPreferences(LocalAutofillDataSource.SHARED_PREF_KEY, Context.MODE_PRIVATE); + AutofillDao autofillDao = AutofillDatabase.getInstance(this).autofillDao(); + mLocalAutofillDataSource = LocalAutofillDataSource.getInstance(localAfDataSourceSharedPrefs, + autofillDao, new AppExecutors()); mAutofillManager = getSystemService(AutofillManager.class); mPackageVerificationDataSource = SharedPrefsPackageVerificationRepository.getInstance(this); @@ -155,7 +165,7 @@ public class SettingsActivity extends AppCompatActivity { .setView(numberOfDatasetsPicker) .setPositiveButton(R.string.settings_ok, (dialog, which) -> { int numOfDatasets = numberOfDatasetsPicker.getValue(); - boolean success = buildAndSaveMockedAutofillFieldCollection(numOfDatasets); + boolean success = buildAndSaveMockedAutofillFieldCollections(numOfDatasets); dialog.dismiss(); if (success) { Snackbar.make(SettingsActivity.this.findViewById(R.id.settings_layout), @@ -168,21 +178,18 @@ public class SettingsActivity extends AppCompatActivity { .create(); } - /** - * Builds mock autofill data and saves it to repository. - */ - private boolean buildAndSaveMockedAutofillFieldCollection(int numOfDatasets) { + public boolean buildAndSaveMockedAutofillFieldCollections(int numOfDatasets) { if (numOfDatasets < 0 || numOfDatasets > 10) { logw("Number of Datasets (%d) out of range.", numOfDatasets); return false; } for (int i = 0; i < numOfDatasets; i++) { - for (int partition : AutofillHints.PARTITIONS) { - FilledAutofillFieldCollection filledAutofillFieldCollection = - AutofillHints.getFakeFieldCollection(partition, i * 2); - mLocalAutofillDataSource.saveFilledAutofillFieldCollection( - filledAutofillFieldCollection); - } + int datasetNumber = mLocalAutofillDataSource.getDatasetNumber(); + AutofillDataBuilder autofillDataBuilder = new FakeAutofillDataBuilder(datasetNumber); + List<DatasetWithFilledAutofillFields> datasetsWithFilledAutofillFields = + autofillDataBuilder.buildDatasetsByPartition(datasetNumber); + // Save datasets to database. + mLocalAutofillDataSource.saveAutofillDatasets(datasetsWithFilledAutofillFields); } return true; } @@ -286,12 +293,12 @@ public class SettingsActivity extends AppCompatActivity { logd(TAG, "onActivityResult(): req=%s", requestCode); switch (requestCode) { case REQUEST_CODE_SET_DEFAULT: - defaultServiceSet(resultCode); + onDefaultServiceSet(resultCode); break; } } - private void defaultServiceSet(int resultCode) { + private void onDefaultServiceSet(int resultCode) { logd(TAG, "resultCode=%d", resultCode); switch (resultCode) { case RESULT_OK: diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/SecurityHelper.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/util/SecurityHelper.java index fb3cf48a..5311a5f9 100644 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/SecurityHelper.java +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/util/SecurityHelper.java @@ -13,9 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.example.android.autofill.service; +package com.example.android.autofill.service.util; -import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; @@ -35,7 +34,7 @@ import java.security.cert.X509Certificate; public final class SecurityHelper { private SecurityHelper() { - throw new UnsupportedOperationException("provides static methods only"); + throw new UnsupportedOperationException("Provides static methods only."); } /** diff --git a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/util/Util.java b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/util/Util.java index f63bfb51..d7b5b45b 100644 --- a/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/util/Util.java +++ b/input/autofill/AutofillFramework/afservice/src/main/java/com/example/android/autofill/service/util/Util.java @@ -37,14 +37,11 @@ import java.util.Set; public final class Util { - private static final String TAG = "AutofillSample"; public static final String EXTRA_DATASET_NAME = "dataset_name"; public static final String EXTRA_FOR_RESPONSE = "for_response"; public static final NodeFilter AUTOFILL_ID_FILTER = (node, id) -> id.equals(node.getAutofillId()); - - public enum LogLevel { OFF, DEBUG, VERBOSE } - + private static final String TAG = "AutofillSample"; public static LogLevel sLoggingLevel = LogLevel.OFF; private static void bundleToString(StringBuilder builder, Bundle data) { @@ -282,6 +279,8 @@ public final class Util { sLoggingLevel = level; } + public enum LogLevel {OFF, DEBUG, VERBOSE} + /** * Helper interface used to filter Assist nodes. */ |