• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 
17 package com.android.settings.applications.credentials;
18 
19 import android.annotation.Nullable;
20 import android.app.Activity;
21 import android.app.settings.SettingsEnums;
22 import android.content.Context;
23 import android.content.DialogInterface;
24 import android.content.Intent;
25 import android.content.pm.ServiceInfo;
26 import android.credentials.CredentialManager;
27 import android.credentials.CredentialProviderInfo;
28 import android.credentials.SetEnabledProvidersException;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.os.OutcomeReceiver;
32 import android.os.UserHandle;
33 import android.provider.Settings;
34 import android.service.autofill.AutofillServiceInfo;
35 import android.text.Html;
36 import android.text.TextUtils;
37 import android.util.Log;
38 
39 import androidx.core.content.ContextCompat;
40 import androidx.preference.Preference;
41 
42 import com.android.internal.content.PackageMonitor;
43 import com.android.settings.R;
44 import com.android.settings.applications.defaultapps.DefaultAppPickerFragment;
45 import com.android.settingslib.applications.DefaultAppInfo;
46 import com.android.settingslib.utils.ThreadUtils;
47 import com.android.settingslib.widget.CandidateInfo;
48 
49 import java.util.ArrayList;
50 import java.util.List;
51 
52 public class DefaultCombinedPicker extends DefaultAppPickerFragment {
53 
54     private static final String TAG = "DefaultCombinedPicker";
55 
56     public static final String AUTOFILL_SETTING = Settings.Secure.AUTOFILL_SERVICE;
57     public static final String CREDENTIAL_SETTING = Settings.Secure.CREDENTIAL_SERVICE;
58 
59     /** Extra set when the fragment is implementing ACTION_REQUEST_SET_AUTOFILL_SERVICE. */
60     public static final String EXTRA_PACKAGE_NAME = "package_name";
61 
62     /** Set when the fragment is implementing ACTION_REQUEST_SET_AUTOFILL_SERVICE. */
63     private DialogInterface.OnClickListener mCancelListener;
64 
65     private CredentialManager mCredentialManager;
66     private int mIntentSenderUserId = -1;
67 
68     @Override
onCreate(Bundle savedInstanceState)69     public void onCreate(Bundle savedInstanceState) {
70         super.onCreate(savedInstanceState);
71 
72         final Activity activity = getActivity();
73         if (activity != null && activity.getIntent().getStringExtra(EXTRA_PACKAGE_NAME) != null) {
74             mCancelListener =
75                     (d, w) -> {
76                         activity.setResult(Activity.RESULT_CANCELED);
77                         activity.finish();
78                     };
79             // If mCancelListener is not null, fragment is started from
80             // ACTION_REQUEST_SET_AUTOFILL_SERVICE and we should always use the calling uid.
81             mIntentSenderUserId = UserHandle.myUserId();
82         }
83 
84         getUser();
85 
86         mSettingsPackageMonitor.register(activity, activity.getMainLooper(), false);
87         update();
88     }
89 
90     @Override
newConfirmationDialogFragment( String selectedKey, CharSequence confirmationMessage)91     protected DefaultAppPickerFragment.ConfirmationDialogFragment newConfirmationDialogFragment(
92             String selectedKey, CharSequence confirmationMessage) {
93         final AutofillPickerConfirmationDialogFragment fragment =
94                 new AutofillPickerConfirmationDialogFragment();
95         fragment.init(this, selectedKey, confirmationMessage);
96         return fragment;
97     }
98 
99     /**
100      * Custom dialog fragment that has a cancel listener used to propagate the result back to caller
101      * (for the cases where the picker is launched by {@code
102      * android.settings.REQUEST_SET_AUTOFILL_SERVICE}.
103      */
104     public static class AutofillPickerConfirmationDialogFragment
105             extends DefaultAppPickerFragment.ConfirmationDialogFragment {
106 
107         @Override
onCreate(Bundle savedInstanceState)108         public void onCreate(Bundle savedInstanceState) {
109             final DefaultCombinedPicker target = (DefaultCombinedPicker) getTargetFragment();
110             setCancelListener(target.mCancelListener);
111             super.onCreate(savedInstanceState);
112         }
113     }
114 
115     @Override
getPreferenceScreenResId()116     protected int getPreferenceScreenResId() {
117         return R.xml.default_credman_picker;
118     }
119 
120     @Override
getMetricsCategory()121     public int getMetricsCategory() {
122         return SettingsEnums.DEFAULT_AUTOFILL_PICKER;
123     }
124 
125     @Override
shouldShowItemNone()126     protected boolean shouldShowItemNone() {
127         return true;
128     }
129 
130     /** Monitor coming and going auto fill services and calls {@link #update()} when necessary */
131     private final PackageMonitor mSettingsPackageMonitor =
132             new PackageMonitor() {
133                 @Override
134                 public void onPackageAdded(String packageName, int uid) {
135                     ThreadUtils.postOnMainThread(() -> update());
136                 }
137 
138                 @Override
139                 public void onPackageModified(String packageName) {
140                     ThreadUtils.postOnMainThread(() -> update());
141                 }
142 
143                 @Override
144                 public void onPackageRemoved(String packageName, int uid) {
145                     ThreadUtils.postOnMainThread(() -> update());
146                 }
147             };
148 
149     /** Update the data in this UI. */
update()150     private void update() {
151         updateCandidates();
152         addAddServicePreference();
153     }
154 
155     @Override
onDestroy()156     public void onDestroy() {
157         mSettingsPackageMonitor.unregister();
158         super.onDestroy();
159     }
160 
161     /**
162      * Gets the preference that allows to add a new autofill service.
163      *
164      * @return The preference or {@code null} if no service can be added
165      */
newAddServicePreferenceOrNull()166     private Preference newAddServicePreferenceOrNull() {
167         final String searchUri =
168                 Settings.Secure.getStringForUser(
169                         getActivity().getContentResolver(),
170                         Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI,
171                         getUser());
172         if (TextUtils.isEmpty(searchUri)) {
173             return null;
174         }
175 
176         final Intent addNewServiceIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri));
177         final Context context = getPrefContext();
178         final Preference preference = new Preference(context);
179         preference.setOnPreferenceClickListener(
180                 p -> {
181                     context.startActivityAsUser(addNewServiceIntent, UserHandle.of(getUser()));
182                     return true;
183                 });
184         preference.setTitle(R.string.print_menu_item_add_service);
185         preference.setIcon(R.drawable.ic_add_24dp);
186         preference.setOrder(Integer.MAX_VALUE - 1);
187         preference.setPersistent(false);
188         return preference;
189     }
190 
191     /**
192      * Add a preference that allows the user to add a service if the market link for that is
193      * configured.
194      */
addAddServicePreference()195     private void addAddServicePreference() {
196         final Preference addNewServicePreference = newAddServicePreferenceOrNull();
197         if (addNewServicePreference != null) {
198             getPreferenceScreen().addPreference(addNewServicePreference);
199         }
200     }
201 
202     /**
203      * Get the Credential Manager service if we haven't already got it. We need to get the service
204      * later because if we do it in onCreate it will fail.
205      */
getCredentialProviderService()206     private @Nullable CredentialManager getCredentialProviderService() {
207         if (mCredentialManager == null) {
208             mCredentialManager = getContext().getSystemService(CredentialManager.class);
209         }
210         return mCredentialManager;
211     }
212 
getAllProviders()213     private List<CombinedProviderInfo> getAllProviders() {
214         final Context context = getContext();
215         final List<AutofillServiceInfo> autofillProviders =
216                 AutofillServiceInfo.getAvailableServices(context, getUser());
217 
218         final CredentialManager service = getCredentialProviderService();
219         final List<CredentialProviderInfo> credManProviders = new ArrayList<>();
220         if (service != null) {
221             credManProviders.addAll(
222                     service.getCredentialProviderServices(
223                             getUser(), CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY));
224         }
225 
226         final String selectedAutofillProvider = getSelectedAutofillProvider(context, getUser());
227         return CombinedProviderInfo.buildMergedList(
228                 autofillProviders, credManProviders, selectedAutofillProvider);
229     }
230 
getSelectedAutofillProvider(Context context, int userId)231     public static String getSelectedAutofillProvider(Context context, int userId) {
232         return Settings.Secure.getStringForUser(
233                 context.getContentResolver(), AUTOFILL_SETTING, userId);
234     }
235 
getCandidates()236     protected List<DefaultAppInfo> getCandidates() {
237         final Context context = getContext();
238         final List<CombinedProviderInfo> allProviders = getAllProviders();
239         final List<DefaultAppInfo> candidates = new ArrayList<>();
240 
241         for (CombinedProviderInfo cpi : allProviders) {
242             ServiceInfo brandingService = cpi.getBrandingService();
243             if (brandingService == null) {
244                 candidates.add(
245                         new DefaultAppInfo(
246                                 context,
247                                 mPm,
248                                 getUser(),
249                                 cpi.getApplicationInfo(),
250                                 cpi.getSettingsSubtitle(),
251                                 true));
252             } else {
253                 candidates.add(
254                         new DefaultAppInfo(
255                                 context,
256                                 mPm,
257                                 getUser(),
258                                 brandingService,
259                                 cpi.getSettingsSubtitle(),
260                                 true));
261             }
262         }
263 
264         return candidates;
265     }
266 
267     @Override
getDefaultKey()268     protected String getDefaultKey() {
269         final CombinedProviderInfo topProvider =
270                 CombinedProviderInfo.getTopProvider(getAllProviders());
271         return topProvider == null ? "" : topProvider.getApplicationInfo().packageName;
272     }
273 
274     @Override
getConfirmationMessage(CandidateInfo appInfo)275     protected CharSequence getConfirmationMessage(CandidateInfo appInfo) {
276         // If we are selecting none then show a warning label.
277         if (appInfo == null) {
278             final String message =
279                     getContext()
280                             .getString(
281                                     R.string.credman_confirmation_message);
282             return Html.fromHtml(message);
283         }
284         final CharSequence appName = appInfo.loadLabel();
285         final String message =
286                 getContext()
287                         .getString(
288                                 R.string.credman_autofill_confirmation_message,
289                                 Html.escapeHtml(appName));
290         return Html.fromHtml(message);
291     }
292 
293     @Override
setDefaultKey(String key)294     protected boolean setDefaultKey(String key) {
295         // Get the list of providers and see if any match the key (package name).
296         final List<CombinedProviderInfo> allProviders = getAllProviders();
297         CombinedProviderInfo matchedProvider = null;
298         for (CombinedProviderInfo cpi : allProviders) {
299             if (cpi.getApplicationInfo().packageName.equals(key)) {
300                 matchedProvider = cpi;
301                 break;
302             }
303         }
304 
305         // If there were none then clear the stored providers.
306         if (matchedProvider == null) {
307             setProviders(null, new ArrayList<>());
308             return true;
309         }
310 
311         // Get the component names and save them.
312         final List<String> credManComponents = new ArrayList<>();
313         for (CredentialProviderInfo pi : matchedProvider.getCredentialProviderInfos()) {
314             credManComponents.add(pi.getServiceInfo().getComponentName().flattenToString());
315         }
316 
317         String autofillValue = null;
318         if (matchedProvider.getAutofillServiceInfo() != null) {
319             autofillValue =
320                     matchedProvider
321                             .getAutofillServiceInfo()
322                             .getServiceInfo()
323                             .getComponentName()
324                             .flattenToString();
325         }
326 
327         setProviders(autofillValue, credManComponents);
328 
329         // Check if activity was launched from Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE
330         // intent, and set proper result if so...
331         final Activity activity = getActivity();
332         if (activity != null) {
333             final String packageName = activity.getIntent().getStringExtra(EXTRA_PACKAGE_NAME);
334             if (packageName != null) {
335                 final int result =
336                         key != null && key.startsWith(packageName)
337                                 ? Activity.RESULT_OK
338                                 : Activity.RESULT_CANCELED;
339                 activity.setResult(result);
340                 activity.finish();
341             }
342         }
343 
344         // TODO: Notify the rest
345 
346         return true;
347     }
348 
setProviders(String autofillProvider, List<String> primaryCredManProviders)349     private void setProviders(String autofillProvider, List<String> primaryCredManProviders) {
350         if (TextUtils.isEmpty(autofillProvider)) {
351             if (primaryCredManProviders.size() > 0) {
352                 autofillProvider =
353                         CredentialManagerPreferenceController
354                                 .AUTOFILL_CREDMAN_ONLY_PROVIDER_PLACEHOLDER;
355             }
356         }
357 
358         Settings.Secure.putStringForUser(
359                 getContext().getContentResolver(), AUTOFILL_SETTING, autofillProvider, getUser());
360 
361         final CredentialManager service = getCredentialProviderService();
362         if (service == null) {
363             return;
364         }
365 
366         // Get the existing secondary providers since we don't touch them in
367         // this part of the UI we should just copy them over.
368         final List<String> credManProviders = new ArrayList<>();
369         for (CredentialProviderInfo cpi :
370                 service.getCredentialProviderServices(
371                         getUser(), CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY)) {
372 
373             if (cpi.isEnabled() && !cpi.isPrimary()) {
374                 credManProviders.add(cpi.getServiceInfo().getComponentName().flattenToString());
375             }
376         }
377 
378         credManProviders.addAll(primaryCredManProviders);
379 
380         // If there is no provider then clear all the providers.
381         if (TextUtils.isEmpty(autofillProvider) && primaryCredManProviders.isEmpty()) {
382             credManProviders.clear();
383         }
384 
385         service.setEnabledProviders(
386                 primaryCredManProviders,
387                 credManProviders,
388                 getUser(),
389                 ContextCompat.getMainExecutor(getContext()),
390                 new OutcomeReceiver<Void, SetEnabledProvidersException>() {
391                     @Override
392                     public void onResult(Void result) {
393                         Log.i(TAG, "setEnabledProviders success");
394                     }
395 
396                     @Override
397                     public void onError(SetEnabledProvidersException e) {
398                         Log.e(TAG, "setEnabledProviders error: " + e.toString());
399                     }
400                 });
401     }
402 
getUser()403     protected int getUser() {
404         if (mIntentSenderUserId >= 0) {
405             return mIntentSenderUserId;
406         }
407         return UserHandle.myUserId();
408     }
409 }
410