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 static com.example.android.autofill.service.util.Util.DalCheckRequirement.AllUrls; 19 import static com.example.android.autofill.service.util.Util.DalCheckRequirement.Disabled; 20 import static com.example.android.autofill.service.util.Util.DalCheckRequirement.LoginOnly; 21 import static com.example.android.autofill.service.util.Util.logd; 22 import static com.example.android.autofill.service.util.Util.logw; 23 24 import android.content.Context; 25 import android.content.DialogInterface; 26 import android.content.Intent; 27 import android.content.SharedPreferences; 28 import android.net.Uri; 29 import android.os.Bundle; 30 import android.provider.Settings; 31 import android.support.design.widget.Snackbar; 32 import android.support.v7.app.AlertDialog; 33 import android.support.v7.app.AppCompatActivity; 34 import android.view.LayoutInflater; 35 import android.view.View; 36 import android.view.ViewGroup; 37 import android.view.autofill.AutofillManager; 38 import android.widget.CompoundButton; 39 import android.widget.EditText; 40 import android.widget.ImageView; 41 import android.widget.NumberPicker; 42 import android.widget.RadioGroup; 43 import android.widget.Switch; 44 import android.widget.TextView; 45 46 import com.example.android.autofill.service.R; 47 import com.example.android.autofill.service.data.AutofillDataBuilder; 48 import com.example.android.autofill.service.data.DataCallback; 49 import com.example.android.autofill.service.data.FakeAutofillDataBuilder; 50 import com.example.android.autofill.service.data.source.DefaultFieldTypesSource; 51 import com.example.android.autofill.service.data.source.PackageVerificationDataSource; 52 import com.example.android.autofill.service.data.source.local.DefaultFieldTypesLocalJsonSource; 53 import com.example.android.autofill.service.data.source.local.LocalAutofillDataSource; 54 import com.example.android.autofill.service.data.source.local.SharedPrefsPackageVerificationRepository; 55 import com.example.android.autofill.service.data.source.local.dao.AutofillDao; 56 import com.example.android.autofill.service.data.source.local.db.AutofillDatabase; 57 import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields; 58 import com.example.android.autofill.service.model.FieldTypeWithHeuristics; 59 import com.example.android.autofill.service.util.AppExecutors; 60 import com.example.android.autofill.service.util.Util; 61 import com.google.gson.GsonBuilder; 62 63 import java.util.List; 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 onCreate(Bundle savedInstanceState)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.getMainPassword() != 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 buildClearDataDialog()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 buildAddDataDialog()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 mPreferences.setNumberDatasets(numOfDatasets); 210 mLocalAutofillDataSource.getFieldTypes(new DataCallback<List<FieldTypeWithHeuristics>>() { 211 @Override 212 public void onLoaded(List<FieldTypeWithHeuristics> fieldTypes) { 213 boolean saved = buildAndSaveMockedAutofillFieldCollections( 214 fieldTypes, numOfDatasets); 215 dialog.dismiss(); 216 if (saved) { 217 Snackbar.make(findViewById(R.id.settings_layout), 218 getResources().getQuantityString( 219 R.plurals.settings_add_data_success, 220 numOfDatasets, numOfDatasets), 221 Snackbar.LENGTH_SHORT).show(); 222 } 223 } 224 225 @Override 226 public void onDataNotAvailable(String msg, Object... params) { 227 228 } 229 }); 230 }) 231 .create(); 232 } 233 234 public boolean buildAndSaveMockedAutofillFieldCollections(List<FieldTypeWithHeuristics> fieldTypes, 235 int numOfDatasets) { 236 if (numOfDatasets < 0 || numOfDatasets > 10) { 237 logw("Number of Datasets (%d) out of range.", numOfDatasets); 238 } 239 for (int i = 0; i < numOfDatasets; i++) { 240 int datasetNumber = mLocalAutofillDataSource.getDatasetNumber(); 241 AutofillDataBuilder autofillDataBuilder = 242 new FakeAutofillDataBuilder(fieldTypes, mPackageName, datasetNumber); 243 List<DatasetWithFilledAutofillFields> datasetsWithFilledAutofillFields = 244 autofillDataBuilder.buildDatasetsByPartition(datasetNumber); 245 // Save datasets to database. 246 mLocalAutofillDataSource.saveAutofillDatasets(datasetsWithFilledAutofillFields); 247 } 248 return true; 249 } 250 251 private AlertDialog.Builder prepareCredentialsDialog() { 252 return new AlertDialog.Builder(SettingsActivity.this) 253 .setTitle(R.string.settings_auth_change_credentials_title) 254 .setNegativeButton(R.string.settings_cancel, null); 255 } 256 257 private AlertDialog buildCurrentCredentialsDialog() { 258 final EditText currentPasswordField = LayoutInflater 259 .from(SettingsActivity.this) 260 .inflate(R.layout.multidataset_service_settings_authentication_dialog, null) 261 .findViewById(R.id.main_password_field); 262 return prepareCredentialsDialog() 263 .setMessage(R.string.settings_auth_enter_current_password) 264 .setView(currentPasswordField) 265 .setPositiveButton(R.string.settings_ok, new DialogInterface.OnClickListener() { 266 @Override 267 public void onClick(DialogInterface dialog, int which) { 268 String password = currentPasswordField.getText().toString(); 269 if (mPreferences.getMainPassword() 270 .equals(password)) { 271 buildNewCredentialsDialog().show(); 272 dialog.dismiss(); 273 } 274 } 275 }) 276 .create(); 277 } 278 279 private AlertDialog buildNewCredentialsDialog() { 280 final EditText newPasswordField = LayoutInflater 281 .from(SettingsActivity.this) 282 .inflate(R.layout.multidataset_service_settings_authentication_dialog, null) 283 .findViewById(R.id.main_password_field); 284 return prepareCredentialsDialog() 285 .setMessage(R.string.settings_auth_enter_new_password) 286 .setView(newPasswordField) 287 .setPositiveButton(R.string.settings_ok, (dialog, which) -> { 288 String password = newPasswordField.getText().toString(); 289 mPreferences.setMainPassword(password); 290 dialog.dismiss(); 291 }) 292 .create(); 293 } 294 295 private void setupSettingsSwitch(int containerId, int labelId, int switchId, boolean checked, 296 CompoundButton.OnCheckedChangeListener checkedChangeListener) { 297 ViewGroup container = findViewById(containerId); 298 String switchLabel = ((TextView) container.findViewById(labelId)).getText().toString(); 299 final Switch switchView = container.findViewById(switchId); 300 switchView.setContentDescription(switchLabel); 301 switchView.setChecked(checked); 302 container.setOnClickListener((view) -> switchView.performClick()); 303 switchView.setOnCheckedChangeListener(checkedChangeListener); 304 } 305 306 private void setupSettingsButton(int containerId, int labelId, int imageViewId, 307 final View.OnClickListener onClickListener) { 308 ViewGroup container = findViewById(containerId); 309 TextView buttonLabel = container.findViewById(labelId); 310 String buttonLabelText = buttonLabel.getText().toString(); 311 ImageView imageView = container.findViewById(imageViewId); 312 imageView.setContentDescription(buttonLabelText); 313 container.setOnClickListener(onClickListener); 314 } 315 316 private void setService(boolean enableService) { 317 if (enableService) { 318 startEnableService(); 319 } else { 320 disableService(); 321 } 322 } 323 324 private void disableService() { 325 if (mAutofillManager != null && mAutofillManager.hasEnabledAutofillServices()) { 326 mAutofillManager.disableAutofillServices(); 327 Snackbar.make(findViewById(R.id.settings_layout), 328 R.string.settings_autofill_disabled_message, Snackbar.LENGTH_SHORT).show(); 329 } else { 330 logd("Sample service already disabled."); 331 } 332 } 333 334 private void startEnableService() { 335 if (mAutofillManager != null && !mAutofillManager.hasEnabledAutofillServices()) { 336 Intent intent = new Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE); 337 intent.setData(Uri.parse("package:com.example.android.autofill.service")); 338 logd(TAG, "enableService(): intent=%s", intent); 339 startActivityForResult(intent, REQUEST_CODE_SET_DEFAULT); 340 } else { 341 logd("Sample service already enabled."); 342 } 343 } 344 345 @Override 346 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 347 logd(TAG, "onActivityResult(): req=%s", requestCode); 348 switch (requestCode) { 349 case REQUEST_CODE_SET_DEFAULT: 350 onDefaultServiceSet(resultCode); 351 break; 352 } 353 } 354 355 private void onDefaultServiceSet(int resultCode) { 356 logd(TAG, "resultCode=%d", resultCode); 357 switch (resultCode) { 358 case RESULT_OK: 359 logd("Autofill service set."); 360 Snackbar.make(findViewById(R.id.settings_layout), 361 R.string.settings_autofill_service_set, Snackbar.LENGTH_SHORT) 362 .show(); 363 break; 364 case RESULT_CANCELED: 365 logd("Autofill service not selected."); 366 Snackbar.make(findViewById(R.id.settings_layout), 367 R.string.settings_autofill_service_not_set, Snackbar.LENGTH_SHORT) 368 .show(); 369 break; 370 } 371 } 372 } 373