Fork autofill default app selection

We are merging the default app selection for autofill
with credman so this forks the existing UI (so we can
flag it off).

Test: ondevice
Bug: 278919696
Change-Id: I96bcf1ff86b169a182b9974f7384c45b474c3d5d
This commit is contained in:
Becca Hughes
2023-04-24 16:08:59 +00:00
parent 99f45dd59b
commit ddf359d0f8
13 changed files with 637 additions and 48 deletions

View File

@@ -44,8 +44,6 @@ import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.TextUtils;
import com.android.settingslib.utils.ThreadUtils;
import com.android.internal.content.PackageMonitor;
import android.util.IconDrawableFactory;
import android.util.Log;
@@ -127,6 +125,23 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
return null;
}
@Override
public int getAvailabilityStatus() {
if (mCredentialManager == null) {
return UNSUPPORTED_ON_DEVICE;
}
if (!isAutofillPrefSelected()) {
return CONDITIONALLY_UNAVAILABLE;
}
if (mServices.isEmpty()) {
return CONDITIONALLY_UNAVAILABLE;
}
return AVAILABLE;
}
@VisibleForTesting
public boolean isConnected() {
return mCredentialManager != null;
@@ -266,12 +281,15 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
if (mPreferenceScreen != null) {
displayPreference(mPreferenceScreen);
}
if (mDelegate != null) {
mDelegate.forceDelegateRefresh();
}
}
@VisibleForTesting
void setAvailableServices(
List<CredentialProviderInfo> availableServices,
String flagOverrideForTest) {
List<CredentialProviderInfo> availableServices, String flagOverrideForTest) {
mFlagOverrideForTest = flagOverrideForTest;
mServices.clear();
mServices.addAll(availableServices);
@@ -291,11 +309,6 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
}
}
@Override
public int getAvailabilityStatus() {
return mServices.isEmpty() ? CONDITIONALLY_UNAVAILABLE : AVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
@@ -305,6 +318,17 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
mPreferenceScreen = screen;
PreferenceGroup group = screen.findPreference(getPreferenceKey());
group.removeAll();
// Hide/show based on autofill pref.
boolean isVisible = isAutofillPrefSelected();
screen.setVisible(isVisible);
group.setVisible(isVisible);
if (!isVisible) {
return;
}
Context context = screen.getContext();
mPrefs.putAll(buildPreferenceList(context, group));
@@ -586,10 +610,9 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
/** If the provider is also the autofill provider then hide it. */
@VisibleForTesting
public boolean isProviderHiddenBecauseOfAutofill(String packageName) {
final String autofillService = Settings.Secure.getStringForUser(
mContext.getContentResolver(),
Settings.Secure.AUTOFILL_SERVICE,
getUser());
final String autofillService =
Settings.Secure.getStringForUser(
mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, getUser());
if (autofillService == null || TextUtils.isEmpty(autofillService)) {
return false;
}
@@ -600,6 +623,13 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
return autofillService.startsWith(packageName);
}
private boolean isAutofillPrefSelected() {
final String autofillService =
Settings.Secure.getStringForUser(
mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, getUser());
return !TextUtils.isEmpty(autofillService);
}
@VisibleForTesting
void completeEnableProviderDialogBox(
int whichButton, String packageName, boolean setActivityResult) {
@@ -682,27 +712,31 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
/** Called to send messages back to the parent fragment. */
public static interface Delegate {
void setActivityResult(int resultCode);
void forceDelegateRefresh();
}
/**
* Monitor coming and going credman services and calls {@link #update()} when necessary
* Monitor coming and going credman services and calls {@link #DefaultCombinedPicker} when
* necessary
*/
private final PackageMonitor mSettingsPackageMonitor = new PackageMonitor() {
@Override
public void onPackageAdded(String packageName, int uid) {
ThreadUtils.postOnMainThread(() -> update());
}
private final PackageMonitor mSettingsPackageMonitor =
new PackageMonitor() {
@Override
public void onPackageAdded(String packageName, int uid) {
ThreadUtils.postOnMainThread(() -> updateFromExternal());
}
@Override
public void onPackageModified(String packageName) {
ThreadUtils.postOnMainThread(() -> update());
}
@Override
public void onPackageModified(String packageName) {
ThreadUtils.postOnMainThread(() -> updateFromExternal());
}
@Override
public void onPackageRemoved(String packageName, int uid) {
ThreadUtils.postOnMainThread(() -> update());
}
};
@Override
public void onPackageRemoved(String packageName, int uid) {
ThreadUtils.postOnMainThread(() -> updateFromExternal());
}
};
/** Dialog fragment parent class. */
private abstract static class CredentialManagerDialogFragment extends DialogFragment

View File

@@ -0,0 +1,258 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.applications.credentials;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.autofill.AutofillService;
import android.service.autofill.AutofillServiceInfo;
import android.text.Html;
import android.text.TextUtils;
import androidx.preference.Preference;
import com.android.internal.content.PackageMonitor;
import com.android.settings.R;
import com.android.settings.applications.defaultapps.DefaultAppPickerFragment;
import com.android.settingslib.applications.DefaultAppInfo;
import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.CandidateInfo;
import java.util.ArrayList;
import java.util.List;
public class DefaultCombinedPicker extends DefaultAppPickerFragment {
private static final String TAG = "DefaultCombinedPicker";
public static final String SETTING = Settings.Secure.AUTOFILL_SERVICE;
public static final Intent AUTOFILL_PROBE = new Intent(AutofillService.SERVICE_INTERFACE);
/** Extra set when the fragment is implementing ACTION_REQUEST_SET_AUTOFILL_SERVICE. */
public static final String EXTRA_PACKAGE_NAME = "package_name";
/** Set when the fragment is implementing ACTION_REQUEST_SET_AUTOFILL_SERVICE. */
private DialogInterface.OnClickListener mCancelListener;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Activity activity = getActivity();
if (activity != null && activity.getIntent().getStringExtra(EXTRA_PACKAGE_NAME) != null) {
mCancelListener =
(d, w) -> {
activity.setResult(Activity.RESULT_CANCELED);
activity.finish();
};
// If mCancelListener is not null, fragment is started from
// ACTION_REQUEST_SET_AUTOFILL_SERVICE and we should always use the calling uid.
mUserId = UserHandle.myUserId();
}
mSettingsPackageMonitor.register(activity, activity.getMainLooper(), false);
update();
}
@Override
protected DefaultAppPickerFragment.ConfirmationDialogFragment newConfirmationDialogFragment(
String selectedKey, CharSequence confirmationMessage) {
final AutofillPickerConfirmationDialogFragment fragment =
new AutofillPickerConfirmationDialogFragment();
fragment.init(this, selectedKey, confirmationMessage);
return fragment;
}
/**
* Custom dialog fragment that has a cancel listener used to propagate the result back to caller
* (for the cases where the picker is launched by {@code
* android.settings.REQUEST_SET_AUTOFILL_SERVICE}.
*/
public static class AutofillPickerConfirmationDialogFragment
extends DefaultAppPickerFragment.ConfirmationDialogFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
final DefaultCombinedPicker target = (DefaultCombinedPicker) getTargetFragment();
setCancelListener(target.mCancelListener);
super.onCreate(savedInstanceState);
}
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.default_credman_picker;
}
@Override
public int getMetricsCategory() {
return SettingsEnums.DEFAULT_AUTOFILL_PICKER;
}
@Override
protected boolean shouldShowItemNone() {
return true;
}
/** Monitor coming and going auto fill services and calls {@link #update()} when necessary */
private final PackageMonitor mSettingsPackageMonitor =
new PackageMonitor() {
@Override
public void onPackageAdded(String packageName, int uid) {
ThreadUtils.postOnMainThread(() -> update());
}
@Override
public void onPackageModified(String packageName) {
ThreadUtils.postOnMainThread(() -> update());
}
@Override
public void onPackageRemoved(String packageName, int uid) {
ThreadUtils.postOnMainThread(() -> update());
}
};
/** Update the data in this UI. */
private void update() {
updateCandidates();
addAddServicePreference();
}
@Override
public void onDestroy() {
mSettingsPackageMonitor.unregister();
super.onDestroy();
}
/**
* Gets the preference that allows to add a new autofill service.
*
* @return The preference or {@code null} if no service can be added
*/
private Preference newAddServicePreferenceOrNull() {
final String searchUri =
Settings.Secure.getStringForUser(
getActivity().getContentResolver(),
Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI,
mUserId);
if (TextUtils.isEmpty(searchUri)) {
return null;
}
final Intent addNewServiceIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri));
final Context context = getPrefContext();
final Preference preference = new Preference(context);
preference.setOnPreferenceClickListener(
p -> {
context.startActivityAsUser(addNewServiceIntent, UserHandle.of(mUserId));
return true;
});
preference.setTitle(R.string.print_menu_item_add_service);
preference.setIcon(R.drawable.ic_add_24dp);
preference.setOrder(Integer.MAX_VALUE - 1);
preference.setPersistent(false);
return preference;
}
/**
* Add a preference that allows the user to add a service if the market link for that is
* configured.
*/
private void addAddServicePreference() {
final Preference addNewServicePreference = newAddServicePreferenceOrNull();
if (addNewServicePreference != null) {
getPreferenceScreen().addPreference(addNewServicePreference);
}
}
@Override
protected List<DefaultAppInfo> getCandidates() {
final List<DefaultAppInfo> candidates = new ArrayList<>();
final List<AutofillServiceInfo> services =
AutofillServiceInfo.getAvailableServices(getContext(), mUserId);
for (AutofillServiceInfo asi : services) {
candidates.add(
new DefaultAppInfo(
getContext(), mPm, mUserId, asi.getServiceInfo().getComponentName()));
}
return candidates;
}
public static String getDefaultKey(Context context, int userId) {
String setting =
Settings.Secure.getStringForUser(context.getContentResolver(), SETTING, userId);
if (setting != null) {
ComponentName componentName = ComponentName.unflattenFromString(setting);
if (componentName != null) {
return componentName.flattenToString();
}
}
return null;
}
@Override
protected String getDefaultKey() {
return getDefaultKey(getContext(), mUserId);
}
@Override
protected CharSequence getConfirmationMessage(CandidateInfo appInfo) {
if (appInfo == null) {
return null;
}
final CharSequence appName = appInfo.loadLabel();
final String message =
getContext()
.getString(
R.string.credman_autofill_confirmation_message,
Html.escapeHtml(appName));
return Html.fromHtml(message);
}
@Override
protected boolean setDefaultKey(String key) {
Settings.Secure.putStringForUser(getContext().getContentResolver(), SETTING, key, mUserId);
// Check if activity was launched from Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE
// intent, and set proper result if so...
final Activity activity = getActivity();
if (activity != null) {
final String packageName = activity.getIntent().getStringExtra(EXTRA_PACKAGE_NAME);
if (packageName != null) {
final int result =
key != null && key.startsWith(packageName)
? Activity.RESULT_OK
: Activity.RESULT_CANCELED;
activity.setResult(result);
activity.finish();
}
}
// TODO: Notify the rest
return true;
}
}

View File

@@ -0,0 +1,150 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.applications.credentials;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.credentials.CredentialManager;
import android.provider.Settings;
import android.service.autofill.AutofillServiceInfo;
import android.text.TextUtils;
import android.util.Log;
import android.view.autofill.AutofillManager;
import com.android.settings.applications.defaultapps.DefaultAppPreferenceController;
import com.android.settingslib.applications.DefaultAppInfo;
import java.util.List;
public class DefaultCombinedPreferenceController extends DefaultAppPreferenceController {
private final AutofillManager mAutofillManager;
private final CredentialManager mCredentialManager;
public DefaultCombinedPreferenceController(Context context) {
super(context);
mAutofillManager = mContext.getSystemService(AutofillManager.class);
if (CredentialManager.isServiceEnabled(context)) {
mCredentialManager = mContext.getSystemService(CredentialManager.class);
} else {
mCredentialManager = null;
}
}
@Override
public boolean isAvailable() {
return mAutofillManager != null
&& mCredentialManager != null
&& mAutofillManager.hasAutofillFeature()
&& mAutofillManager.isAutofillSupported();
}
@Override
public String getPreferenceKey() {
return "default_credman_autofill_main";
}
@Override
protected Intent getSettingIntent(DefaultAppInfo info) {
if (info == null) {
return null;
}
final AutofillSettingIntentProvider intentProvider =
new AutofillSettingIntentProvider(mContext, mUserId, info.getKey());
return intentProvider.getIntent();
}
@Override
protected DefaultAppInfo getDefaultAppInfo() {
final String flattenComponent =
Settings.Secure.getString(
mContext.getContentResolver(), DefaultCombinedPicker.SETTING);
if (!TextUtils.isEmpty(flattenComponent)) {
DefaultAppInfo appInfo =
new DefaultAppInfo(
mContext,
mPackageManager,
mUserId,
ComponentName.unflattenFromString(flattenComponent));
return appInfo;
}
return null;
}
@Override
protected boolean showLabelAsTitle() {
return true;
}
/** Provides Intent to setting activity for the specified autofill service. */
static final class AutofillSettingIntentProvider {
private final String mSelectedKey;
private final Context mContext;
private final int mUserId;
public AutofillSettingIntentProvider(Context context, int userId, String key) {
mSelectedKey = key;
mContext = context;
mUserId = userId;
}
public Intent getIntent() {
final List<ResolveInfo> resolveInfos =
mContext.getPackageManager()
.queryIntentServicesAsUser(
DefaultCombinedPicker.AUTOFILL_PROBE,
PackageManager.GET_META_DATA,
mUserId);
for (ResolveInfo resolveInfo : resolveInfos) {
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
final String flattenKey =
new ComponentName(serviceInfo.packageName, serviceInfo.name)
.flattenToString();
if (TextUtils.equals(mSelectedKey, flattenKey)) {
final String settingsActivity;
try {
settingsActivity =
new AutofillServiceInfo(mContext, serviceInfo)
.getSettingsActivity();
} catch (SecurityException e) {
// Service does not declare the proper permission, ignore it.
Log.w(
"AutofillSettingIntentProvider",
"Error getting info for " + serviceInfo + ": " + e);
return null;
}
if (TextUtils.isEmpty(settingsActivity)) {
return null;
}
return new Intent(Intent.ACTION_MAIN)
.setComponent(
new ComponentName(serviceInfo.packageName, settingsActivity));
}
}
return null;
}
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.applications.credentials;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import com.android.settings.Utils;
import com.android.settingslib.applications.DefaultAppInfo;
public class DefaultWorkCombinedPreferenceController extends DefaultCombinedPreferenceController {
private final UserHandle mUserHandle;
public DefaultWorkCombinedPreferenceController(Context context) {
super(context);
mUserHandle = Utils.getManagedProfile(mUserManager);
}
@Override
public boolean isAvailable() {
if (mUserHandle == null) {
return false;
}
return super.isAvailable();
}
@Override
public String getPreferenceKey() {
return "default_autofill_work";
}
@Override
protected DefaultAppInfo getDefaultAppInfo() {
final String flattenComponent =
Settings.Secure.getStringForUser(
mContext.getContentResolver(),
DefaultCombinedPicker.SETTING,
mUserHandle.getIdentifier());
if (!TextUtils.isEmpty(flattenComponent)) {
DefaultAppInfo appInfo =
new DefaultAppInfo(
mContext,
mPackageManager,
mUserHandle.getIdentifier(),
ComponentName.unflattenFromString(flattenComponent));
return appInfo;
}
return null;
}
@Override
protected Intent getSettingIntent(DefaultAppInfo info) {
if (info == null) {
return null;
}
final AutofillSettingIntentProvider intentProvider =
new AutofillSettingIntentProvider(
mContext, mUserHandle.getIdentifier(), info.getKey());
return intentProvider.getIntent();
}
@Override
protected void startActivity(Intent intent) {
mContext.startActivityAsUser(intent, mUserHandle);
}
}