• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.defaultapps;
18 
19 import android.Manifest;
20 import android.app.Activity;
21 import android.app.settings.SettingsEnums;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.content.Intent;
26 import android.content.pm.PackageManager;
27 import android.content.pm.ResolveInfo;
28 import android.content.pm.ServiceInfo;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.os.UserHandle;
32 import android.provider.Settings;
33 import android.service.autofill.AutofillService;
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.preference.Preference;
40 
41 import com.android.internal.content.PackageMonitor;
42 import com.android.settings.R;
43 import com.android.settingslib.applications.DefaultAppInfo;
44 import com.android.settingslib.utils.ThreadUtils;
45 import com.android.settingslib.widget.CandidateInfo;
46 
47 import java.util.ArrayList;
48 import java.util.List;
49 
50 public class DefaultAutofillPicker extends DefaultAppPickerFragment {
51 
52     private static final String TAG = "DefaultAutofillPicker";
53 
54     static final String SETTING = Settings.Secure.AUTOFILL_SERVICE;
55     static final Intent AUTOFILL_PROBE = new Intent(AutofillService.SERVICE_INTERFACE);
56 
57     /**
58      * Extra set when the fragment is implementing ACTION_REQUEST_SET_AUTOFILL_SERVICE.
59      */
60     public static final String EXTRA_PACKAGE_NAME = "package_name";
61 
62     /**
63      * Set when the fragment is implementing ACTION_REQUEST_SET_AUTOFILL_SERVICE.
64      */
65     private DialogInterface.OnClickListener mCancelListener;
66 
67     @Override
onCreate(Bundle savedInstanceState)68     public void onCreate(Bundle savedInstanceState) {
69         super.onCreate(savedInstanceState);
70 
71         final Activity activity = getActivity();
72         if (activity != null && activity.getIntent().getStringExtra(EXTRA_PACKAGE_NAME) != null) {
73             mCancelListener = (d, w) -> {
74                 activity.setResult(Activity.RESULT_CANCELED);
75                 activity.finish();
76             };
77             // If mCancelListener is not null, fragment is started from
78             // ACTION_REQUEST_SET_AUTOFILL_SERVICE and we should always use the calling uid.
79             mUserId = UserHandle.myUserId();
80         }
81         mSettingsPackageMonitor.register(activity, activity.getMainLooper(), false);
82         update();
83     }
84 
85     @Override
newConfirmationDialogFragment(String selectedKey, CharSequence confirmationMessage)86     protected ConfirmationDialogFragment newConfirmationDialogFragment(String selectedKey,
87             CharSequence confirmationMessage) {
88         final AutofillPickerConfirmationDialogFragment fragment =
89                 new AutofillPickerConfirmationDialogFragment();
90         fragment.init(this, selectedKey, confirmationMessage);
91         return fragment;
92     }
93 
94     /**
95      * Custom dialog fragment that has a cancel listener used to propagate the result back to
96      * caller (for the cases where the picker is launched by
97      * {@code android.settings.REQUEST_SET_AUTOFILL_SERVICE}.
98      */
99     public static class AutofillPickerConfirmationDialogFragment
100             extends ConfirmationDialogFragment {
101 
102         @Override
onCreate(Bundle savedInstanceState)103         public void onCreate(Bundle savedInstanceState) {
104             final DefaultAutofillPicker target = (DefaultAutofillPicker) getTargetFragment();
105             setCancelListener(target.mCancelListener);
106             super.onCreate(savedInstanceState);
107         }
108     }
109 
110     @Override
getPreferenceScreenResId()111     protected int getPreferenceScreenResId() {
112         return R.xml.default_autofill_settings;
113     }
114 
115     @Override
getMetricsCategory()116     public int getMetricsCategory() {
117         return SettingsEnums.DEFAULT_AUTOFILL_PICKER;
118     }
119 
120     @Override
shouldShowItemNone()121     protected boolean shouldShowItemNone() {
122         return true;
123     }
124 
125     /**
126      * Monitor coming and going auto fill services and calls {@link #update()} when necessary
127      */
128     private final PackageMonitor mSettingsPackageMonitor = new PackageMonitor() {
129         @Override
130         public void onPackageAdded(String packageName, int uid) {
131             ThreadUtils.postOnMainThread(() -> update());
132         }
133 
134         @Override
135         public void onPackageModified(String packageName) {
136             ThreadUtils.postOnMainThread(() -> update());
137         }
138 
139         @Override
140         public void onPackageRemoved(String packageName, int uid) {
141             ThreadUtils.postOnMainThread(() -> update());
142         }
143     };
144 
145     /**
146      * Update the data in this UI.
147      */
update()148     private void update() {
149         updateCandidates();
150         addAddServicePreference();
151     }
152 
153     @Override
onDestroy()154     public void onDestroy() {
155         mSettingsPackageMonitor.unregister();
156         super.onDestroy();
157     }
158 
159     /**
160      * Gets the preference that allows to add a new autofill service.
161      *
162      * @return The preference or {@code null} if no service can be added
163      */
newAddServicePreferenceOrNull()164     private Preference newAddServicePreferenceOrNull() {
165         final String searchUri = Settings.Secure.getStringForUser(
166                 getActivity().getContentResolver(),
167                 Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI,
168                 mUserId);
169         if (TextUtils.isEmpty(searchUri)) {
170             return null;
171         }
172 
173         final Intent addNewServiceIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri));
174         final Context context = getPrefContext();
175         final Preference preference = new Preference(context);
176         preference.setOnPreferenceClickListener(p -> {
177                     context.startActivityAsUser(addNewServiceIntent, UserHandle.of(mUserId));
178                     return true;
179                 });
180         preference.setTitle(R.string.print_menu_item_add_service);
181         preference.setIcon(R.drawable.ic_add_24dp);
182         preference.setOrder(Integer.MAX_VALUE -1);
183         preference.setPersistent(false);
184         return preference;
185     }
186 
187     /**
188      * Add a preference that allows the user to add a service if the market link for that is
189      * configured.
190      */
addAddServicePreference()191     private void addAddServicePreference() {
192         final Preference addNewServicePreference = newAddServicePreferenceOrNull();
193         if (addNewServicePreference != null) {
194             getPreferenceScreen().addPreference(addNewServicePreference);
195         }
196     }
197 
198     @Override
getCandidates()199     protected List<DefaultAppInfo> getCandidates() {
200         final List<DefaultAppInfo> candidates = new ArrayList<>();
201         final List<ResolveInfo> resolveInfos = mPm.queryIntentServicesAsUser(
202                 AUTOFILL_PROBE, PackageManager.GET_META_DATA, mUserId);
203         final Context context = getContext();
204         for (ResolveInfo info : resolveInfos) {
205             final String permission = info.serviceInfo.permission;
206             if (Manifest.permission.BIND_AUTOFILL_SERVICE.equals(permission)) {
207                 candidates.add(new DefaultAppInfo(context, mPm, mUserId, new ComponentName(
208                         info.serviceInfo.packageName, info.serviceInfo.name)));
209             }
210             if (Manifest.permission.BIND_AUTOFILL.equals(permission)) {
211                 // Let it go for now...
212                 Log.w(TAG, "AutofillService from '" + info.serviceInfo.packageName
213                         + "' uses unsupported permission " + Manifest.permission.BIND_AUTOFILL
214                         + ". It works for now, but might not be supported on future releases");
215                 candidates.add(new DefaultAppInfo(context, mPm, mUserId, new ComponentName(
216                         info.serviceInfo.packageName, info.serviceInfo.name)));
217             }
218         }
219         return candidates;
220     }
221 
getDefaultKey(Context context, int userId)222     public static String getDefaultKey(Context context, int userId) {
223         String setting = Settings.Secure.getStringForUser(
224                 context.getContentResolver(), SETTING, userId);
225         if (setting != null) {
226             ComponentName componentName = ComponentName.unflattenFromString(setting);
227             if (componentName != null) {
228                 return componentName.flattenToString();
229             }
230         }
231         return null;
232     }
233 
234     @Override
getDefaultKey()235     protected String getDefaultKey() {
236         return getDefaultKey(getContext(), mUserId);
237     }
238 
239     @Override
getConfirmationMessage(CandidateInfo appInfo)240     protected CharSequence getConfirmationMessage(CandidateInfo appInfo) {
241         if (appInfo == null) {
242             return null;
243         }
244         final CharSequence appName = appInfo.loadLabel();
245         final String message = getContext().getString(
246                 R.string.autofill_confirmation_message, appName);
247         return Html.fromHtml(message);
248     }
249 
250     @Override
setDefaultKey(String key)251     protected boolean setDefaultKey(String key) {
252         Settings.Secure.putStringForUser(getContext().getContentResolver(), SETTING, key, mUserId);
253 
254         // Check if activity was launched from Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE
255         // intent, and set proper result if so...
256         final Activity activity = getActivity();
257         if (activity != null) {
258             final String packageName = activity.getIntent().getStringExtra(EXTRA_PACKAGE_NAME);
259             if (packageName != null) {
260                 final int result = key != null && key.startsWith(packageName) ? Activity.RESULT_OK
261                         : Activity.RESULT_CANCELED;
262                 activity.setResult(result);
263                 activity.finish();
264             }
265         }
266         return true;
267     }
268 
269     /**
270      * Provides Intent to setting activity for the specified autofill service.
271      */
272     static final class AutofillSettingIntentProvider implements SettingIntentProvider {
273 
274         private final String mSelectedKey;
275         private final Context mContext;
276         private final int mUserId;
277 
AutofillSettingIntentProvider(Context context, int userId, String key)278         public AutofillSettingIntentProvider(Context context, int userId, String key) {
279             mSelectedKey = key;
280             mContext = context;
281             mUserId = userId;
282         }
283 
284         @Override
getIntent()285         public Intent getIntent() {
286             final List<ResolveInfo> resolveInfos = mContext.getPackageManager()
287                     .queryIntentServicesAsUser(
288                             AUTOFILL_PROBE, PackageManager.GET_META_DATA, mUserId);
289 
290             for (ResolveInfo resolveInfo : resolveInfos) {
291                 final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
292                 final String flattenKey = new ComponentName(
293                         serviceInfo.packageName, serviceInfo.name).flattenToString();
294                 if (TextUtils.equals(mSelectedKey, flattenKey)) {
295                     final String settingsActivity;
296                     try {
297                         settingsActivity = new AutofillServiceInfo(mContext, serviceInfo)
298                                 .getSettingsActivity();
299                     } catch (SecurityException e) {
300                         // Service does not declare the proper permission, ignore it.
301                         Log.w(TAG, "Error getting info for " + serviceInfo + ": " + e);
302                         return null;
303                     }
304                     if (TextUtils.isEmpty(settingsActivity)) {
305                         return null;
306                     }
307                     return new Intent(Intent.ACTION_MAIN).setComponent(
308                             new ComponentName(serviceInfo.packageName, settingsActivity));
309                 }
310             }
311 
312             return null;
313         }
314     }
315 }
316