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.content.Context;
20 import android.content.Intent;
21 import android.credentials.CredentialManager;
22 import android.credentials.CredentialProviderInfo;
23 import android.credentials.SetEnabledProvidersException;
24 import android.graphics.drawable.Drawable;
25 import android.os.OutcomeReceiver;
26 import android.os.UserHandle;
27 import android.provider.Settings;
28 import android.service.autofill.AutofillService;
29 import android.service.autofill.AutofillServiceInfo;
30 import android.text.TextUtils;
31 import android.util.Log;
32 import android.view.autofill.AutofillManager;
33 
34 import androidx.annotation.NonNull;
35 import androidx.annotation.Nullable;
36 import androidx.core.content.ContextCompat;
37 import androidx.preference.Preference;
38 
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.settings.R;
41 import com.android.settings.Utils;
42 import com.android.settings.applications.defaultapps.DefaultAppPreferenceController;
43 import com.android.settingslib.applications.DefaultAppInfo;
44 import com.android.settingslib.widget.TwoTargetPreference;
45 
46 import java.util.ArrayList;
47 import java.util.List;
48 import java.util.concurrent.Executor;
49 
50 public class DefaultCombinedPreferenceController extends DefaultAppPreferenceController {
51 
52     private static final Intent AUTOFILL_PROBE = new Intent(AutofillService.SERVICE_INTERFACE);
53     private static final String TAG = "DefaultCombinedPreferenceController";
54 
55     private final AutofillManager mAutofillManager;
56     private final CredentialManager mCredentialManager;
57     private final Executor mExecutor;
58 
DefaultCombinedPreferenceController(Context context)59     public DefaultCombinedPreferenceController(Context context) {
60         super(context);
61 
62         mExecutor = ContextCompat.getMainExecutor(context);
63         mAutofillManager = mContext.getSystemService(AutofillManager.class);
64 
65         if (CredentialManager.isServiceEnabled(context)) {
66             mCredentialManager = mContext.getSystemService(CredentialManager.class);
67         } else {
68             mCredentialManager = null;
69         }
70     }
71 
72     @Override
isAvailable()73     public boolean isAvailable() {
74         return mAutofillManager != null
75                 && mCredentialManager != null
76                 && mAutofillManager.hasAutofillFeature()
77                 && mAutofillManager.isAutofillSupported();
78     }
79 
80     @Override
getPreferenceKey()81     public String getPreferenceKey() {
82         return "default_credman_autofill_main";
83     }
84 
85     @Override
getSettingIntent(DefaultAppInfo info)86     protected Intent getSettingIntent(DefaultAppInfo info) {
87         // Despite this method being called getSettingIntent this intent actually
88         // opens the primary picker. This is so that we can swap the cog and the left
89         // hand side presses to align the UX.
90         if (PrimaryProviderPreference.shouldUseNewSettingsUi()) {
91             // We need to return an empty intent here since the class we inherit
92             // from will throw an NPE if we return null and we don't want it to
93             // open anything since we added the buttons.
94             return new Intent();
95         }
96         return createIntentToOpenPicker();
97     }
98 
99     @Override
updateState(@onNull Preference preference)100     public void updateState(@NonNull Preference preference) {
101         final CombinedProviderInfo topProvider = getTopProvider();
102         final int userId = getUser();
103 
104         if (topProvider != null && mContext != null) {
105             updatePreferenceForProvider(
106                     preference,
107                     topProvider.getAppName(mContext),
108                     topProvider.getSettingsSubtitle(),
109                     topProvider.getAppIcon(mContext, userId),
110                     topProvider.getPackageName(),
111                     topProvider.getSettingsActivity());
112         } else {
113             updatePreferenceForProvider(preference, null, null, null, null, null);
114         }
115     }
116 
117     @VisibleForTesting
updatePreferenceForProvider( Preference preference, @Nullable CharSequence appName, @Nullable String appSubtitle, @Nullable Drawable appIcon, @Nullable String packageName, @Nullable CharSequence settingsActivity)118     public void updatePreferenceForProvider(
119             Preference preference,
120             @Nullable CharSequence appName,
121             @Nullable String appSubtitle,
122             @Nullable Drawable appIcon,
123             @Nullable String packageName,
124             @Nullable CharSequence settingsActivity) {
125         if (appName == null) {
126             preference.setTitle(R.string.credman_app_list_preference_none);
127         } else {
128             preference.setTitle(appName);
129         }
130 
131         if (appIcon == null) {
132             preference.setIcon(null);
133         } else {
134             preference.setIcon(Utils.getSafeIcon(appIcon));
135         }
136 
137         preference.setSummary(appSubtitle);
138 
139         if (preference instanceof PrimaryProviderPreference) {
140             PrimaryProviderPreference primaryPref = (PrimaryProviderPreference) preference;
141             primaryPref.setIconSize(TwoTargetPreference.ICON_SIZE_MEDIUM);
142             primaryPref.setDelegate(
143                     new PrimaryProviderPreference.Delegate() {
144                         public void onOpenButtonClicked() {
145                             CombinedProviderInfo.launchSettingsActivityIntent(
146                                     mContext, packageName, settingsActivity, getUser());
147                         }
148 
149                         public void onChangeButtonClicked() {
150                             startActivity(createIntentToOpenPicker());
151                         }
152                     });
153 
154             // Hide the open button if there is no defined settings activity.
155             primaryPref.setOpenButtonVisible(!TextUtils.isEmpty(settingsActivity));
156             primaryPref.setButtonsCompactMode(appName != null);
157         }
158     }
159 
getTopProvider()160     private @Nullable CombinedProviderInfo getTopProvider() {
161         final int userId = getUser();
162         final @Nullable CombinedProviderInfo topProvider =
163                 CombinedProviderInfo.getTopProvider(getAllProviders(userId));
164 
165         // Apply device admin restrictions to top provider.
166         if (topProvider != null
167                 && topProvider.getDeviceAdminRestrictions(mContext, userId) != null) {
168             // This case means, the provider is blocked by device admin, but settings' storage has
169             // not be cleared correctly. So clean the storage here.
170             removePrimaryProvider();
171             return null;
172         }
173 
174         return topProvider;
175     }
176 
177     @Override
getDefaultAppInfo()178     protected DefaultAppInfo getDefaultAppInfo() {
179         return null;
180     }
181 
getAllProviders(int userId)182     private List<CombinedProviderInfo> getAllProviders(int userId) {
183         final List<AutofillServiceInfo> autofillProviders =
184                 AutofillServiceInfo.getAvailableServices(mContext, userId);
185         final String selectedAutofillProvider =
186                 CredentialManagerPreferenceController
187                 .getSelectedAutofillProvider(mContext, userId, TAG);
188 
189         final List<CredentialProviderInfo> credManProviders = new ArrayList<>();
190         if (mCredentialManager != null) {
191             credManProviders.addAll(
192                     mCredentialManager.getCredentialProviderServices(
193                             userId,
194                             CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_INCLUDING_HIDDEN));
195         }
196 
197         return CombinedProviderInfo.buildMergedList(
198                 autofillProviders, credManProviders, selectedAutofillProvider);
199     }
200 
201     @Override
showLabelAsTitle()202     protected boolean showLabelAsTitle() {
203         return true;
204     }
205 
206     @Override
showAppSummary()207     protected boolean showAppSummary() {
208         return true;
209     }
210 
getUser()211     protected int getUser() {
212         return UserHandle.myUserId();
213     }
214 
215     /** Creates an intent to open the credential picker. */
createIntentToOpenPicker()216     private Intent createIntentToOpenPicker() {
217         final Context context =
218                 mContext.createContextAsUser(UserHandle.of(getUser()), /* flags= */ 0);
219         return new Intent(context, CredentialsPickerActivity.class);
220     }
221 
removePrimaryProvider()222     private void removePrimaryProvider() {
223         // Commit using the CredMan API.
224         if (mCredentialManager == null) {
225             return;
226         }
227 
228         // Clean the autofill provider settings
229         Settings.Secure.putStringForUser(
230                 mContext.getContentResolver(),
231                 DefaultCombinedPicker.AUTOFILL_SETTING, null, getUser());
232 
233         // Clean the credman provider settings.
234         mCredentialManager.setEnabledProviders(
235                 List.of(), // empty primary provider.
236                 List.of(), // empty enabled providers.
237                 getUser(),
238                 mExecutor,
239                 new OutcomeReceiver<Void, SetEnabledProvidersException>() {
240                     @Override
241                     public void onResult(Void result) {
242                         Log.i(TAG, "setEnabledProviders success");
243                     }
244 
245                     @Override
246                     public void onError(SetEnabledProvidersException e) {
247                         Log.e(TAG, "setEnabledProviders error: " + e.toString());
248                     }
249                 });
250     }
251 }
252