Home | History | Annotate | Download | only in settings
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.example.android.autofill.service.settings;
     17 
     18 import android.content.Context;
     19 import android.content.DialogInterface;
     20 import android.content.Intent;
     21 import android.content.SharedPreferences;
     22 import android.net.Uri;
     23 import android.os.Bundle;
     24 import android.provider.Settings;
     25 import android.support.design.widget.Snackbar;
     26 import android.support.v7.app.AlertDialog;
     27 import android.support.v7.app.AppCompatActivity;
     28 import android.view.LayoutInflater;
     29 import android.view.View;
     30 import android.view.ViewGroup;
     31 import android.view.autofill.AutofillManager;
     32 import android.widget.CompoundButton;
     33 import android.widget.EditText;
     34 import android.widget.ImageView;
     35 import android.widget.NumberPicker;
     36 import android.widget.RadioGroup;
     37 import android.widget.Switch;
     38 import android.widget.TextView;
     39 
     40 import com.example.android.autofill.service.R;
     41 import com.example.android.autofill.service.data.AutofillDataBuilder;
     42 import com.example.android.autofill.service.data.DataCallback;
     43 import com.example.android.autofill.service.data.FakeAutofillDataBuilder;
     44 import com.example.android.autofill.service.data.source.DefaultFieldTypesSource;
     45 import com.example.android.autofill.service.data.source.PackageVerificationDataSource;
     46 import com.example.android.autofill.service.data.source.local.DefaultFieldTypesLocalJsonSource;
     47 import com.example.android.autofill.service.data.source.local.LocalAutofillDataSource;
     48 import com.example.android.autofill.service.data.source.local.SharedPrefsPackageVerificationRepository;
     49 import com.example.android.autofill.service.data.source.local.dao.AutofillDao;
     50 import com.example.android.autofill.service.data.source.local.db.AutofillDatabase;
     51 import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
     52 import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
     53 import com.example.android.autofill.service.util.AppExecutors;
     54 import com.example.android.autofill.service.util.Util;
     55 import com.google.gson.GsonBuilder;
     56 
     57 import java.util.List;
     58 
     59 import static com.example.android.autofill.service.util.Util.DalCheckRequirement.AllUrls;
     60 import static com.example.android.autofill.service.util.Util.DalCheckRequirement.Disabled;
     61 import static com.example.android.autofill.service.util.Util.DalCheckRequirement.LoginOnly;
     62 import static com.example.android.autofill.service.util.Util.logd;
     63 import static com.example.android.autofill.service.util.Util.logw;
     64 
     65 public class SettingsActivity extends AppCompatActivity {
     66     private static final String TAG = "SettingsActivity";
     67     private static final int REQUEST_CODE_SET_DEFAULT = 1;
     68     private AutofillManager mAutofillManager;
     69     private LocalAutofillDataSource mLocalAutofillDataSource;
     70     private PackageVerificationDataSource mPackageVerificationDataSource;
     71     private MyPreferences mPreferences;
     72     private String mPackageName;
     73 
     74     @Override
     75     public void onCreate(Bundle savedInstanceState) {
     76         super.onCreate(savedInstanceState);
     77         setContentView(R.layout.multidataset_service_settings_activity);
     78         SharedPreferences localAfDataSourceSharedPrefs =
     79                 getSharedPreferences(LocalAutofillDataSource.SHARED_PREF_KEY, Context.MODE_PRIVATE);
     80         DefaultFieldTypesSource defaultFieldTypesSource =
     81                 DefaultFieldTypesLocalJsonSource.getInstance(getResources(),
     82                         new GsonBuilder().create());
     83         AutofillDao autofillDao = AutofillDatabase.getInstance(
     84                 this, defaultFieldTypesSource, new AppExecutors()).autofillDao();
     85         mPackageName = getPackageName();
     86         mLocalAutofillDataSource = LocalAutofillDataSource.getInstance(localAfDataSourceSharedPrefs,
     87                 autofillDao, new AppExecutors());
     88         mAutofillManager = getSystemService(AutofillManager.class);
     89         mPackageVerificationDataSource =
     90                 SharedPrefsPackageVerificationRepository.getInstance(this);
     91         mPreferences = MyPreferences.getInstance(this);
     92         setupSettingsSwitch(R.id.settings_auth_responses_container,
     93                 R.id.settings_auth_responses_label,
     94                 R.id.settings_auth_responses_switch,
     95                 mPreferences.isResponseAuth(),
     96                 (compoundButton, isResponseAuth) -> mPreferences.setResponseAuth(isResponseAuth));
     97         setupSettingsSwitch(R.id.settings_auth_datasets_container,
     98                 R.id.settings_auth_datasets_label,
     99                 R.id.settings_auth_datasets_switch,
    100                 mPreferences.isDatasetAuth(),
    101                 (compoundButton, isDatasetAuth) -> mPreferences.setDatasetAuth(isDatasetAuth));
    102         setupSettingsButton(R.id.settings_add_data_container,
    103                 R.id.settings_add_data_label,
    104                 R.id.settings_add_data_icon,
    105                 (view) -> buildAddDataDialog().show());
    106         setupSettingsButton(R.id.settings_clear_data_container,
    107                 R.id.settings_clear_data_label,
    108                 R.id.settings_clear_data_icon,
    109                 (view) -> buildClearDataDialog().show());
    110         setupSettingsButton(R.id.settings_auth_credentials_container,
    111                 R.id.settings_auth_credentials_label,
    112                 R.id.settings_auth_credentials_icon,
    113                 (view) -> {
    114                     if (mPreferences.getMasterPassword() != null) {
    115                         buildCurrentCredentialsDialog().show();
    116                     } else {
    117                         buildNewCredentialsDialog().show();
    118                     }
    119                 });
    120         setupSettingsSwitch(R.id.settingsSetServiceContainer,
    121                 R.id.settingsSetServiceLabel,
    122                 R.id.settingsSetServiceSwitch,
    123                 mAutofillManager.hasEnabledAutofillServices(),
    124                 (compoundButton, serviceSet) -> setService(serviceSet));
    125         RadioGroup loggingLevelContainer = findViewById(R.id.loggingLevelContainer);
    126         Util.LogLevel loggingLevel = mPreferences.getLoggingLevel();
    127         Util.setLoggingLevel(loggingLevel);
    128         switch (loggingLevel) {
    129             case Off:
    130                 loggingLevelContainer.check(R.id.loggingOff);
    131                 break;
    132             case Debug:
    133                 loggingLevelContainer.check(R.id.loggingDebug);
    134                 break;
    135             case Verbose:
    136                 loggingLevelContainer.check(R.id.loggingVerbose);
    137                 break;
    138         }
    139         loggingLevelContainer.setOnCheckedChangeListener((group, checkedId) -> {
    140             switch (checkedId) {
    141                 case R.id.loggingOff:
    142                     mPreferences.setLoggingLevel(Util.LogLevel.Off);
    143                     break;
    144                 case R.id.loggingDebug:
    145                     mPreferences.setLoggingLevel(Util.LogLevel.Debug);
    146                     break;
    147                 case R.id.loggingVerbose:
    148                     mPreferences.setLoggingLevel(Util.LogLevel.Verbose);
    149                     break;
    150             }
    151         });
    152         RadioGroup dalCheckRequirementContainer = findViewById(R.id.dalCheckRequirementContainer);
    153         Util.DalCheckRequirement dalCheckRequirement = mPreferences.getDalCheckRequirement();
    154         switch (dalCheckRequirement) {
    155             case Disabled:
    156                 dalCheckRequirementContainer.check(R.id.dalDisabled);
    157                 break;
    158             case LoginOnly:
    159                 dalCheckRequirementContainer.check(R.id.dalLoginOnly);
    160                 break;
    161             case AllUrls:
    162                 dalCheckRequirementContainer.check(R.id.dalAllUrls);
    163                 break;
    164         }
    165         dalCheckRequirementContainer.setOnCheckedChangeListener((group, checkedId) -> {
    166             switch (checkedId) {
    167                 case R.id.dalDisabled:
    168                     mPreferences.setDalCheckRequired(Disabled);
    169                     break;
    170                 case R.id.dalLoginOnly:
    171                     mPreferences.setDalCheckRequired(LoginOnly);
    172                     break;
    173                 case R.id.dalAllUrls:
    174                     mPreferences.setDalCheckRequired(AllUrls);
    175                     break;
    176             }
    177         });
    178     }
    179 
    180     private AlertDialog buildClearDataDialog() {
    181         return new AlertDialog.Builder(SettingsActivity.this)
    182                 .setMessage(R.string.settings_clear_data_confirmation)
    183                 .setTitle(R.string.settings_clear_data_confirmation_title)
    184                 .setNegativeButton(R.string.settings_cancel, null)
    185                 .setPositiveButton(R.string.settings_ok, (dialog, which) -> {
    186                     mLocalAutofillDataSource.clear();
    187                     mPackageVerificationDataSource.clear();
    188                     mPreferences.clearCredentials();
    189                     dialog.dismiss();
    190                 })
    191                 .create();
    192     }
    193 
    194     private AlertDialog buildAddDataDialog() {
    195         NumberPicker numberOfDatasetsPicker = LayoutInflater
    196                 .from(SettingsActivity.this)
    197                 .inflate(R.layout.multidataset_service_settings_add_data_dialog, null)
    198                 .findViewById(R.id.number_of_datasets_picker);
    199         numberOfDatasetsPicker.setMinValue(0);
    200         numberOfDatasetsPicker.setMaxValue(10);
    201         numberOfDatasetsPicker.setWrapSelectorWheel(false);
    202         return new AlertDialog.Builder(SettingsActivity.this)
    203                 .setTitle(R.string.settings_add_data_title)
    204                 .setNegativeButton(R.string.settings_cancel, null)
    205                 .setMessage(R.string.settings_select_number_of_datasets)
    206                 .setView(numberOfDatasetsPicker)
    207                 .setPositiveButton(R.string.settings_ok, (dialog, which) -> {
    208                     int numOfDatasets = numberOfDatasetsPicker.getValue();
    209                     mLocalAutofillDataSource.getFieldTypes(new DataCallback<List<FieldTypeWithHeuristics>>() {
    210                         @Override
    211                         public void onLoaded(List<FieldTypeWithHeuristics> fieldTypes) {
    212                             boolean saved = buildAndSaveMockedAutofillFieldCollections(
    213                                     fieldTypes, numOfDatasets);
    214                             dialog.dismiss();
    215                             if (saved) {
    216                                 Snackbar.make(findViewById(R.id.settings_layout),
    217                                         getResources().getQuantityString(
    218                                                 R.plurals.settings_add_data_success,
    219                                                 numOfDatasets, numOfDatasets),
    220                                         Snackbar.LENGTH_SHORT).show();
    221                             }
    222                         }
    223 
    224                         @Override
    225                         public void onDataNotAvailable(String msg, Object... params) {
    226 
    227                         }
    228                     });
    229                 })
    230                 .create();
    231     }
    232 
    233     public boolean buildAndSaveMockedAutofillFieldCollections(List<FieldTypeWithHeuristics> fieldTypes,
    234             int numOfDatasets) {
    235         if (numOfDatasets < 0 || numOfDatasets > 10) {
    236             logw("Number of Datasets (%d) out of range.", numOfDatasets);
    237         }
    238         for (int i = 0; i < numOfDatasets; i++) {
    239             int datasetNumber = mLocalAutofillDataSource.getDatasetNumber();
    240             AutofillDataBuilder autofillDataBuilder =
    241                     new FakeAutofillDataBuilder(fieldTypes, mPackageName, datasetNumber);
    242             List<DatasetWithFilledAutofillFields> datasetsWithFilledAutofillFields =
    243                     autofillDataBuilder.buildDatasetsByPartition(datasetNumber);
    244             // Save datasets to database.
    245             mLocalAutofillDataSource.saveAutofillDatasets(datasetsWithFilledAutofillFields);
    246         }
    247         return true;
    248     }
    249 
    250     private AlertDialog.Builder prepareCredentialsDialog() {
    251         return new AlertDialog.Builder(SettingsActivity.this)
    252                 .setTitle(R.string.settings_auth_change_credentials_title)
    253                 .setNegativeButton(R.string.settings_cancel, null);
    254     }
    255 
    256     private AlertDialog buildCurrentCredentialsDialog() {
    257         final EditText currentPasswordField = LayoutInflater
    258                 .from(SettingsActivity.this)
    259                 .inflate(R.layout.multidataset_service_settings_authentication_dialog, null)
    260                 .findViewById(R.id.master_password_field);
    261         return prepareCredentialsDialog()
    262                 .setMessage(R.string.settings_auth_enter_current_password)
    263                 .setView(currentPasswordField)
    264                 .setPositiveButton(R.string.settings_ok, new DialogInterface.OnClickListener() {
    265                     @Override
    266                     public void onClick(DialogInterface dialog, int which) {
    267                         String password = currentPasswordField.getText().toString();
    268                         if (mPreferences.getMasterPassword()
    269                                 .equals(password)) {
    270                             buildNewCredentialsDialog().show();
    271                             dialog.dismiss();
    272                         }
    273                     }
    274                 })
    275                 .create();
    276     }
    277 
    278     private AlertDialog buildNewCredentialsDialog() {
    279         final EditText newPasswordField = LayoutInflater
    280                 .from(SettingsActivity.this)
    281                 .inflate(R.layout.multidataset_service_settings_authentication_dialog, null)
    282                 .findViewById(R.id.master_password_field);
    283         return prepareCredentialsDialog()
    284                 .setMessage(R.string.settings_auth_enter_new_password)
    285                 .setView(newPasswordField)
    286                 .setPositiveButton(R.string.settings_ok, (dialog, which) -> {
    287                     String password = newPasswordField.getText().toString();
    288                     mPreferences.setMasterPassword(password);
    289                     dialog.dismiss();
    290                 })
    291                 .create();
    292     }
    293 
    294     private void setupSettingsSwitch(int containerId, int labelId, int switchId, boolean checked,
    295             CompoundButton.OnCheckedChangeListener checkedChangeListener) {
    296         ViewGroup container = findViewById(containerId);
    297         String switchLabel = ((TextView) container.findViewById(labelId)).getText().toString();
    298         final Switch switchView = container.findViewById(switchId);
    299         switchView.setContentDescription(switchLabel);
    300         switchView.setChecked(checked);
    301         container.setOnClickListener((view) -> switchView.performClick());
    302         switchView.setOnCheckedChangeListener(checkedChangeListener);
    303     }
    304 
    305     private void setupSettingsButton(int containerId, int labelId, int imageViewId,
    306             final View.OnClickListener onClickListener) {
    307         ViewGroup container = findViewById(containerId);
    308         TextView buttonLabel = container.findViewById(labelId);
    309         String buttonLabelText = buttonLabel.getText().toString();
    310         ImageView imageView = container.findViewById(imageViewId);
    311         imageView.setContentDescription(buttonLabelText);
    312         container.setOnClickListener(onClickListener);
    313     }
    314 
    315     private void setService(boolean enableService) {
    316         if (enableService) {
    317             startEnableService();
    318         } else {
    319             disableService();
    320         }
    321     }
    322 
    323     private void disableService() {
    324         if (mAutofillManager != null && mAutofillManager.hasEnabledAutofillServices()) {
    325             mAutofillManager.disableAutofillServices();
    326             Snackbar.make(findViewById(R.id.settings_layout),
    327                     R.string.settings_autofill_disabled_message, Snackbar.LENGTH_SHORT).show();
    328         } else {
    329             logd("Sample service already disabled.");
    330         }
    331     }
    332 
    333     private void startEnableService() {
    334         if (mAutofillManager != null && !mAutofillManager.hasEnabledAutofillServices()) {
    335             Intent intent = new Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE);
    336             intent.setData(Uri.parse("package:com.example.android.autofill.service"));
    337             logd(TAG, "enableService(): intent=%s", intent);
    338             startActivityForResult(intent, REQUEST_CODE_SET_DEFAULT);
    339         } else {
    340             logd("Sample service already enabled.");
    341         }
    342     }
    343 
    344     @Override
    345     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    346         logd(TAG, "onActivityResult(): req=%s", requestCode);
    347         switch (requestCode) {
    348             case REQUEST_CODE_SET_DEFAULT:
    349                 onDefaultServiceSet(resultCode);
    350                 break;
    351         }
    352     }
    353 
    354     private void onDefaultServiceSet(int resultCode) {
    355         logd(TAG, "resultCode=%d", resultCode);
    356         switch (resultCode) {
    357             case RESULT_OK:
    358                 logd("Autofill service set.");
    359                 Snackbar.make(findViewById(R.id.settings_layout),
    360                         R.string.settings_autofill_service_set, Snackbar.LENGTH_SHORT)
    361                         .show();
    362                 break;
    363             case RESULT_CANCELED:
    364                 logd("Autofill service not selected.");
    365                 Snackbar.make(findViewById(R.id.settings_layout),
    366                         R.string.settings_autofill_service_not_set, Snackbar.LENGTH_SHORT)
    367                         .show();
    368                 break;
    369         }
    370     }
    371 }
    372