• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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