| /* |
| * Copyright (C) 2018 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.android.tv.settings.autofill; |
| |
| import android.app.Activity; |
| import android.app.AlertDialog; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.pm.PackageManager; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.UserHandle; |
| import android.text.Html; |
| import android.widget.Button; |
| |
| import androidx.annotation.Keep; |
| import androidx.annotation.VisibleForTesting; |
| import androidx.preference.Preference; |
| import androidx.preference.PreferenceScreen; |
| |
| import com.android.settingslib.applications.DefaultAppInfo; |
| import com.android.tv.settings.R; |
| import com.android.tv.settings.RadioPreference; |
| import com.android.tv.settings.SettingsPreferenceFragment; |
| |
| import java.util.List; |
| |
| /** |
| * Picker Fragment to select Autofill service |
| */ |
| @Keep |
| public class AutofillPickerFragment extends SettingsPreferenceFragment { |
| |
| private static final String AUTOFILL_SERVICE_RADIO_GROUP = "autofill_service_group"; |
| |
| @VisibleForTesting |
| static final String KEY_FOR_NONE = "_none_"; |
| |
| private static final int FINISH_ACTIVITY_DELAY = 300; |
| |
| private PackageManager mPm; |
| |
| private final Handler mHandler = new Handler(); |
| |
| /** |
| * Set when the fragment is implementing ACTION_REQUEST_SET_AUTOFILL_SERVICE. |
| */ |
| private DialogInterface.OnClickListener mCancelListener; |
| |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| |
| final Activity activity = getActivity(); |
| if (activity != null && activity.getIntent() |
| .getStringExtra(AutofillPickerActivity.EXTRA_PACKAGE_NAME) != null) { |
| mCancelListener = (d, w) -> { |
| activity.setResult(Activity.RESULT_CANCELED); |
| activity.finish(); |
| }; |
| } |
| } |
| |
| /** |
| * @return new AutofillPickerFragment instance |
| */ |
| public static AutofillPickerFragment newInstance() { |
| return new AutofillPickerFragment(); |
| } |
| |
| @Override |
| public void onAttach(Context context) { |
| super.onAttach(context); |
| mPm = context.getPackageManager(); |
| } |
| |
| @Override |
| public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { |
| setPreferencesFromResource(R.xml.autofill_picker, null); |
| final PreferenceScreen screen = getPreferenceScreen(); |
| bind(screen, savedInstanceState == null); |
| setPreferenceScreen(screen); |
| } |
| |
| @VisibleForTesting |
| void bind(PreferenceScreen screen, boolean scrollToSelection) { |
| Context context = getContext(); |
| |
| List<DefaultAppInfo> candidates = AutofillHelper.getAutofillCandidates(context, |
| mPm, UserHandle.myUserId()); |
| DefaultAppInfo current = AutofillHelper.getCurrentAutofill(context, candidates); |
| |
| RadioPreference activePref = null; |
| for (final DefaultAppInfo appInfo : candidates) { |
| final RadioPreference radioPreference = new RadioPreference(context); |
| radioPreference.setKey(appInfo.getKey()); |
| radioPreference.setPersistent(false); |
| radioPreference.setTitle(appInfo.loadLabel()); |
| radioPreference.setRadioGroup(AUTOFILL_SERVICE_RADIO_GROUP); |
| radioPreference.setLayoutResource(R.layout.preference_reversed_widget); |
| |
| if (current == appInfo) { |
| radioPreference.setChecked(true); |
| activePref = radioPreference; |
| } |
| screen.addPreference(radioPreference); |
| } |
| if (activePref == null) { |
| // select the none |
| activePref = ((RadioPreference) screen.findPreference(KEY_FOR_NONE)); |
| activePref.setChecked(true); |
| } |
| |
| if (activePref != null && scrollToSelection) { |
| scrollToPreference(activePref); |
| } |
| } |
| |
| @Override |
| public boolean onPreferenceTreeClick(Preference preference) { |
| if (preference instanceof RadioPreference) { |
| |
| final Context context = getContext(); |
| List<DefaultAppInfo> candidates = AutofillHelper.getAutofillCandidates(context, |
| mPm, UserHandle.myUserId()); |
| final DefaultAppInfo current = AutofillHelper.getCurrentAutofill(context, |
| candidates); |
| final String currentKey = current != null ? current.getKey() : KEY_FOR_NONE; |
| |
| final RadioPreference newPref = (RadioPreference) preference; |
| final String newKey = newPref.getKey(); |
| final boolean clickOnCurrent = currentKey.equals(newKey); |
| |
| if (!clickOnCurrent && !KEY_FOR_NONE.equals(newKey)) { |
| // Undo checked state change and wait dialog to confirm click |
| RadioPreference currentPref = (RadioPreference) findPreference(currentKey); |
| currentPref.setChecked(true); |
| currentPref.clearOtherRadioPreferences(getPreferenceScreen()); |
| CharSequence confirmationMessage = Html.fromHtml(getContext().getString( |
| R.string.autofill_confirmation_message, newPref.getTitle())); |
| displayAlert(confirmationMessage, (dialog, which) -> { |
| RadioPreference pref = (RadioPreference) findPreference(newKey); |
| if (pref != null) { |
| pref.setChecked(true); |
| pref.clearOtherRadioPreferences(getPreferenceScreen()); |
| setAutofillService(newKey); |
| } |
| }); |
| } else { |
| // Either clicked on already checked or click on "none": just close the fragment |
| newPref.setChecked(true); |
| newPref.clearOtherRadioPreferences(getPreferenceScreen()); |
| setAutofillService(newKey); |
| } |
| } |
| return true; |
| } |
| |
| private void setAutofillService(String key) { |
| AutofillHelper.setCurrentAutofill(getContext(), key); |
| // Check if activity was launched from Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE |
| // intent, and set proper result if so... |
| final Activity activity = getActivity(); |
| if (activity != null) { |
| final String packageName = activity.getIntent() |
| .getStringExtra(AutofillPickerActivity.EXTRA_PACKAGE_NAME); |
| if (packageName != null) { |
| ComponentName componentName = ComponentName.unflattenFromString(key); |
| final int result = componentName != null |
| && componentName.getPackageName().equals(packageName) |
| ? Activity.RESULT_OK : Activity.RESULT_CANCELED; |
| finishActivity(true, result); |
| } else { |
| if (!getFragmentManager().popBackStackImmediate()) { |
| finishActivity(false, 0); |
| } |
| } |
| } |
| } |
| |
| private void finishActivity(final boolean sendResult, final int result) { |
| // RadioPreference does not update UI if activity is marked as finished. |
| // Wait a little bit for the RadioPreference UI update. |
| mHandler.postDelayed(() -> { |
| if (sendResult) { |
| getActivity().setResult(result); |
| } |
| getActivity().finish(); |
| }, FINISH_ACTIVITY_DELAY); |
| } |
| |
| private void displayAlert( |
| CharSequence message, |
| DialogInterface.OnClickListener positiveOnClickListener) { |
| |
| final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) |
| .setMessage(message) |
| .setCancelable(true) |
| .setPositiveButton(android.R.string.ok, positiveOnClickListener) |
| .setNegativeButton(android.R.string.cancel, mCancelListener); |
| final AlertDialog dialog = builder.create(); |
| dialog.setOnShowListener((dialogInterface) -> { |
| final Button negative = dialog.getButton(AlertDialog.BUTTON_NEGATIVE); |
| negative.requestFocus(); |
| }); |
| dialog.show(); |
| } |
| } |