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