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
This commit is contained in:
Vania Januar
2023-04-25 14:10:52 +01:00
parent f6b49a0204
commit 64d64fdd36
3 changed files with 154 additions and 11 deletions

View File

@@ -253,8 +253,10 @@
<!-- Title for stylus device details page [CHAR LIMIT=50] -->
<string name="stylus_device_details_title">Stylus</string>
<!-- Preference title for setting the default note taking app [CHAR LIMIT=none] -->
<string name="stylus_default_notes_app">Default notes app</string>
<!-- Preference title for setting the app that opens when stylus button is pressed [CHAR LIMIT=none] -->
<string name="stylus_default_notes_app">Tail button press</string>
<!-- Summary for the app that opens when tail button is pressed, if set to a work profile app [CHAR LIMIT=none] -->
<string name="stylus_default_notes_summary_work"><xliff:g id="app_name" example="WhatsApp">%s</xliff:g> (Work profile)</string>
<!-- Preference title for toggling whether handwriting in textfields is enabled [CHAR LIMIT=none] -->
<string name="stylus_textfield_handwriting">Write in text fields</string>
<!-- Preference title for toggling whether stylus button presses are ignored [CHAR LIMIT=none] -->

View File

@@ -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<String> roleHolders = rm.getRoleHoldersAsUser(RoleManager.ROLE_NOTES,
mContext.getUser());
UserHandle user = getDefaultNoteTaskProfile();
List<String> 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.");
}
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);
List<UserHandle> 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<UserHandle> getUserAndManagedProfiles() {
UserManager um = mContext.getSystemService(UserManager.class);
final ArrayList<UserHandle> userManagedProfiles = new ArrayList<>();
// Add the current user, then add all the associated managed profiles.
final UserHandle currentUser = Process.myUserHandle();
userManagedProfiles.add(currentUser);
final List<UserInfo> 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<UserHandle> 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<UserHandle> 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}.

View File

@@ -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<Intent> 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<UserInfo> 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<UserHandle> 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