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