DefaultAutofillPicker.java revision 7d3fb0481dd42af4f359fa3b2f5039c52f5825c0
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
17package com.android.settings.applications.defaultapps;
18
19import android.Manifest;
20import android.app.Activity;
21import android.content.ComponentName;
22import android.content.ContentResolver;
23import android.content.Context;
24import android.content.DialogInterface;
25import android.content.Intent;
26import android.content.pm.PackageManager;
27import android.content.pm.ResolveInfo;
28import android.content.pm.ServiceInfo;
29import android.net.Uri;
30import android.os.Bundle;
31import android.os.Handler;
32import android.os.Looper;
33import android.provider.Settings;
34import android.service.autofill.AutofillService;
35import android.service.autofill.AutofillServiceInfo;
36import android.support.v7.preference.Preference;
37import android.text.Html;
38import android.text.TextUtils;
39import android.util.Log;
40
41import com.android.internal.content.PackageMonitor;
42import com.android.internal.logging.nano.MetricsProto;
43import com.android.settings.R;
44
45import java.util.ArrayList;
46import java.util.List;
47
48public 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    public DialogInterface.OnClickListener mCancelListener;
64    private final Handler mHandler = new Handler();
65
66    @Override
67    public void onCreate(Bundle savedInstanceState) {
68        super.onCreate(savedInstanceState);
69
70        final Activity activity = getActivity();
71        if (activity != null && activity.getIntent().getStringExtra(EXTRA_PACKAGE_NAME) != null) {
72            mCancelListener = (d, w) -> {
73                activity.setResult(Activity.RESULT_CANCELED);
74                activity.finish();
75            };
76        }
77
78        mSettingsPackageMonitor.register(getActivity(), getActivity().getMainLooper(), false);
79        update();
80    }
81
82    @Override
83    protected ConfirmationDialogFragment newConfirmationDialogFragment(String selectedKey,
84            CharSequence confirmationMessage) {
85        return ConfirmationDialogFragment.newInstance(this, selectedKey, confirmationMessage,
86                mCancelListener);
87    }
88
89    @Override
90    public int getMetricsCategory() {
91        return MetricsProto.MetricsEvent.DEFAULT_AUTOFILL_PICKER;
92    }
93
94    @Override
95    protected boolean shouldShowItemNone() {
96        return true;
97    }
98
99    /**
100     * Monitor coming and going auto fill services and calls {@link #update()} when necessary
101     */
102    private final PackageMonitor mSettingsPackageMonitor = new PackageMonitor() {
103        @Override
104        public void onPackageAdded(String packageName, int uid) {
105            mHandler.post(() -> update());
106        }
107
108        @Override
109        public void onPackageModified(String packageName) {
110            mHandler.post(() -> update());
111        }
112
113        @Override
114        public void onPackageRemoved(String packageName, int uid) {
115            mHandler.post(() -> update());
116        }
117    };
118
119    /**
120     * Update the data in this UI.
121     */
122    private void update() {
123        updateCandidates();
124        addAddServicePreference();
125    }
126
127    @Override
128    public void onDestroy() {
129        mSettingsPackageMonitor.unregister();
130        super.onDestroy();
131    }
132
133    /**
134     * Gets the preference that allows to add a new autofill service.
135     *
136     * @return The preference or {@code null} if no service can be added
137     */
138    private Preference newAddServicePreferenceOrNull() {
139        final String searchUri = Settings.Secure.getString(getActivity().getContentResolver(),
140                Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI);
141        if (TextUtils.isEmpty(searchUri)) {
142            return null;
143        }
144
145        final Intent addNewServiceIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri));
146        Preference preference = new Preference(getPrefContext());
147        preference.setTitle(R.string.print_menu_item_add_service);
148        preference.setIcon(R.drawable.ic_menu_add);
149        preference.setOrder(Integer.MAX_VALUE -1);
150        preference.setIntent(addNewServiceIntent);
151        preference.setPersistent(false);
152        return preference;
153    }
154
155    /**
156     * Add a preference that allows the user to add a service if the market link for that is
157     * configured.
158     */
159    private void addAddServicePreference() {
160        final Preference addNewServicePreference = newAddServicePreferenceOrNull();
161        if (addNewServicePreference != null) {
162            getPreferenceScreen().addPreference(addNewServicePreference);
163        }
164    }
165
166    @Override
167    protected List<DefaultAppInfo> getCandidates() {
168        final List<DefaultAppInfo> candidates = new ArrayList<>();
169        final List<ResolveInfo> resolveInfos = mPm.getPackageManager()
170                .queryIntentServices(AUTOFILL_PROBE, PackageManager.GET_META_DATA);
171        for (ResolveInfo info : resolveInfos) {
172            final String permission = info.serviceInfo.permission;
173            // TODO(b/37563972): remove BIND_AUTOFILL once clients use BIND_AUTOFILL_SERVICE
174            if (Manifest.permission.BIND_AUTOFILL_SERVICE.equals(permission)
175                    || Manifest.permission.BIND_AUTOFILL.equals(permission)) {
176                candidates.add(new DefaultAppInfo(mPm, mUserId, new ComponentName(
177                        info.serviceInfo.packageName, info.serviceInfo.name)));
178            }
179        }
180        return candidates;
181    }
182
183    public static String getDefaultKey(Context context) {
184        String setting = Settings.Secure.getString(context.getContentResolver(), SETTING);
185        if (setting != null) {
186            ComponentName componentName = ComponentName.unflattenFromString(setting);
187            if (componentName != null) {
188                return componentName.flattenToString();
189            }
190        }
191        return null;
192    }
193
194    @Override
195    protected String getDefaultKey() {
196        return getDefaultKey(getContext());
197    }
198
199    @Override
200    protected CharSequence getConfirmationMessage(CandidateInfo appInfo) {
201        if (appInfo == null) {
202            return null;
203        }
204        final CharSequence appName = appInfo.loadLabel();
205        final String message = getContext().getString(
206                R.string.autofill_confirmation_message, appName);
207        return Html.fromHtml(message);
208    }
209
210    @Override
211    protected boolean setDefaultKey(String key) {
212        Settings.Secure.putString(getContext().getContentResolver(), SETTING, key);
213
214        // Check if activity was launched from Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE
215        // intent, and set proper result if so...
216        final Activity activity = getActivity();
217        if (activity != null) {
218            final String packageName = activity.getIntent().getStringExtra(EXTRA_PACKAGE_NAME);
219            if (packageName != null) {
220                final int result = key != null && key.startsWith(packageName) ? Activity.RESULT_OK
221                        : Activity.RESULT_CANCELED;
222                activity.setResult(result);
223                activity.finish();
224            }
225        }
226        return true;
227    }
228
229    /**
230     * Provides Intent to setting activity for the specified autofill service.
231     */
232    static final class AutofillSettingIntentProvider implements SettingIntentProvider {
233
234        private final String mSelectedKey;
235        private final PackageManager mPackageManager;
236
237        public AutofillSettingIntentProvider(PackageManager packageManager, String key) {
238            mSelectedKey = key;
239            mPackageManager = packageManager;
240        }
241
242        @Override
243        public Intent getIntent() {
244            final List<ResolveInfo> resolveInfos = mPackageManager.queryIntentServices(
245                    AUTOFILL_PROBE, PackageManager.GET_META_DATA);
246
247            for (ResolveInfo resolveInfo : resolveInfos) {
248                final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
249                final String flattenKey = new ComponentName(
250                        serviceInfo.packageName, serviceInfo.name).flattenToString();
251                if (TextUtils.equals(mSelectedKey, flattenKey)) {
252                    final String settingsActivity;
253                    try {
254                        settingsActivity = new AutofillServiceInfo(mPackageManager, serviceInfo)
255                                .getSettingsActivity();
256                    } catch (SecurityException e) {
257                        // Service does not declare the proper permission, ignore it.
258                        Log.w(TAG, "Error getting info for " + serviceInfo + ": " + e);
259                        return null;
260                    }
261                    if (TextUtils.isEmpty(settingsActivity)) {
262                        return null;
263                    }
264                    return new Intent(Intent.ACTION_MAIN).setComponent(
265                            new ComponentName(serviceInfo.packageName, settingsActivity));
266                }
267            }
268
269            return null;
270        }
271    }
272}
273