From 64d64fdd3605eabf2a697eee19be5377fc7e81c5 Mon Sep 17 00:00:00 2001 From: Vania Januar Date: Tue, 25 Apr 2023 14:10:52 +0100 Subject: [PATCH] Disambiguate default notes app user from stylus settings Users can now select between work or personal notes app to be the app that opens when the stylus tail button is pressed via a dialog. Bug: 278555728 Test: StylusDevicesControllerTest Change-Id: I9c63de6f11deb357b0497c7b972d4ac19b876e1f --- res/values/strings.xml | 6 +- .../stylus/StylusDevicesController.java | 80 ++++++++++++++++++- .../stylus/StylusDevicesControllerTest.java | 79 ++++++++++++++++-- 3 files changed, 154 insertions(+), 11 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 6605be2c8da..10a20d403d9 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -253,8 +253,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