diff --git a/res/values/strings.xml b/res/values/strings.xml index 1f3ba2a5b3c..fbb8b27ab2e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -258,8 +258,10 @@ Stylus - - Default notes app + + Tail button press + + %s (Work profile) Write in text fields diff --git a/src/com/android/settings/connecteddevice/stylus/StylusDevicesController.java b/src/com/android/settings/connecteddevice/stylus/StylusDevicesController.java index c93a1c6e659..e821966f428 100644 --- a/src/com/android/settings/connecteddevice/stylus/StylusDevicesController.java +++ b/src/com/android/settings/connecteddevice/stylus/StylusDevicesController.java @@ -16,12 +16,17 @@ package com.android.settings.connecteddevice.stylus; +import android.app.Dialog; import android.app.role.RoleManager; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; +import android.os.Process; +import android.os.UserHandle; +import android.os.UserManager; import android.provider.Settings; import android.provider.Settings.Secure; import android.text.TextUtils; @@ -38,6 +43,8 @@ import androidx.preference.PreferenceScreen; import androidx.preference.SwitchPreference; import com.android.settings.R; +import com.android.settings.dashboard.profileselector.ProfileSelectDialog; +import com.android.settings.dashboard.profileselector.UserAdapter; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.core.AbstractPreferenceController; @@ -45,6 +52,7 @@ import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnResume; +import java.util.ArrayList; import java.util.List; /** @@ -73,6 +81,9 @@ public class StylusDevicesController extends AbstractPreferenceController implem @VisibleForTesting PreferenceCategory mPreferencesContainer; + @VisibleForTesting + Dialog mDialog; + public StylusDevicesController(Context context, InputDevice inputDevice, CachedBluetoothDevice cachedBluetoothDevice, Lifecycle lifecycle) { super(context); @@ -100,8 +111,8 @@ public class StylusDevicesController extends AbstractPreferenceController implem pref.setOnPreferenceClickListener(this); pref.setEnabled(true); - List roleHolders = rm.getRoleHoldersAsUser(RoleManager.ROLE_NOTES, - mContext.getUser()); + UserHandle user = getDefaultNoteTaskProfile(); + List roleHolders = rm.getRoleHoldersAsUser(RoleManager.ROLE_NOTES, user); if (roleHolders.isEmpty()) { pref.setSummary(R.string.default_app_none); return pref; @@ -117,7 +128,13 @@ public class StylusDevicesController extends AbstractPreferenceController implem } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Notes role package not found."); } - pref.setSummary(appName); + + if (mContext.getSystemService(UserManager.class).isManagedProfile(user.getIdentifier())) { + pref.setSummary( + mContext.getString(R.string.stylus_default_notes_summary_work, appName)); + } else { + pref.setSummary(appName); + } return pref; } @@ -155,7 +172,13 @@ public class StylusDevicesController extends AbstractPreferenceController implem String packageName = pm.getPermissionControllerPackageName(); Intent intent = new Intent(Intent.ACTION_MANAGE_DEFAULT_APP).setPackage( packageName).putExtra(Intent.EXTRA_ROLE_NAME, RoleManager.ROLE_NOTES); - mContext.startActivity(intent); + + List users = getUserAndManagedProfiles(); + if (users.size() <= 1) { + mContext.startActivity(intent); + } else { + createAndShowProfileSelectDialog(intent, users); + } break; case KEY_HANDWRITING: Settings.Secure.putInt(mContext.getContentResolver(), @@ -229,6 +252,55 @@ public class StylusDevicesController extends AbstractPreferenceController implem return inputMethod != null && inputMethod.supportsStylusHandwriting(); } + private List getUserAndManagedProfiles() { + UserManager um = mContext.getSystemService(UserManager.class); + final ArrayList userManagedProfiles = new ArrayList<>(); + // Add the current user, then add all the associated managed profiles. + final UserHandle currentUser = Process.myUserHandle(); + userManagedProfiles.add(currentUser); + + final List userInfos = um.getUsers(); + for (UserInfo info : userInfos) { + if (um.isManagedProfile(info.id) + && um.getProfileParent(info.id).id == currentUser.getIdentifier()) { + userManagedProfiles.add(UserHandle.of(info.id)); + } + } + return userManagedProfiles; + } + + private UserHandle getDefaultNoteTaskProfile() { + final int userId = Secure.getInt( + mContext.getContentResolver(), + Secure.DEFAULT_NOTE_TASK_PROFILE, + UserHandle.myUserId()); + return UserHandle.of(userId); + } + + @VisibleForTesting + UserAdapter.OnClickListener createProfileDialogClickCallback( + Intent intent, List users) { + // TODO(b/281659827): improve UX flow for when activity is cancelled + return (int position) -> { + intent.putExtra(Intent.EXTRA_USER, users.get(position)); + + Secure.putInt(mContext.getContentResolver(), + Secure.DEFAULT_NOTE_TASK_PROFILE, + users.get(position).getIdentifier()); + mContext.startActivity(intent); + + mDialog.dismiss(); + }; + } + + private void createAndShowProfileSelectDialog(Intent intent, List users) { + mDialog = ProfileSelectDialog.createDialog( + mContext, + users, + createProfileDialogClickCallback(intent, users)); + mDialog.show(); + } + /** * Identifies whether a device is a stylus using the associated {@link InputDevice} or * {@link CachedBluetoothDevice}. diff --git a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java index f4fa397e3d4..7fa6236b197 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java @@ -18,6 +18,10 @@ package com.android.settings.connecteddevice.stylus; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doNothing; @@ -27,13 +31,17 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.Dialog; import android.app.role.RoleManager; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; +import android.os.Process; import android.os.UserHandle; +import android.os.UserManager; import android.provider.Settings; import android.provider.Settings.Secure; import android.view.InputDevice; @@ -48,6 +56,7 @@ import androidx.preference.SwitchPreference; import androidx.test.core.app.ApplicationProvider; import com.android.settings.R; +import com.android.settings.dashboard.profileselector.UserAdapter; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.core.lifecycle.Lifecycle; @@ -59,7 +68,9 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; +import java.util.Arrays; import java.util.Collections; +import java.util.List; @RunWith(RobolectricTestRunner.class) public class StylusDevicesControllerTest { @@ -79,6 +90,8 @@ public class StylusDevicesControllerTest { @Mock private PackageManager mPm; @Mock + private UserManager mUserManager; + @Mock private RoleManager mRm; @Mock private Lifecycle mLifecycle; @@ -87,7 +100,6 @@ public class StylusDevicesControllerTest { @Mock private BluetoothDevice mBluetoothDevice; - @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -101,6 +113,7 @@ public class StylusDevicesControllerTest { when(mContext.getSystemService(InputMethodManager.class)).thenReturn(mImm); when(mContext.getSystemService(RoleManager.class)).thenReturn(mRm); + when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); doNothing().when(mContext).startActivity(any()); when(mImm.getCurrentInputMethodInfo()).thenReturn(mInputMethodInfo); @@ -115,6 +128,8 @@ public class StylusDevicesControllerTest { when(mPm.getApplicationInfo(eq(NOTES_PACKAGE_NAME), any(PackageManager.ApplicationInfoFlags.class))).thenReturn(new ApplicationInfo()); when(mPm.getApplicationLabel(any(ApplicationInfo.class))).thenReturn(NOTES_APP_LABEL); + when(mUserManager.getUsers()).thenReturn(Arrays.asList(new UserInfo(0, "default", 0))); + when(mUserManager.isManagedProfile(anyInt())).thenReturn(false); when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); @@ -228,21 +243,35 @@ public class StylusDevicesControllerTest { when(mInputMethodInfo.supportsStylusHandwriting()).thenReturn(false); showScreen(mController); - Preference handwritingPref = mPreferenceContainer.getPreference(1); + Preference handwritingPref = mPreferenceContainer.getPreference(1); assertThat(handwritingPref.isVisible()).isFalse(); } @Test - public void defaultNotesPreference_showsNotesRoleApp() { + public void defaultNotesPreference_singleUser_showsNotesRoleApp() { showScreen(mController); - Preference defaultNotesPref = mPreferenceContainer.getPreference(0); + Preference defaultNotesPref = mPreferenceContainer.getPreference(0); assertThat(defaultNotesPref.getTitle().toString()).isEqualTo( mContext.getString(R.string.stylus_default_notes_app)); assertThat(defaultNotesPref.getSummary().toString()).isEqualTo(NOTES_APP_LABEL.toString()); } + @Test + public void defaultNotesPreference_workProfileUser_showsWorkNotesRoleApp() { + when(mUserManager.isManagedProfile(0)).thenReturn(true); + + showScreen(mController); + + Preference defaultNotesPref = mPreferenceContainer.getPreference(0); + assertThat(defaultNotesPref.getTitle().toString()).isEqualTo( + mContext.getString(R.string.stylus_default_notes_app)); + assertThat(defaultNotesPref.getSummary().toString()).isEqualTo( + mContext.getString(R.string.stylus_default_notes_summary_work, + NOTES_APP_LABEL.toString())); + } + @Test public void defaultNotesPreference_roleHolderChanges_updatesPreference() { showScreen(mController); @@ -267,7 +296,7 @@ public class StylusDevicesControllerTest { } @Test - public void defaultNotesPreferenceClick_sendsManageDefaultRoleIntent() { + public void defaultNotesPreferenceClick_singleUser_sendsManageDefaultRoleIntent() { final String permissionPackageName = "permissions.package"; when(mPm.getPermissionControllerPackageName()).thenReturn(permissionPackageName); final ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); @@ -282,6 +311,46 @@ public class StylusDevicesControllerTest { assertThat(intent.getPackage()).isEqualTo(permissionPackageName); assertThat(intent.getStringExtra(Intent.EXTRA_ROLE_NAME)).isEqualTo( RoleManager.ROLE_NOTES); + assertNull(mController.mDialog); + } + + @Test + public void defaultNotesPreferenceClick_multiUser_showsProfileSelectorDialog() { + mContext.setTheme(R.style.Theme_AppCompat); + final String permissionPackageName = "permissions.package"; + final UserHandle currentUser = Process.myUserHandle(); + List userInfos = Arrays.asList( + new UserInfo(currentUser.getIdentifier(), "current", 0), + new UserInfo(1, "profile", UserInfo.FLAG_PROFILE) + ); + when(mUserManager.getUsers()).thenReturn(userInfos); + when(mUserManager.isManagedProfile(1)).thenReturn(true); + when(mUserManager.getUserInfo(currentUser.getIdentifier())).thenReturn(userInfos.get(0)); + when(mUserManager.getUserInfo(1)).thenReturn(userInfos.get(1)); + when(mUserManager.getProfileParent(1)).thenReturn(userInfos.get(0)); + when(mPm.getPermissionControllerPackageName()).thenReturn(permissionPackageName); + + showScreen(mController); + Preference defaultNotesPref = mPreferenceContainer.getPreference(0); + mController.onPreferenceClick(defaultNotesPref); + + assertTrue(mController.mDialog.isShowing()); + } + + @Test + public void profileSelectDialogClickCallback_onClick_sendsIntent() { + Intent intent = new Intent(); + UserHandle user1 = mock(UserHandle.class); + UserHandle user2 = mock(UserHandle.class); + List users = Arrays.asList(new UserHandle[] {user1, user2}); + mController.mDialog = mock(Dialog.class); + UserAdapter.OnClickListener callback = mController + .createProfileDialogClickCallback(intent, users); + + callback.onClick(1); + + assertEquals(intent.getExtra(Intent.EXTRA_USER), user2); + verify(mContext).startActivity(intent); } @Test