Merge "Add autofill service setting in managed profile"

This commit is contained in:
Zimuzo Ezeozue
2018-08-10 17:14:13 +00:00
committed by Android (Google) Code Review
10 changed files with 390 additions and 40 deletions

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2018 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.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="default_autofill_picker"
android:title="@string/autofill_app"
settings:keywords="@string/autofill_keywords">
<com.android.settings.widget.GearPreference
android:key="default_autofill_main"
android:title="@string/autofill_app"
android:fragment="com.android.settings.applications.defaultapps.DefaultAutofillPicker"
settings:keywords="@string/autofill_keywords">
<extra android:name="for_work" android:value="false" />
</com.android.settings.widget.GearPreference>
<com.android.settings.widget.WorkOnlyCategory
android:key="work_app_defaults"
android:title="@string/default_for_work">
<com.android.settings.widget.GearPreference
android:key="default_autofill_work"
android:title="@string/autofill_app"
android:fragment="com.android.settings.applications.defaultapps.DefaultAutofillPicker"
settings:keywords="@string/autofill_keywords">
<extra android:name="for_work" android:value="true" />
</com.android.settings.widget.GearPreference>
</com.android.settings.widget.WorkOnlyCategory>
</PreferenceScreen>

View File

@@ -54,10 +54,10 @@
android:persistent="false"
android:fragment="com.android.settings.inputmethod.SpellCheckersSettings" />
<com.android.settings.widget.GearPreference
<Preference
android:key="default_autofill"
android:title="@string/autofill_app"
android:fragment="com.android.settings.applications.defaultapps.DefaultAutofillPicker"
android:fragment="com.android.settings.applications.defaultapps.AutofillPicker"
settings:keywords="@string/autofill_keywords" />
<!-- User dictionary preference title and fragment will be set programmatically. -->

View File

@@ -16,6 +16,7 @@ package com.android.settings.applications.autofill;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.UserHandle;
import android.view.autofill.AutofillManager;
import com.android.settings.applications.defaultapps.DefaultAutofillPicker;
@@ -36,7 +37,8 @@ public class AutofillPickerTrampolineActivity extends Activity {
// First check if the current user's service already belongs to the app...
final Intent intent = getIntent();
final String packageName = intent.getData().getSchemeSpecificPart();
final String currentService = DefaultAutofillPicker.getDefaultKey(this);
final String currentService = DefaultAutofillPicker.getDefaultKey(
this, UserHandle.myUserId());
if (currentService != null && currentService.startsWith(packageName)) {
// ...and succeed right away if it does.
setResult(RESULT_OK);

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2018 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.defaultapps;
import android.content.Context;
import android.provider.SearchIndexableResource;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.applications.defaultapps.DefaultAutofillPreferenceController;
import com.android.settings.applications.defaultapps.DefaultWorkAutofillPreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class AutofillPicker extends DashboardFragment {
private static final String TAG = "AutofillPicker";
@Override
public int getMetricsCategory() {
return MetricsProto.MetricsEvent.DEFAULT_AUTOFILL_PICKER;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.default_autofill_picker_settings;
}
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
return buildPreferenceControllers(context);
}
public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
boolean enabled) {
SearchIndexableResource searchIndexableResource =
new SearchIndexableResource(context);
searchIndexableResource.xmlResId = R.xml.default_autofill_picker_settings;
return Arrays.asList(searchIndexableResource);
}
@Override
public List<AbstractPreferenceController> getPreferenceControllers(Context
context) {
return buildPreferenceControllers(context);
}
};
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context) {
return Arrays.asList(
new DefaultAutofillPreferenceController(context),
new DefaultWorkAutofillPreferenceController(context));
}
}

View File

@@ -82,12 +82,16 @@ public abstract class DefaultAppPreferenceController extends AbstractPreferenceC
final Intent settingIntent = getSettingIntent(app);
if (settingIntent != null) {
((GearPreference) preference).setOnGearClickListener(
p -> mContext.startActivity(settingIntent));
p -> startActivity(settingIntent));
} else {
((GearPreference) preference).setOnGearClickListener(null);
}
}
protected void startActivity(Intent intent) {
mContext.startActivity(intent);
}
protected abstract DefaultAppInfo getDefaultAppInfo();
/**

View File

@@ -27,25 +27,23 @@ import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
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 android.util.Log;
import androidx.preference.Preference;
import com.android.internal.content.PackageMonitor;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
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;
import androidx.preference.Preference;
public class DefaultAutofillPicker extends DefaultAppPickerFragment {
private static final String TAG = "DefaultAutofillPicker";
@@ -73,8 +71,10 @@ public class DefaultAutofillPicker extends DefaultAppPickerFragment {
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();
}
@@ -159,8 +159,10 @@ public class DefaultAutofillPicker extends DefaultAppPickerFragment {
* @return The preference or {@code null} if no service can be added
*/
private Preference newAddServicePreferenceOrNull() {
final String searchUri = Settings.Secure.getString(getActivity().getContentResolver(),
Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI);
final String searchUri = Settings.Secure.getStringForUser(
getActivity().getContentResolver(),
Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI,
mUserId);
if (TextUtils.isEmpty(searchUri)) {
return null;
}
@@ -189,8 +191,8 @@ public class DefaultAutofillPicker extends DefaultAppPickerFragment {
@Override
protected List<DefaultAppInfo> getCandidates() {
final List<DefaultAppInfo> candidates = new ArrayList<>();
final List<ResolveInfo> resolveInfos = mPm.queryIntentServices(
AUTOFILL_PROBE, PackageManager.GET_META_DATA);
final List<ResolveInfo> resolveInfos = mPm.queryIntentServicesAsUser(
AUTOFILL_PROBE, PackageManager.GET_META_DATA, mUserId);
final Context context = getContext();
for (ResolveInfo info : resolveInfos) {
final String permission = info.serviceInfo.permission;
@@ -210,8 +212,9 @@ public class DefaultAutofillPicker extends DefaultAppPickerFragment {
return candidates;
}
public static String getDefaultKey(Context context) {
String setting = Settings.Secure.getString(context.getContentResolver(), SETTING);
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) {
@@ -223,7 +226,7 @@ public class DefaultAutofillPicker extends DefaultAppPickerFragment {
@Override
protected String getDefaultKey() {
return getDefaultKey(getContext());
return getDefaultKey(getContext(), mUserId);
}
@Override
@@ -239,7 +242,7 @@ public class DefaultAutofillPicker extends DefaultAppPickerFragment {
@Override
protected boolean setDefaultKey(String key) {
Settings.Secure.putString(getContext().getContentResolver(), SETTING, 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...
@@ -263,16 +266,19 @@ public class DefaultAutofillPicker extends DefaultAppPickerFragment {
private final String mSelectedKey;
private final Context mContext;
private final int mUserId;
public AutofillSettingIntentProvider(Context context, String key) {
public AutofillSettingIntentProvider(Context context, int userId, String key) {
mSelectedKey = key;
mContext = context;
mUserId = userId;
}
@Override
public Intent getIntent() {
final List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServices(
AUTOFILL_PROBE, PackageManager.GET_META_DATA);
final List<ResolveInfo> resolveInfos = mContext.getPackageManager()
.queryIntentServicesAsUser(
AUTOFILL_PROBE, PackageManager.GET_META_DATA, mUserId);
for (ResolveInfo resolveInfo : resolveInfos) {
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;

View File

@@ -44,7 +44,7 @@ public class DefaultAutofillPreferenceController extends DefaultAppPreferenceCon
@Override
public String getPreferenceKey() {
return "default_autofill";
return "default_autofill_main";
}
@Override
@@ -54,7 +54,7 @@ public class DefaultAutofillPreferenceController extends DefaultAppPreferenceCon
}
final DefaultAutofillPicker.AutofillSettingIntentProvider intentProvider =
new DefaultAutofillPicker.AutofillSettingIntentProvider(
mContext, info.getKey());
mContext, mUserId, info.getKey());
return intentProvider.getIntent();
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (C) 2018 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.defaultapps;
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 DefaultWorkAutofillPreferenceController extends DefaultAutofillPreferenceController {
private final UserHandle mUserHandle;
public DefaultWorkAutofillPreferenceController(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(),
DefaultAutofillPicker.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 DefaultAutofillPicker.AutofillSettingIntentProvider intentProvider =
new DefaultAutofillPicker.AutofillSettingIntentProvider(
mContext, mUserHandle.getIdentifier(), info.getKey());
return intentProvider.getIntent();
}
@Override
protected void startActivity(Intent intent) {
mContext.startActivityAsUser(intent, mUserHandle);
}
}

View File

@@ -27,10 +27,10 @@ import android.speech.tts.TtsEngines;
import android.text.TextUtils;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.applications.defaultapps.DefaultAutofillPreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.dashboard.SummaryLoader;
import com.android.settings.inputmethod.PhysicalKeyboardPreferenceController;
@@ -41,14 +41,10 @@ import com.android.settings.widget.PreferenceCategoryController;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@SearchIndexable
public class LanguageAndInputSettings extends DashboardFragment {
@@ -122,7 +118,6 @@ public class LanguageAndInputSettings extends DashboardFragment {
// Input Assistance
controllers.add(new SpellCheckerPreferenceController(context));
controllers.add(new DefaultAutofillPreferenceController(context));
return controllers;
}

View File

@@ -17,65 +17,196 @@
package com.android.settings.applications.defaultapps;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.RuntimeEnvironment.application;
import android.app.Activity;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.PreferenceScreen;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.SettingsShadowResources;
import com.android.settings.testutils.shadow.ShadowProcess;
import com.android.settings.testutils.shadow.ShadowSecureSettings;
import com.android.settingslib.applications.DefaultAppInfo;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.util.ReflectionHelpers;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(shadows = {
SettingsShadowResources.SettingsShadowTheme.class,
ShadowProcess.class,
ShadowSecureSettings.class
})
public class DefaultAutofillPickerTest {
private static final String TEST_APP_KEY = "foo.bar/foo.bar.Baz";
private static final String MAIN_APP_KEY = "main.foo.bar/foo.bar.Baz";
private static final String MANAGED_APP_KEY = "managed.foo.bar/foo.bar.Baz";
private static final int MANAGED_PROFILE_UID = 10;
private static final int MAIN_PROFILE_UID = 0;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Activity mActivity;
private FragmentActivity mActivity;
@Mock
private UserManager mUserManager;
@Mock
private AppOpsManager mAppOpsManager;
@Mock
private PackageManager mPackageManager;
@Mock
private PreferenceScreen mScreen;
private DefaultAutofillPicker mPicker;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
FakeFeatureFactory.setupForTest();
Resources res = application.getResources();
when(mActivity.getApplicationContext()).thenReturn(mActivity);
when(mActivity.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
when(mActivity.getTheme()).thenReturn(res.newTheme());
when(mActivity.getResources()).thenReturn(res);
mPicker = spy(new DefaultAutofillPicker());
mPicker.onAttach((Context) mActivity);
doReturn(application.getApplicationContext()).when(mPicker).getContext();
doReturn(mActivity).when(mPicker).getActivity();
doReturn(res).when(mPicker).getResources();
doReturn(mScreen).when(mPicker).getPreferenceScreen();
doNothing().when(mPicker).onCreatePreferences(any(), any());
doNothing().when(mPicker).updateCandidates();
ReflectionHelpers.setField(mPicker, "mPm", mPackageManager);
doReturn(RuntimeEnvironment.application).when(mPicker).getContext();
}
@Test
public void setAndGetDefaultAppKey_shouldUpdateDefaultAutoFill() {
assertThat(mPicker.setDefaultKey(TEST_APP_KEY)).isTrue();
assertThat(mPicker.getDefaultKey()).isEqualTo(TEST_APP_KEY);
mPicker.onAttach((Context) mActivity);
ReflectionHelpers.setField(
mPicker, "mUserId", MAIN_PROFILE_UID * UserHandle.PER_USER_RANGE);
assertThat(mPicker.setDefaultKey(MAIN_APP_KEY)).isTrue();
ReflectionHelpers.setField(
mPicker, "mUserId", MANAGED_PROFILE_UID * UserHandle.PER_USER_RANGE);
assertThat(mPicker.setDefaultKey(MANAGED_APP_KEY)).isTrue();
ReflectionHelpers.setField(
mPicker, "mUserId", MAIN_PROFILE_UID * UserHandle.PER_USER_RANGE);
assertThat(mPicker.getDefaultKey()).isEqualTo(MAIN_APP_KEY);
ReflectionHelpers.setField(
mPicker, "mUserId", MANAGED_PROFILE_UID * UserHandle.PER_USER_RANGE);
assertThat(mPicker.getDefaultKey()).isEqualTo(MANAGED_APP_KEY);
}
@Test
public void getConfirmationMessage_shouldNotBeNull() {
mPicker.onAttach((Context) mActivity);
final DefaultAppInfo info = mock(DefaultAppInfo.class);
when(info.loadLabel()).thenReturn("test_app_name");
assertThat(mPicker.getConfirmationMessage(info)).isNotNull();
}
@Test
public void mUserId_shouldDeriveUidFromManagedCaller() {
setupUserManager();
setupCaller();
ShadowProcess.setMyUid(MANAGED_PROFILE_UID * UserHandle.PER_USER_RANGE);
mPicker.onAttach((Context) mActivity);
mPicker.onCreate(null);
assertUserId(MANAGED_PROFILE_UID);
}
@Test
public void mUserId_shouldDeriveUidFromMainCaller() {
setupUserManager();
setupCaller();
ShadowProcess.setMyUid(MAIN_PROFILE_UID * UserHandle.PER_USER_RANGE);
mPicker.onAttach((Context) mActivity);
mPicker.onCreate(null);
assertUserId(MAIN_PROFILE_UID);
}
@Test
public void mUserId_shouldDeriveUidFromManagedClick() {
setupUserManager();
setupClick(/* forWork= */ true);
ShadowProcess.setMyUid(MAIN_PROFILE_UID * UserHandle.PER_USER_RANGE);
mPicker.onAttach((Context) mActivity);
mPicker.onCreate(null);
assertUserId(MANAGED_PROFILE_UID);
}
@Test
public void mUserId_shouldDeriveUidFromMainClick() {
setupUserManager();
setupClick(/* forWork= */ false);
ShadowProcess.setMyUid(MAIN_PROFILE_UID * UserHandle.PER_USER_RANGE);
mPicker.onAttach((Context) mActivity);
mPicker.onCreate(null);
assertUserId(MAIN_PROFILE_UID);
}
private void setupUserManager() {
UserHandle mainUserHandle = new UserHandle(MAIN_PROFILE_UID);
UserHandle managedUserHandle = new UserHandle(MANAGED_PROFILE_UID);
UserInfo managedUserInfo = new UserInfo(
MANAGED_PROFILE_UID, "managed", UserInfo.FLAG_MANAGED_PROFILE);
when(mUserManager.getUserProfiles())
.thenReturn(Arrays.asList(mainUserHandle, managedUserHandle));
when(mUserManager.getUserInfo(MANAGED_PROFILE_UID))
.thenReturn(managedUserInfo);
when(mUserManager.getUserHandle()).thenReturn(MAIN_PROFILE_UID);
}
private void setupCaller() {
Intent intent = new Intent();
intent.putExtra("package_name", "any package name");
when(mActivity.getIntent()).thenReturn(intent);
}
private void setupClick(boolean forWork) {
Bundle bundle = new Bundle();
bundle.putBoolean("for_work", forWork);
doReturn(bundle).when(mPicker).getArguments();
}
private void assertUserId(int userId) {
assertThat((Integer) ReflectionHelpers.getField(mPicker, "mUserId"))
.isEqualTo(userId);
}
}