diff --git a/res/xml/default_autofill_picker_settings.xml b/res/xml/default_autofill_picker_settings.xml new file mode 100644 index 00000000000..26dff7eca87 --- /dev/null +++ b/res/xml/default_autofill_picker_settings.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + diff --git a/res/xml/language_and_input.xml b/res/xml/language_and_input.xml index ec15f0d5268..50c6a824579 100644 --- a/res/xml/language_and_input.xml +++ b/res/xml/language_and_input.xml @@ -54,10 +54,10 @@ android:persistent="false" android:fragment="com.android.settings.inputmethod.SpellCheckersSettings" /> - diff --git a/src/com/android/settings/applications/autofill/AutofillPickerTrampolineActivity.java b/src/com/android/settings/applications/autofill/AutofillPickerTrampolineActivity.java index 1db3c82804f..9500fd5ae34 100644 --- a/src/com/android/settings/applications/autofill/AutofillPickerTrampolineActivity.java +++ b/src/com/android/settings/applications/autofill/AutofillPickerTrampolineActivity.java @@ -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); diff --git a/src/com/android/settings/applications/defaultapps/AutofillPicker.java b/src/com/android/settings/applications/defaultapps/AutofillPicker.java new file mode 100644 index 00000000000..3f392e59a08 --- /dev/null +++ b/src/com/android/settings/applications/defaultapps/AutofillPicker.java @@ -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 createPreferenceControllers(Context context) { + return buildPreferenceControllers(context); + } + + public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + @Override + public List 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 getPreferenceControllers(Context + context) { + return buildPreferenceControllers(context); + } + }; + + private static List buildPreferenceControllers(Context context) { + return Arrays.asList( + new DefaultAutofillPreferenceController(context), + new DefaultWorkAutofillPreferenceController(context)); + } +} diff --git a/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java index 6016dbc118f..78248e67c93 100644 --- a/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java +++ b/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java @@ -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(); /** diff --git a/src/com/android/settings/applications/defaultapps/DefaultAutofillPicker.java b/src/com/android/settings/applications/defaultapps/DefaultAutofillPicker.java index 9bb82d4fff0..1705dc578a6 100644 --- a/src/com/android/settings/applications/defaultapps/DefaultAutofillPicker.java +++ b/src/com/android/settings/applications/defaultapps/DefaultAutofillPicker.java @@ -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 getCandidates() { final List candidates = new ArrayList<>(); - final List resolveInfos = mPm.queryIntentServices( - AUTOFILL_PROBE, PackageManager.GET_META_DATA); + final List 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 resolveInfos = mContext.getPackageManager().queryIntentServices( - AUTOFILL_PROBE, PackageManager.GET_META_DATA); + final List resolveInfos = mContext.getPackageManager() + .queryIntentServicesAsUser( + AUTOFILL_PROBE, PackageManager.GET_META_DATA, mUserId); for (ResolveInfo resolveInfo : resolveInfos) { final ServiceInfo serviceInfo = resolveInfo.serviceInfo; diff --git a/src/com/android/settings/applications/defaultapps/DefaultAutofillPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultAutofillPreferenceController.java index bab1d167df9..d32322b6fdb 100644 --- a/src/com/android/settings/applications/defaultapps/DefaultAutofillPreferenceController.java +++ b/src/com/android/settings/applications/defaultapps/DefaultAutofillPreferenceController.java @@ -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(); } diff --git a/src/com/android/settings/applications/defaultapps/DefaultWorkAutofillPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultWorkAutofillPreferenceController.java new file mode 100644 index 00000000000..ea4eff6b657 --- /dev/null +++ b/src/com/android/settings/applications/defaultapps/DefaultWorkAutofillPreferenceController.java @@ -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); + } +} diff --git a/src/com/android/settings/language/LanguageAndInputSettings.java b/src/com/android/settings/language/LanguageAndInputSettings.java index c983c071ba8..6c7c0d3861c 100644 --- a/src/com/android/settings/language/LanguageAndInputSettings.java +++ b/src/com/android/settings/language/LanguageAndInputSettings.java @@ -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; } diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAutofillPickerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAutofillPickerTest.java index ed823c113bd..dd57315365b 100644 --- a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAutofillPickerTest.java +++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAutofillPickerTest.java @@ -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); + } }