Fix enterprise settings bug

This fixes a bug where the picker
is not using the correct user id
and also ensures that the settings
activity uses the correct user when
launched.

Test: flash & test w/ work profile
Bug 322071206

Change-Id: I846593ff9ae320d9bb774e3e79ed9ef41f101ff5
This commit is contained in:
Becca Hughes
2024-01-29 22:40:34 +00:00
parent ee85a6e0e9
commit 401ebcb53d
9 changed files with 206 additions and 85 deletions

View File

@@ -4403,9 +4403,12 @@
<activity android:name=".applications.credentials.CredentialsPickerActivity" <activity android:name=".applications.credentials.CredentialsPickerActivity"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:launchMode="singleInstance" android:launchMode="singleInstance"
android:exported="false"> android:exported="true">
<meta-data android:name="com.android.settings.FRAGMENT_CLASS" <intent-filter android:priority="1">
android:value="com.android.settings.applications.credentials.DefaultCombinedPicker" /> <action android:name="android.settings.REQUEST_SET_AUTOFILL_SERVICE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="package" />
</intent-filter>
</activity> </activity>
<activity <activity
@@ -4550,19 +4553,6 @@
android:exported="false"> android:exported="false">
</activity> </activity>
<activity android:name=".applications.autofill.AutofillPickerTrampolineActivity"
android:theme="@android:style/Theme.NoDisplay"
android:excludeFromRecents="true"
android:launchMode="singleInstance"
android:exported="true"
android:label="@string/autofill_app">
<intent-filter android:priority="1">
<action android:name="android.settings.REQUEST_SET_AUTOFILL_SERVICE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="package" />
</intent-filter>
</activity>
<activity android:name="Settings$AdvancedConnectedDeviceActivity" <activity android:name="Settings$AdvancedConnectedDeviceActivity"
android:exported="true" android:exported="true"
android:label="@string/connected_device_connections_title"> android:label="@string/connected_device_connections_title">

View File

@@ -16,6 +16,7 @@
package com.android.settings.applications.credentials; package com.android.settings.applications.credentials;
import android.content.ActivityNotFoundException;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@@ -24,7 +25,6 @@ import android.content.pm.ServiceInfo;
import android.credentials.CredentialProviderInfo; import android.credentials.CredentialProviderInfo;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager;
import android.service.autofill.AutofillServiceInfo; import android.service.autofill.AutofillServiceInfo;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.IconDrawableFactory; import android.util.IconDrawableFactory;
@@ -49,7 +49,7 @@ public final class CombinedProviderInfo {
private static final String TAG = "CombinedProviderInfo"; private static final String TAG = "CombinedProviderInfo";
private static final String SETTINGS_ACTIVITY_INTENT_ACTION = "android.intent.action.MAIN"; private static final String SETTINGS_ACTIVITY_INTENT_ACTION = "android.intent.action.MAIN";
private static final String SETTINGS_ACTIVITY_INTENT_CATEGORY = private static final String SETTINGS_ACTIVITY_INTENT_CATEGORY =
"android.intent.category.LAUNCHER"; "android.intent.category.DEFAULT";
private final List<CredentialProviderInfo> mCredentialProviderInfos; private final List<CredentialProviderInfo> mCredentialProviderInfos;
private final @Nullable AutofillServiceInfo mAutofillServiceInfo; private final @Nullable AutofillServiceInfo mAutofillServiceInfo;
@@ -327,10 +327,8 @@ public final class CombinedProviderInfo {
} }
public static @Nullable Intent createSettingsActivityIntent( public static @Nullable Intent createSettingsActivityIntent(
@NonNull Context context,
@Nullable CharSequence packageName, @Nullable CharSequence packageName,
@Nullable CharSequence settingsActivity, @Nullable CharSequence settingsActivity) {
int currentUserId) {
if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(settingsActivity)) { if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(settingsActivity)) {
return null; return null;
} }
@@ -350,19 +348,25 @@ public final class CombinedProviderInfo {
Intent intent = new Intent(SETTINGS_ACTIVITY_INTENT_ACTION); Intent intent = new Intent(SETTINGS_ACTIVITY_INTENT_ACTION);
intent.addCategory(SETTINGS_ACTIVITY_INTENT_CATEGORY); intent.addCategory(SETTINGS_ACTIVITY_INTENT_CATEGORY);
intent.setComponent(cn); intent.setComponent(cn);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
int contextUserId = context.getUser().getIdentifier();
if (currentUserId != contextUserId && UserManager.isHeadlessSystemUserMode()) {
Log.w(
TAG,
"onLeftSideClicked(): using context for current user ("
+ currentUserId
+ ") instead of user "
+ contextUserId
+ " on headless system user mode");
context = context.createContextAsUser(UserHandle.of(currentUserId), /* flags= */ 0);
}
return intent; return intent;
} }
/** Launches the settings activity intent. */
public static void launchSettingsActivityIntent(
@NonNull Context context,
@Nullable CharSequence packageName,
@Nullable CharSequence settingsActivity,
int userId) {
Intent settingsIntent = createSettingsActivityIntent(packageName, settingsActivity);
if (settingsIntent == null) {
return;
}
try {
context.startActivityAsUser(settingsIntent, UserHandle.of(userId));
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Failed to open settings activity", e);
}
}
} }

View File

@@ -20,7 +20,6 @@ import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
import android.content.ActivityNotFoundException;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
@@ -653,16 +652,8 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
@Override @Override
public void onLeftSideClicked() { public void onLeftSideClicked() {
Intent settingsIntent = CombinedProviderInfo.launchSettingsActivityIntent(
CombinedProviderInfo.createSettingsActivityIntent( mContext, packageName, settingsActivity, getUser());
mContext, packageName, settingsActivity, getUser());
if (settingsIntent != null) {
try {
mContext.startActivity(settingsIntent);
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Failed to open settings activity", e);
}
}
} }
}); });

View File

@@ -16,15 +16,53 @@
package com.android.settings.applications.credentials; package com.android.settings.applications.credentials;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.SettingsActivity; import com.android.settings.SettingsActivity;
/** Standalone activity used to launch a {@link DefaultCombinedPicker} fragment. */ /**
* Standalone activity used to launch a {@link DefaultCombinedPicker} fragment if the user is a
* normal user, a {@link DefaultCombinedPickerWork} fragment if the user is a work profile or {@link
* DefaultCombinedPickerPrivate} fragment if the user is a private profile.
*/
public class CredentialsPickerActivity extends SettingsActivity { public class CredentialsPickerActivity extends SettingsActivity {
private static final String TAG = "CredentialsPickerActivity";
/** Injects the fragment name into the intent so the correct fragment is opened. */
@VisibleForTesting
public static void injectFragmentIntoIntent(Context context, Intent intent) {
final int userId = UserHandle.myUserId();
final UserManager userManager = UserManager.get(context);
if (DefaultCombinedPickerWork.isUserHandledByFragment(userManager, userId)) {
Slog.d(TAG, "Creating picker fragment using work profile");
intent.putExtra(EXTRA_SHOW_FRAGMENT, DefaultCombinedPickerWork.class.getName());
} else if (DefaultCombinedPickerPrivate.isUserHandledByFragment(userManager)) {
Slog.d(TAG, "Creating picker fragment using private profile");
intent.putExtra(EXTRA_SHOW_FRAGMENT, DefaultCombinedPickerPrivate.class.getName());
} else {
Slog.d(TAG, "Creating picker fragment using normal profile");
intent.putExtra(EXTRA_SHOW_FRAGMENT, DefaultCombinedPicker.class.getName());
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
injectFragmentIntoIntent(this, getIntent());
super.onCreate(savedInstanceState);
}
@Override @Override
protected boolean isValidFragment(String fragmentName) { protected boolean isValidFragment(String fragmentName) {
return super.isValidFragment(fragmentName) return super.isValidFragment(fragmentName)
|| DefaultCombinedPicker.class.getName().equals(fragmentName); || DefaultCombinedPicker.class.getName().equals(fragmentName)
|| DefaultCombinedPickerWork.class.getName().equals(fragmentName)
|| DefaultCombinedPickerPrivate.class.getName().equals(fragmentName);
} }
} }

View File

@@ -17,14 +17,29 @@
package com.android.settings.applications.credentials; package com.android.settings.applications.credentials;
import android.os.UserManager; import android.os.UserManager;
import android.util.Slog;
import com.android.settings.Utils; import com.android.settings.Utils;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment.ProfileType; import com.android.settings.dashboard.profileselector.ProfileSelectFragment.ProfileType;
public class DefaultCombinedPickerPrivate extends DefaultCombinedPicker { public class DefaultCombinedPickerPrivate extends DefaultCombinedPicker {
private static final String TAG = "DefaultCombinedPickerPrivate";
@Override @Override
protected int getUser() { protected int getUser() {
UserManager userManager = getContext().getSystemService(UserManager.class); UserManager userManager = getContext().getSystemService(UserManager.class);
return Utils.getCurrentUserIdOfType(userManager, ProfileType.PRIVATE); return Utils.getCurrentUserIdOfType(userManager, ProfileType.PRIVATE);
} }
/** Returns whether the user is handled by this fragment. */
public static boolean isUserHandledByFragment(UserManager userManager) {
try {
// If there is no private profile then this will throw an exception.
Utils.getCurrentUserIdOfType(userManager, ProfileType.PRIVATE);
return true;
} catch (IllegalStateException e) {
Slog.e(TAG, "Failed to get private profile user id", e);
return false;
}
}
} }

View File

@@ -19,13 +19,16 @@ package com.android.settings.applications.credentials;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager; import android.os.UserManager;
import com.android.settings.Utils;
public class DefaultCombinedPickerWork extends DefaultCombinedPicker { public class DefaultCombinedPickerWork extends DefaultCombinedPicker {
private static final String TAG = "DefaultCombinedPickerWork";
@Override @Override
protected int getUser() { protected int getUser() {
UserHandle workProfile = Utils.getManagedProfile(UserManager.get(getContext())); return UserHandle.myUserId();
return workProfile.getIdentifier(); }
/** Returns whether the user is handled by this fragment. */
public static boolean isUserHandledByFragment(UserManager userManager, int userId) {
return userManager.isManagedProfile(userId);
} }
} }

View File

@@ -16,7 +16,6 @@
package com.android.settings.applications.credentials; package com.android.settings.applications.credentials;
import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.credentials.CredentialManager; import android.credentials.CredentialManager;
@@ -26,11 +25,11 @@ import android.os.UserHandle;
import android.provider.Settings; import android.provider.Settings;
import android.service.autofill.AutofillService; import android.service.autofill.AutofillService;
import android.service.autofill.AutofillServiceInfo; import android.service.autofill.AutofillServiceInfo;
import android.text.TextUtils;
import android.view.autofill.AutofillManager; import android.view.autofill.AutofillManager;
import android.util.Slog;
import androidx.annotation.Nullable;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.Preference; import androidx.preference.Preference;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
@@ -99,10 +98,10 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon
topProvider.getAppName(mContext), topProvider.getAppName(mContext),
topProvider.getSettingsSubtitle(), topProvider.getSettingsSubtitle(),
topProvider.getAppIcon(mContext, getUser()), topProvider.getAppIcon(mContext, getUser()),
createSettingsActivityIntent( topProvider.getPackageName(),
topProvider.getPackageName(), topProvider.getSettingsActivity())); topProvider.getSettingsActivity());
} else { } else {
updatePreferenceForProvider(preference, null, null, null, null); updatePreferenceForProvider(preference, null, null, null, null, null);
} }
} }
@@ -112,7 +111,8 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon
@Nullable CharSequence appName, @Nullable CharSequence appName,
@Nullable String appSubtitle, @Nullable String appSubtitle,
@Nullable Drawable appIcon, @Nullable Drawable appIcon,
@Nullable Intent settingsActivityIntent) { @Nullable CharSequence packageName,
@Nullable CharSequence settingsActivity) {
if (appName == null) { if (appName == null) {
preference.setTitle(R.string.app_list_preference_none); preference.setTitle(R.string.app_list_preference_none);
} else { } else {
@@ -133,13 +133,8 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon
primaryPref.setDelegate( primaryPref.setDelegate(
new PrimaryProviderPreference.Delegate() { new PrimaryProviderPreference.Delegate() {
public void onOpenButtonClicked() { public void onOpenButtonClicked() {
if (settingsActivityIntent != null) { CombinedProviderInfo.launchSettingsActivityIntent(
try { mContext, packageName, settingsActivity, getUser());
startActivity(settingsActivityIntent);
} catch (ActivityNotFoundException e) {
Slog.e(TAG, "Failed to open settings activity", e);
}
}
} }
public void onChangeButtonClicked() { public void onChangeButtonClicked() {
@@ -148,7 +143,7 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon
}); });
// Hide the open button if there is no defined settings activity. // Hide the open button if there is no defined settings activity.
primaryPref.setOpenButtonVisible(settingsActivityIntent != null); primaryPref.setOpenButtonVisible(!TextUtils.isEmpty(settingsActivity));
primaryPref.setButtonsVisible(appName != null); primaryPref.setButtonsVisible(appName != null);
} }
} }
@@ -198,13 +193,8 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon
/** Creates an intent to open the credential picker. */ /** Creates an intent to open the credential picker. */
private Intent createIntentToOpenPicker() { private Intent createIntentToOpenPicker() {
return new Intent(mContext, CredentialsPickerActivity.class); final Context context =
} mContext.createContextAsUser(UserHandle.of(getUser()), /* flags= */ 0);
return new Intent(context, CredentialsPickerActivity.class);
/** Creates an intent to open the settings activity of the primary provider (if available). */
public @Nullable Intent createSettingsActivityIntent(
@Nullable String packageName, @Nullable String settingsActivity) {
return CombinedProviderInfo.createSettingsActivityIntent(
mContext, packageName, settingsActivity, getUser());
} }
} }

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2024 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 static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.os.UserHandle;
import android.os.UserManager;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.Lists;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
public class CredentialsPickerActivityTest {
@Mock private UserManager mUserManager;
private Context mMockContext;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mMockContext = spy(ApplicationProvider.getApplicationContext());
when(mMockContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
}
@Test
public void testInjectFragmentIntoIntent_normalProfile() {
Intent intent = new Intent();
CredentialsPickerActivity.injectFragmentIntoIntent(mMockContext, intent);
assertThat(intent.getStringExtra(CredentialsPickerActivity.EXTRA_SHOW_FRAGMENT))
.isEqualTo(DefaultCombinedPicker.class.getName());
}
@Test
public void testInjectFragmentIntoIntent_workProfile() {
Intent intent = new Intent();
// Simulate managed / work profile.
when(mUserManager.isManagedProfile(anyInt())).thenReturn(true);
assertThat(DefaultCombinedPickerWork.isUserHandledByFragment(mUserManager, 10)).isTrue();
CredentialsPickerActivity.injectFragmentIntoIntent(mMockContext, intent);
assertThat(intent.getStringExtra(CredentialsPickerActivity.EXTRA_SHOW_FRAGMENT))
.isEqualTo(DefaultCombinedPickerWork.class.getName());
}
@Test
public void testInjectFragmentIntoIntent_privateProfile() {
Intent intent = new Intent();
// Simulate private profile.
UserHandle privateUser = new UserHandle(100);
when(mUserManager.getUserInfo(100))
.thenReturn(new UserInfo(100, "", "", 0, UserManager.USER_TYPE_PROFILE_PRIVATE));
when(mUserManager.getUserProfiles()).thenReturn(Lists.newArrayList(privateUser));
assertThat(DefaultCombinedPickerPrivate.isUserHandledByFragment(mUserManager)).isTrue();
CredentialsPickerActivity.injectFragmentIntoIntent(mMockContext, intent);
assertThat(intent.getStringExtra(CredentialsPickerActivity.EXTRA_SHOW_FRAGMENT))
.isEqualTo(DefaultCombinedPickerPrivate.class.getName());
}
}

View File

@@ -87,16 +87,14 @@ public class DefaultCombinedPreferenceControllerTest {
@Test @Test
public void ensureSettingsActivityIntentCreatedSuccessfully() { public void ensureSettingsActivityIntentCreatedSuccessfully() {
DefaultCombinedPreferenceController dcpc =
new DefaultCombinedPreferenceController(mContext);
// Ensure that the settings activity is only created if we haved the right combination // Ensure that the settings activity is only created if we haved the right combination
// of package and class name. // of package and class name.
assertThat(dcpc.createSettingsActivityIntent(null, null)).isNull(); assertThat(CombinedProviderInfo.createSettingsActivityIntent(null, null)).isNull();
assertThat(dcpc.createSettingsActivityIntent("", null)).isNull(); assertThat(CombinedProviderInfo.createSettingsActivityIntent("", null)).isNull();
assertThat(dcpc.createSettingsActivityIntent("", "")).isNull(); assertThat(CombinedProviderInfo.createSettingsActivityIntent("", "")).isNull();
assertThat(dcpc.createSettingsActivityIntent("com.test", "")).isNull(); assertThat(CombinedProviderInfo.createSettingsActivityIntent("com.test", "")).isNull();
assertThat(dcpc.createSettingsActivityIntent("com.test", "ClassName")).isNotNull(); assertThat(CombinedProviderInfo.createSettingsActivityIntent("com.test", "ClassName"))
.isNotNull();
} }
@Test @Test
@@ -112,13 +110,13 @@ public class DefaultCombinedPreferenceControllerTest {
// Update the preference to use the provider and make sure the view // Update the preference to use the provider and make sure the view
// was updated. // was updated.
dcpc.updatePreferenceForProvider(ppp, "App Name", "Subtitle", appIcon, null); dcpc.updatePreferenceForProvider(ppp, "App Name", "Subtitle", appIcon, null, null);
assertThat(ppp.getTitle().toString()).isEqualTo("App Name"); assertThat(ppp.getTitle().toString()).isEqualTo("App Name");
assertThat(ppp.getSummary().toString()).isEqualTo("Subtitle"); assertThat(ppp.getSummary().toString()).isEqualTo("Subtitle");
assertThat(ppp.getIcon()).isEqualTo(appIcon); assertThat(ppp.getIcon()).isEqualTo(appIcon);
// Set the preference back to none and make sure the view was updated. // Set the preference back to none and make sure the view was updated.
dcpc.updatePreferenceForProvider(ppp, null, null, null, null); dcpc.updatePreferenceForProvider(ppp, null, null, null, null, null);
assertThat(ppp.getTitle().toString()).isEqualTo("None"); assertThat(ppp.getTitle().toString()).isEqualTo("None");
assertThat(ppp.getSummary()).isNull(); assertThat(ppp.getSummary()).isNull();
assertThat(ppp.getIcon()).isNull(); assertThat(ppp.getIcon()).isNull();