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