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:
@@ -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] -->
|
||||
|
@@ -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}.
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user