1 /* 2 * Copyright (C) 2018 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.android.tv.settings.autofill; 17 18 import android.app.Activity; 19 import android.app.AlertDialog; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.DialogInterface; 23 import android.content.pm.PackageManager; 24 import android.os.Bundle; 25 import android.os.Handler; 26 import android.os.UserHandle; 27 import android.text.Html; 28 import android.widget.Button; 29 30 import androidx.annotation.Keep; 31 import androidx.annotation.VisibleForTesting; 32 import androidx.preference.Preference; 33 import androidx.preference.PreferenceScreen; 34 35 import com.android.internal.logging.nano.MetricsProto; 36 import com.android.settingslib.applications.DefaultAppInfo; 37 import com.android.tv.settings.R; 38 import com.android.tv.settings.RadioPreference; 39 import com.android.tv.settings.SettingsPreferenceFragment; 40 41 import java.util.List; 42 43 /** 44 * Picker Fragment to select Autofill service 45 */ 46 @Keep 47 public class AutofillPickerFragment extends SettingsPreferenceFragment { 48 49 private static final String AUTOFILL_SERVICE_RADIO_GROUP = "autofill_service_group"; 50 51 @VisibleForTesting 52 static final String KEY_FOR_NONE = "_none_"; 53 54 private static final int FINISH_ACTIVITY_DELAY = 300; 55 56 private PackageManager mPm; 57 58 private final Handler mHandler = new Handler(); 59 60 /** 61 * Set when the fragment is implementing ACTION_REQUEST_SET_AUTOFILL_SERVICE. 62 */ 63 private DialogInterface.OnClickListener mCancelListener; 64 65 @Override onCreate(Bundle savedInstanceState)66 public void onCreate(Bundle savedInstanceState) { 67 super.onCreate(savedInstanceState); 68 69 final Activity activity = getActivity(); 70 if (activity != null && activity.getIntent() 71 .getStringExtra(AutofillPickerActivity.EXTRA_PACKAGE_NAME) != null) { 72 mCancelListener = (d, w) -> { 73 activity.setResult(Activity.RESULT_CANCELED); 74 activity.finish(); 75 }; 76 } 77 } 78 79 /** 80 * @return new AutofillPickerFragment instance 81 */ newInstance()82 public static AutofillPickerFragment newInstance() { 83 return new AutofillPickerFragment(); 84 } 85 86 @Override onAttach(Context context)87 public void onAttach(Context context) { 88 super.onAttach(context); 89 mPm = context.getPackageManager(); 90 } 91 92 @Override onCreatePreferences(Bundle savedInstanceState, String rootKey)93 public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 94 setPreferencesFromResource(R.xml.autofill_picker, null); 95 final PreferenceScreen screen = getPreferenceScreen(); 96 bind(screen, savedInstanceState == null); 97 setPreferenceScreen(screen); 98 } 99 100 @VisibleForTesting bind(PreferenceScreen screen, boolean scrollToSelection)101 void bind(PreferenceScreen screen, boolean scrollToSelection) { 102 Context context = getContext(); 103 104 List<DefaultAppInfo> candidates = AutofillHelper.getAutofillCandidates(context, 105 mPm, UserHandle.myUserId()); 106 DefaultAppInfo current = AutofillHelper.getCurrentAutofill(context, candidates); 107 108 RadioPreference activePref = null; 109 for (final DefaultAppInfo appInfo : candidates) { 110 final RadioPreference radioPreference = new RadioPreference(context); 111 radioPreference.setKey(appInfo.getKey()); 112 radioPreference.setPersistent(false); 113 radioPreference.setTitle(appInfo.loadLabel()); 114 radioPreference.setRadioGroup(AUTOFILL_SERVICE_RADIO_GROUP); 115 radioPreference.setLayoutResource(R.layout.preference_reversed_widget); 116 117 if (current == appInfo) { 118 radioPreference.setChecked(true); 119 activePref = radioPreference; 120 } 121 screen.addPreference(radioPreference); 122 } 123 if (activePref == null) { 124 // select the none 125 activePref = ((RadioPreference) screen.findPreference(KEY_FOR_NONE)); 126 activePref.setChecked(true); 127 } 128 129 if (activePref != null && scrollToSelection) { 130 scrollToPreference(activePref); 131 } 132 } 133 134 @Override onPreferenceTreeClick(Preference preference)135 public boolean onPreferenceTreeClick(Preference preference) { 136 if (preference instanceof RadioPreference) { 137 138 final Context context = getContext(); 139 List<DefaultAppInfo> candidates = AutofillHelper.getAutofillCandidates(context, 140 mPm, UserHandle.myUserId()); 141 final DefaultAppInfo current = AutofillHelper.getCurrentAutofill(context, 142 candidates); 143 final String currentKey = current != null ? current.getKey() : KEY_FOR_NONE; 144 145 final RadioPreference newPref = (RadioPreference) preference; 146 final String newKey = newPref.getKey(); 147 final boolean clickOnCurrent = currentKey.equals(newKey); 148 149 if (!clickOnCurrent && !KEY_FOR_NONE.equals(newKey)) { 150 // Undo checked state change and wait dialog to confirm click 151 RadioPreference currentPref = (RadioPreference) findPreference(currentKey); 152 currentPref.setChecked(true); 153 currentPref.clearOtherRadioPreferences(getPreferenceScreen()); 154 CharSequence confirmationMessage = Html.fromHtml(getContext().getString( 155 R.string.autofill_confirmation_message, newPref.getTitle())); 156 displayAlert(confirmationMessage, (dialog, which) -> { 157 RadioPreference pref = (RadioPreference) findPreference(newKey); 158 if (pref != null) { 159 pref.setChecked(true); 160 pref.clearOtherRadioPreferences(getPreferenceScreen()); 161 setAutofillService(newKey); 162 } 163 }); 164 } else { 165 // Either clicked on already checked or click on "none": just close the fragment 166 newPref.setChecked(true); 167 newPref.clearOtherRadioPreferences(getPreferenceScreen()); 168 setAutofillService(newKey); 169 } 170 } 171 return true; 172 } 173 setAutofillService(String key)174 private void setAutofillService(String key) { 175 AutofillHelper.setCurrentAutofill(getContext(), key); 176 // Check if activity was launched from Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE 177 // intent, and set proper result if so... 178 final Activity activity = getActivity(); 179 if (activity != null) { 180 final String packageName = activity.getIntent() 181 .getStringExtra(AutofillPickerActivity.EXTRA_PACKAGE_NAME); 182 if (packageName != null) { 183 ComponentName componentName = ComponentName.unflattenFromString(key); 184 final int result = componentName != null 185 && componentName.getPackageName().equals(packageName) 186 ? Activity.RESULT_OK : Activity.RESULT_CANCELED; 187 finishActivity(true, result); 188 } else { 189 if (!getFragmentManager().popBackStackImmediate()) { 190 finishActivity(false, 0); 191 } 192 } 193 } 194 } 195 finishActivity(final boolean sendResult, final int result)196 private void finishActivity(final boolean sendResult, final int result) { 197 // RadioPreference does not update UI if activity is marked as finished. 198 // Wait a little bit for the RadioPreference UI update. 199 mHandler.postDelayed(() -> { 200 if (sendResult) { 201 getActivity().setResult(result); 202 } 203 getActivity().finish(); 204 }, FINISH_ACTIVITY_DELAY); 205 } 206 displayAlert( CharSequence message, DialogInterface.OnClickListener positiveOnClickListener)207 private void displayAlert( 208 CharSequence message, 209 DialogInterface.OnClickListener positiveOnClickListener) { 210 211 final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) 212 .setMessage(message) 213 .setCancelable(true) 214 .setPositiveButton(android.R.string.ok, positiveOnClickListener) 215 .setNegativeButton(android.R.string.cancel, mCancelListener); 216 final AlertDialog dialog = builder.create(); 217 dialog.setOnShowListener((dialogInterface) -> { 218 final Button negative = dialog.getButton(AlertDialog.BUTTON_NEGATIVE); 219 negative.requestFocus(); 220 }); 221 dialog.show(); 222 } 223 224 @Override getMetricsCategory()225 public int getMetricsCategory() { 226 return MetricsProto.MetricsEvent.DEFAULT_AUTOFILL_PICKER; 227 } 228 } 229