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