summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/credentials/CredentialManager.java9
-rw-r--r--core/java/android/credentials/CredentialProviderInfo.java31
-rw-r--r--core/java/android/credentials/ICredentialManager.aidl2
-rw-r--r--core/java/android/service/credentials/CredentialProviderInfoFactory.java72
-rw-r--r--core/tests/coretests/src/android/credentials/CredentialManagerTest.java437
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt18
-rw-r--r--services/core/java/com/android/server/input/InputFeatureFlagProvider.java67
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java13
-rw-r--r--services/core/java/com/android/server/input/KeyboardBacklightController.java117
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerService.java51
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerUi.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt464
12 files changed, 799 insertions, 487 deletions
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 9140d02e223d..c2a0062b43e8 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -355,7 +355,11 @@ public final class CredentialManager {
* Sets a list of all user configurable credential providers registered on the system. This API
* is intended for settings apps.
*
- * @param providers the list of enabled providers
+ * @param primaryProviders the primary providers that user selected for saving credentials. In
+ * the most case, there should be only one primary provider, However,
+ * if there are more than one CredentialProviderService in the same APK,
+ * they should be passed in altogether.
+ * @param providers the list of enabled providers.
* @param userId the user ID to configure credential manager for
* @param executor the callback will take place on this {@link Executor}
* @param callback the callback invoked when the request succeeds or fails
@@ -363,6 +367,7 @@ public final class CredentialManager {
*/
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
public void setEnabledProviders(
+ @NonNull List<String> primaryProviders,
@NonNull List<String> providers,
int userId,
@CallbackExecutor @NonNull Executor executor,
@@ -370,9 +375,11 @@ public final class CredentialManager {
requireNonNull(executor, "executor must not be null");
requireNonNull(callback, "callback must not be null");
requireNonNull(providers, "providers must not be null");
+ requireNonNull(primaryProviders, "primaryProviders must not be null");
try {
mService.setEnabledProviders(
+ primaryProviders,
providers, userId, new SetEnabledProvidersTransport(executor, callback));
} catch (RemoteException e) {
e.rethrowFromSystemServer();
diff --git a/core/java/android/credentials/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java
index c224f01055e1..d66b8f00a2e9 100644
--- a/core/java/android/credentials/CredentialProviderInfo.java
+++ b/core/java/android/credentials/CredentialProviderInfo.java
@@ -46,6 +46,7 @@ public final class CredentialProviderInfo implements Parcelable {
@Nullable private CharSequence mSettingsSubtitle = null;
private final boolean mIsSystemProvider;
private final boolean mIsEnabled;
+ private final boolean mIsPrimary;
/**
* Constructs an information instance of the credential provider.
@@ -58,6 +59,7 @@ public final class CredentialProviderInfo implements Parcelable {
mIsSystemProvider = builder.mIsSystemProvider;
mSettingsSubtitle = builder.mSettingsSubtitle;
mIsEnabled = builder.mIsEnabled;
+ mIsPrimary = builder.mIsPrimary;
mOverrideLabel = builder.mOverrideLabel;
}
@@ -108,6 +110,15 @@ public final class CredentialProviderInfo implements Parcelable {
return mIsEnabled;
}
+ /**
+ * Returns whether the provider is set as primary by the user.
+ *
+ * @hide
+ */
+ public boolean isPrimary() {
+ return mIsPrimary;
+ }
+
/** Returns the settings subtitle. */
@Nullable
public CharSequence getSettingsSubtitle() {
@@ -125,6 +136,7 @@ public final class CredentialProviderInfo implements Parcelable {
dest.writeTypedObject(mServiceInfo, flags);
dest.writeBoolean(mIsSystemProvider);
dest.writeBoolean(mIsEnabled);
+ dest.writeBoolean(mIsPrimary);
TextUtils.writeToParcel(mOverrideLabel, dest, flags);
TextUtils.writeToParcel(mSettingsSubtitle, dest, flags);
@@ -149,6 +161,9 @@ public final class CredentialProviderInfo implements Parcelable {
+ "isEnabled="
+ mIsEnabled
+ ", "
+ + "isPrimary="
+ + mIsPrimary
+ + ", "
+ "overrideLabel="
+ mOverrideLabel
+ ", "
@@ -164,6 +179,7 @@ public final class CredentialProviderInfo implements Parcelable {
mServiceInfo = in.readTypedObject(ServiceInfo.CREATOR);
mIsSystemProvider = in.readBoolean();
mIsEnabled = in.readBoolean();
+ mIsPrimary = in.readBoolean();
mOverrideLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
mSettingsSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
@@ -193,6 +209,7 @@ public final class CredentialProviderInfo implements Parcelable {
private boolean mIsSystemProvider = false;
@Nullable private CharSequence mSettingsSubtitle = null;
private boolean mIsEnabled = false;
+ private boolean mIsPrimary = false;
@Nullable private CharSequence mOverrideLabel = null;
/**
@@ -248,6 +265,20 @@ public final class CredentialProviderInfo implements Parcelable {
return this;
}
+ /**
+ * Sets whether it is set as primary by the user.
+ *
+ * <p>Primary provider will be used for saving credentials by default. In most cases, there
+ * should only one primary provider exist. However, if there are multiple credential
+ * providers exist in the same package, all of them will be marked as primary.
+ *
+ * @hide
+ */
+ public @NonNull Builder setPrimary(boolean isPrimary) {
+ mIsPrimary = isPrimary;
+ return this;
+ }
+
/** Builds a new {@link CredentialProviderInfo} instance. */
public @NonNull CredentialProviderInfo build() {
return new CredentialProviderInfo(this);
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index b779c56035d3..dec729f4a19f 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -47,7 +47,7 @@ interface ICredentialManager {
@nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
- void setEnabledProviders(in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback);
+ void setEnabledProviders(in List<String> primaryProviders, in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback);
void registerCredentialDescription(in RegisterCredentialDescriptionRequest request, String callingPackage);
diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index 751c675b28f4..b196b064a670 100644
--- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -75,13 +75,12 @@ public final class CredentialProviderInfoFactory {
/**
* Constructs an information instance of the credential provider.
*
- * @param context the context object
+ * @param context the context object
* @param serviceComponent the serviceComponent of the provider service
- * @param userId the android userId for which the current process is running
+ * @param userId the android userId for which the current process is running
* @param isSystemProvider whether this provider is a system provider
* @throws PackageManager.NameNotFoundException If provider service is not found
- * @throws SecurityException If provider does not require the relevant
- * permission
+ * @throws SecurityException If provider does not require the relevant permission
*/
public static CredentialProviderInfo create(
@NonNull Context context,
@@ -94,21 +93,20 @@ public final class CredentialProviderInfoFactory {
getServiceInfoOrThrow(serviceComponent, userId),
isSystemProvider,
/* disableSystemAppVerificationForTests= */ false,
- /* isEnabled= */ false);
+ /* isEnabled= */ false,
+ /* isPrimary= */ false);
}
/**
* Constructs an information instance of the credential provider.
*
- * @param context the context object
- * @param serviceInfo the service info for the provider app. This must
- * be retrieved from the
- * {@code PackageManager}
- * @param isSystemProvider whether the provider app is a system provider
+ * @param context the context object
+ * @param serviceInfo the service info for the provider app. This must be retrieved from the
+ * {@code PackageManager}
+ * @param isSystemProvider whether the provider app is a system provider
* @param disableSystemAppVerificationForTests whether to disable system app permission
- * verification so that tests can install system
- * providers
- * @param isEnabled whether the user enabled this provider
+ * verification so that tests can install system providers
+ * @param isEnabled whether the user enabled this provider
* @throws SecurityException If provider does not require the relevant permission
*/
public static CredentialProviderInfo create(
@@ -116,7 +114,8 @@ public final class CredentialProviderInfoFactory {
@NonNull ServiceInfo serviceInfo,
boolean isSystemProvider,
boolean disableSystemAppVerificationForTests,
- boolean isEnabled)
+ boolean isEnabled,
+ boolean isPrimary)
throws SecurityException {
verifyProviderPermission(serviceInfo);
if (isSystemProvider) {
@@ -131,6 +130,7 @@ public final class CredentialProviderInfoFactory {
return populateMetadata(context, serviceInfo)
.setSystemProvider(isSystemProvider)
.setEnabled(isEnabled)
+ .setPrimary(isPrimary)
.build();
}
@@ -168,7 +168,9 @@ public final class CredentialProviderInfoFactory {
Slog.w(TAG, "Context is null in isSystemProviderWithValidPermission");
return false;
}
- return PermissionUtils.hasPermission(context, serviceInfo.packageName,
+ return PermissionUtils.hasPermission(
+ context,
+ serviceInfo.packageName,
Manifest.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE);
}
@@ -181,8 +183,11 @@ public final class CredentialProviderInfoFactory {
if (disableSystemAppVerificationForTests) {
Bundle metadata = serviceInfo.metaData;
if (metadata == null) {
- Slog.w(TAG, "metadata is null while reading "
- + "TEST_SYSTEM_PROVIDER_META_DATA_KEY: " + serviceInfo);
+ Slog.w(
+ TAG,
+ "metadata is null while reading "
+ + "TEST_SYSTEM_PROVIDER_META_DATA_KEY: "
+ + serviceInfo);
return false;
}
return metadata.getBoolean(
@@ -215,8 +220,10 @@ public final class CredentialProviderInfoFactory {
// 3. Stop if we are missing data.
if (resources == null) {
- Slog.w(TAG, "Resources are null for the serviceInfo being processed: "
- + serviceInfo.getComponentName());
+ Slog.w(
+ TAG,
+ "Resources are null for the serviceInfo being processed: "
+ + serviceInfo.getComponentName());
return builder;
}
@@ -408,7 +415,7 @@ public final class CredentialProviderInfoFactory {
si,
/* isSystemProvider= */ true,
disableSystemAppVerificationForTests,
- enabledServices.contains(si.getComponentName()));
+ enabledServices.contains(si.getComponentName()), false);
if (cpi.isSystemProvider()) {
providerInfos.add(cpi);
} else {
@@ -446,7 +453,8 @@ public final class CredentialProviderInfoFactory {
@NonNull Context context,
int userId,
int providerFilter,
- Set<ComponentName> enabledServices) {
+ Set<ComponentName> enabledServices,
+ Set<String> primaryServices) {
requireNonNull(context, "context must not be null");
// Get the device policy.
@@ -459,7 +467,11 @@ public final class CredentialProviderInfoFactory {
context, pp, disableSystemAppVerificationForTests, providerFilter);
generator.addUserProviders(
getUserProviders(
- context, userId, disableSystemAppVerificationForTests, enabledServices));
+ context,
+ userId,
+ disableSystemAppVerificationForTests,
+ enabledServices,
+ primaryServices));
generator.addSystemProviders(
getAvailableSystemServices(
context, userId, disableSystemAppVerificationForTests, enabledServices));
@@ -475,7 +487,8 @@ public final class CredentialProviderInfoFactory {
@NonNull Context context,
int userId,
int providerFilter,
- Set<ComponentName> enabledServices) {
+ Set<ComponentName> enabledServices,
+ Set<String> primaryServices) {
requireNonNull(context, "context must not be null");
// Get the device policy.
@@ -488,7 +501,11 @@ public final class CredentialProviderInfoFactory {
context, pp, disableSystemAppVerificationForTests, providerFilter);
generator.addUserProviders(
getUserProviders(
- context, userId, disableSystemAppVerificationForTests, enabledServices));
+ context,
+ userId,
+ disableSystemAppVerificationForTests,
+ enabledServices,
+ primaryServices));
generator.addSystemProviders(
getAvailableSystemServices(
context, userId, disableSystemAppVerificationForTests, enabledServices));
@@ -581,7 +598,8 @@ public final class CredentialProviderInfoFactory {
@NonNull Context context,
@UserIdInt int userId,
boolean disableSystemAppVerificationForTests,
- Set<ComponentName> enabledServices) {
+ Set<ComponentName> enabledServices,
+ Set<String> primaryServices) {
final List<CredentialProviderInfo> services = new ArrayList<>();
final List<ResolveInfo> resolveInfos =
context.getPackageManager()
@@ -603,7 +621,9 @@ public final class CredentialProviderInfoFactory {
serviceInfo,
/* isSystemProvider= */ false,
disableSystemAppVerificationForTests,
- enabledServices.contains(serviceInfo.getComponentName()));
+ enabledServices.contains(serviceInfo.getComponentName()),
+ primaryServices.contains(
+ serviceInfo.getComponentName().flattenToString()));
if (!cpi.isSystemProvider()) {
services.add(cpi);
}
diff --git a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
index b0d5240c61ba..dc4c2527cc03 100644
--- a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
+++ b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
@@ -56,22 +56,20 @@ import java.util.concurrent.Executor;
@RunWith(MockitoJUnitRunner.class)
public class CredentialManagerTest {
- @Mock
- private ICredentialManager mMockCredentialManagerService;
+ @Mock private ICredentialManager mMockCredentialManagerService;
- @Mock
- private Activity mMockActivity;
+ @Mock private Activity mMockActivity;
private static final int TEST_USER_ID = 1;
private static final CredentialProviderInfo TEST_CREDENTIAL_PROVIDER_INFO =
- new CredentialProviderInfo.Builder(new ServiceInfo())
- .setSystemProvider(true)
- .setOverrideLabel("test")
- .addCapabilities(Arrays.asList("passkey"))
- .setEnabled(true)
- .build();
+ new CredentialProviderInfo.Builder(new ServiceInfo())
+ .setSystemProvider(true)
+ .setOverrideLabel("test")
+ .addCapabilities(Arrays.asList("passkey"))
+ .setEnabled(true)
+ .build();
private static final List<CredentialProviderInfo> TEST_CREDENTIAL_PROVIDER_INFO_LIST =
- Arrays.asList(TEST_CREDENTIAL_PROVIDER_INFO);
+ Arrays.asList(TEST_CREDENTIAL_PROVIDER_INFO);
private GetCredentialRequest mGetRequest;
private CreateCredentialRequest mCreateRequest;
@@ -112,27 +110,43 @@ public class CredentialManagerTest {
@Before
public void setup() {
- mGetRequest = new GetCredentialRequest.Builder(Bundle.EMPTY).addCredentialOption(
- new CredentialOption(Credential.TYPE_PASSWORD_CREDENTIAL, Bundle.EMPTY,
- Bundle.EMPTY, false)).build();
- mCreateRequest = new CreateCredentialRequest.Builder(
- Credential.TYPE_PASSWORD_CREDENTIAL,
- Bundle.EMPTY, Bundle.EMPTY)
- .setIsSystemProviderRequired(false)
- .setAlwaysSendAppInfoToProvider(false)
- .build();
+ mGetRequest =
+ new GetCredentialRequest.Builder(Bundle.EMPTY)
+ .addCredentialOption(
+ new CredentialOption(
+ Credential.TYPE_PASSWORD_CREDENTIAL,
+ Bundle.EMPTY,
+ Bundle.EMPTY,
+ false))
+ .build();
+ mCreateRequest =
+ new CreateCredentialRequest.Builder(
+ Credential.TYPE_PASSWORD_CREDENTIAL, Bundle.EMPTY, Bundle.EMPTY)
+ .setIsSystemProviderRequired(false)
+ .setAlwaysSendAppInfoToProvider(false)
+ .build();
mClearRequest = new ClearCredentialStateRequest(Bundle.EMPTY);
- final Slice slice = new Slice.Builder(Uri.parse("foo://bar"), null).addText("some text",
- null, List.of(Slice.HINT_TITLE)).build();
- mRegisterRequest = new RegisterCredentialDescriptionRequest(
- new CredentialDescription(Credential.TYPE_PASSWORD_CREDENTIAL,
- new HashSet<>(List.of("{ \"foo\": \"bar\" }")),
- List.of(new CredentialEntry(Credential.TYPE_PASSWORD_CREDENTIAL, slice))));
- mUnregisterRequest = new UnregisterCredentialDescriptionRequest(
- new CredentialDescription(Credential.TYPE_PASSWORD_CREDENTIAL,
- new HashSet<>(List.of("{ \"foo\": \"bar\" }")),
- List.of(new CredentialEntry(Credential.TYPE_PASSWORD_CREDENTIAL, slice))));
+ final Slice slice =
+ new Slice.Builder(Uri.parse("foo://bar"), null)
+ .addText("some text", null, List.of(Slice.HINT_TITLE))
+ .build();
+ mRegisterRequest =
+ new RegisterCredentialDescriptionRequest(
+ new CredentialDescription(
+ Credential.TYPE_PASSWORD_CREDENTIAL,
+ new HashSet<>(List.of("{ \"foo\": \"bar\" }")),
+ List.of(
+ new CredentialEntry(
+ Credential.TYPE_PASSWORD_CREDENTIAL, slice))));
+ mUnregisterRequest =
+ new UnregisterCredentialDescriptionRequest(
+ new CredentialDescription(
+ Credential.TYPE_PASSWORD_CREDENTIAL,
+ new HashSet<>(List.of("{ \"foo\": \"bar\" }")),
+ List.of(
+ new CredentialEntry(
+ Credential.TYPE_PASSWORD_CREDENTIAL, slice))));
final Context context = InstrumentationRegistry.getInstrumentation().getContext();
mCredentialManager = new CredentialManager(context, mMockCredentialManagerService);
@@ -143,56 +157,63 @@ public class CredentialManagerTest {
@Test
public void testGetCredential_nullRequest() {
GetCredentialRequest nullRequest = null;
- assertThrows(NullPointerException.class,
- () -> mCredentialManager.getCredential(mMockActivity, nullRequest, null, mExecutor,
- result -> {
- }));
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ mCredentialManager.getCredential(
+ mMockActivity, nullRequest, null, mExecutor, result -> {}));
}
@Test
public void testGetCredential_nullActivity() {
- assertThrows(NullPointerException.class,
- () -> mCredentialManager.getCredential(null, mGetRequest, null, mExecutor,
- result -> {
- }));
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ mCredentialManager.getCredential(
+ null, mGetRequest, null, mExecutor, result -> {}));
}
@Test
public void testGetCredential_nullExecutor() {
- assertThrows(NullPointerException.class,
- () -> mCredentialManager.getCredential(mMockActivity, mGetRequest, null, null,
- result -> {
- }));
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ mCredentialManager.getCredential(
+ mMockActivity, mGetRequest, null, null, result -> {}));
}
@Test
public void testGetCredential_nullCallback() {
- assertThrows(NullPointerException.class,
- () -> mCredentialManager.getCredential(mMockActivity, mGetRequest, null, null,
- null));
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ mCredentialManager.getCredential(
+ mMockActivity, mGetRequest, null, null, null));
}
@Test
public void testGetCredential_noCredential() throws RemoteException {
- ArgumentCaptor<IGetCredentialCallback> callbackCaptor = ArgumentCaptor.forClass(
- IGetCredentialCallback.class);
- ArgumentCaptor<GetCredentialException> errorCaptor = ArgumentCaptor.forClass(
- GetCredentialException.class);
+ ArgumentCaptor<IGetCredentialCallback> callbackCaptor =
+ ArgumentCaptor.forClass(IGetCredentialCallback.class);
+ ArgumentCaptor<GetCredentialException> errorCaptor =
+ ArgumentCaptor.forClass(GetCredentialException.class);
- OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback = mock(
- OutcomeReceiver.class);
+ OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback =
+ mock(OutcomeReceiver.class);
- when(mMockCredentialManagerService.executeGetCredential(any(), callbackCaptor.capture(),
- any())).thenReturn(mock(ICancellationSignal.class));
+ when(mMockCredentialManagerService.executeGetCredential(
+ any(), callbackCaptor.capture(), any()))
+ .thenReturn(mock(ICancellationSignal.class));
mCredentialManager.getCredential(mMockActivity, mGetRequest, null, mExecutor, callback);
verify(mMockCredentialManagerService).executeGetCredential(any(), any(), eq(mPackageName));
- callbackCaptor.getValue().onError(GetCredentialException.TYPE_NO_CREDENTIAL,
- "no credential found");
+ callbackCaptor
+ .getValue()
+ .onError(GetCredentialException.TYPE_NO_CREDENTIAL, "no credential found");
verify(callback).onError(errorCaptor.capture());
- assertThat(errorCaptor.getValue().getType()).isEqualTo(
- GetCredentialException.TYPE_NO_CREDENTIAL);
+ assertThat(errorCaptor.getValue().getType())
+ .isEqualTo(GetCredentialException.TYPE_NO_CREDENTIAL);
}
@Test
@@ -200,9 +221,8 @@ public class CredentialManagerTest {
final CancellationSignal cancellation = new CancellationSignal();
cancellation.cancel();
- mCredentialManager.getCredential(mMockActivity, mGetRequest, cancellation, mExecutor,
- result -> {
- });
+ mCredentialManager.getCredential(
+ mMockActivity, mGetRequest, cancellation, mExecutor, result -> {});
verify(mMockCredentialManagerService, never()).executeGetCredential(any(), any(), any());
}
@@ -212,14 +232,14 @@ public class CredentialManagerTest {
final ICancellationSignal serviceSignal = mock(ICancellationSignal.class);
final CancellationSignal cancellation = new CancellationSignal();
- OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback = mock(
- OutcomeReceiver.class);
+ OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback =
+ mock(OutcomeReceiver.class);
- when(mMockCredentialManagerService.executeGetCredential(any(), any(), any())).thenReturn(
- serviceSignal);
+ when(mMockCredentialManagerService.executeGetCredential(any(), any(), any()))
+ .thenReturn(serviceSignal);
- mCredentialManager.getCredential(mMockActivity, mGetRequest, cancellation, mExecutor,
- callback);
+ mCredentialManager.getCredential(
+ mMockActivity, mGetRequest, cancellation, mExecutor, callback);
verify(mMockCredentialManagerService).executeGetCredential(any(), any(), eq(mPackageName));
@@ -231,16 +251,17 @@ public class CredentialManagerTest {
public void testGetCredential_success() throws RemoteException {
final Credential cred = new Credential(Credential.TYPE_PASSWORD_CREDENTIAL, Bundle.EMPTY);
- ArgumentCaptor<IGetCredentialCallback> callbackCaptor = ArgumentCaptor.forClass(
- IGetCredentialCallback.class);
- ArgumentCaptor<GetCredentialResponse> responseCaptor = ArgumentCaptor.forClass(
- GetCredentialResponse.class);
+ ArgumentCaptor<IGetCredentialCallback> callbackCaptor =
+ ArgumentCaptor.forClass(IGetCredentialCallback.class);
+ ArgumentCaptor<GetCredentialResponse> responseCaptor =
+ ArgumentCaptor.forClass(GetCredentialResponse.class);
- OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback = mock(
- OutcomeReceiver.class);
+ OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback =
+ mock(OutcomeReceiver.class);
- when(mMockCredentialManagerService.executeGetCredential(any(), callbackCaptor.capture(),
- any())).thenReturn(mock(ICancellationSignal.class));
+ when(mMockCredentialManagerService.executeGetCredential(
+ any(), callbackCaptor.capture(), any()))
+ .thenReturn(mock(ICancellationSignal.class));
mCredentialManager.getCredential(mMockActivity, mGetRequest, null, mExecutor, callback);
verify(mMockCredentialManagerService).executeGetCredential(any(), any(), eq(mPackageName));
@@ -252,33 +273,38 @@ public class CredentialManagerTest {
@Test
public void testCreateCredential_nullRequest() {
- assertThrows(NullPointerException.class,
- () -> mCredentialManager.createCredential(mMockActivity, null, null, mExecutor,
- result -> {
- }));
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ mCredentialManager.createCredential(
+ mMockActivity, null, null, mExecutor, result -> {}));
}
@Test
public void testCreateCredential_nullActivity() {
- assertThrows(NullPointerException.class,
- () -> mCredentialManager.createCredential(null, mCreateRequest, null, mExecutor,
- result -> {
- }));
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ mCredentialManager.createCredential(
+ null, mCreateRequest, null, mExecutor, result -> {}));
}
@Test
public void testCreateCredential_nullExecutor() {
- assertThrows(NullPointerException.class,
- () -> mCredentialManager.createCredential(mMockActivity, mCreateRequest, null, null,
- result -> {
- }));
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ mCredentialManager.createCredential(
+ mMockActivity, mCreateRequest, null, null, result -> {}));
}
@Test
public void testCreateCredential_nullCallback() {
- assertThrows(NullPointerException.class,
- () -> mCredentialManager.createCredential(mMockActivity, mCreateRequest, null,
- mExecutor, null));
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ mCredentialManager.createCredential(
+ mMockActivity, mCreateRequest, null, mExecutor, null));
}
@Test
@@ -286,9 +312,8 @@ public class CredentialManagerTest {
final CancellationSignal cancellation = new CancellationSignal();
cancellation.cancel();
- mCredentialManager.createCredential(mMockActivity, mCreateRequest, cancellation, mExecutor,
- result -> {
- });
+ mCredentialManager.createCredential(
+ mMockActivity, mCreateRequest, cancellation, mExecutor, result -> {});
verify(mMockCredentialManagerService, never()).executeCreateCredential(any(), any(), any());
}
@@ -298,17 +323,17 @@ public class CredentialManagerTest {
final ICancellationSignal serviceSignal = mock(ICancellationSignal.class);
final CancellationSignal cancellation = new CancellationSignal();
- OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback = mock(
- OutcomeReceiver.class);
+ OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback =
+ mock(OutcomeReceiver.class);
- when(mMockCredentialManagerService.executeCreateCredential(any(), any(), any())).thenReturn(
- serviceSignal);
+ when(mMockCredentialManagerService.executeCreateCredential(any(), any(), any()))
+ .thenReturn(serviceSignal);
- mCredentialManager.createCredential(mMockActivity, mCreateRequest, cancellation, mExecutor,
- callback);
+ mCredentialManager.createCredential(
+ mMockActivity, mCreateRequest, cancellation, mExecutor, callback);
- verify(mMockCredentialManagerService).executeCreateCredential(any(), any(),
- eq(mPackageName));
+ verify(mMockCredentialManagerService)
+ .executeCreateCredential(any(), any(), eq(mPackageName));
cancellation.cancel();
verify(serviceSignal).cancel();
@@ -316,26 +341,27 @@ public class CredentialManagerTest {
@Test
public void testCreateCredential_failed() throws RemoteException {
- ArgumentCaptor<ICreateCredentialCallback> callbackCaptor = ArgumentCaptor.forClass(
- ICreateCredentialCallback.class);
- ArgumentCaptor<CreateCredentialException> errorCaptor = ArgumentCaptor.forClass(
- CreateCredentialException.class);
-
- OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback = mock(
- OutcomeReceiver.class);
-
- when(mMockCredentialManagerService.executeCreateCredential(any(), callbackCaptor.capture(),
- any())).thenReturn(mock(ICancellationSignal.class));
- mCredentialManager.createCredential(mMockActivity, mCreateRequest, null, mExecutor,
- callback);
- verify(mMockCredentialManagerService).executeCreateCredential(any(), any(),
- eq(mPackageName));
+ ArgumentCaptor<ICreateCredentialCallback> callbackCaptor =
+ ArgumentCaptor.forClass(ICreateCredentialCallback.class);
+ ArgumentCaptor<CreateCredentialException> errorCaptor =
+ ArgumentCaptor.forClass(CreateCredentialException.class);
+
+ OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback =
+ mock(OutcomeReceiver.class);
+
+ when(mMockCredentialManagerService.executeCreateCredential(
+ any(), callbackCaptor.capture(), any()))
+ .thenReturn(mock(ICancellationSignal.class));
+ mCredentialManager.createCredential(
+ mMockActivity, mCreateRequest, null, mExecutor, callback);
+ verify(mMockCredentialManagerService)
+ .executeCreateCredential(any(), any(), eq(mPackageName));
callbackCaptor.getValue().onError(CreateCredentialException.TYPE_UNKNOWN, "unknown error");
verify(callback).onError(errorCaptor.capture());
- assertThat(errorCaptor.getValue().getType()).isEqualTo(
- CreateCredentialException.TYPE_UNKNOWN);
+ assertThat(errorCaptor.getValue().getType())
+ .isEqualTo(CreateCredentialException.TYPE_UNKNOWN);
}
@Test
@@ -343,20 +369,21 @@ public class CredentialManagerTest {
final Bundle responseData = new Bundle();
responseData.putString("foo", "bar");
- ArgumentCaptor<ICreateCredentialCallback> callbackCaptor = ArgumentCaptor.forClass(
- ICreateCredentialCallback.class);
- ArgumentCaptor<CreateCredentialResponse> responseCaptor = ArgumentCaptor.forClass(
- CreateCredentialResponse.class);
+ ArgumentCaptor<ICreateCredentialCallback> callbackCaptor =
+ ArgumentCaptor.forClass(ICreateCredentialCallback.class);
+ ArgumentCaptor<CreateCredentialResponse> responseCaptor =
+ ArgumentCaptor.forClass(CreateCredentialResponse.class);
- OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback = mock(
- OutcomeReceiver.class);
+ OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback =
+ mock(OutcomeReceiver.class);
- when(mMockCredentialManagerService.executeCreateCredential(any(), callbackCaptor.capture(),
- any())).thenReturn(mock(ICancellationSignal.class));
- mCredentialManager.createCredential(mMockActivity, mCreateRequest, null, mExecutor,
- callback);
- verify(mMockCredentialManagerService).executeCreateCredential(any(), any(),
- eq(mPackageName));
+ when(mMockCredentialManagerService.executeCreateCredential(
+ any(), callbackCaptor.capture(), any()))
+ .thenReturn(mock(ICancellationSignal.class));
+ mCredentialManager.createCredential(
+ mMockActivity, mCreateRequest, null, mExecutor, callback);
+ verify(mMockCredentialManagerService)
+ .executeCreateCredential(any(), any(), eq(mPackageName));
callbackCaptor.getValue().onResponse(new CreateCredentialResponse(responseData));
verify(callback).onResult(responseCaptor.capture());
@@ -366,23 +393,27 @@ public class CredentialManagerTest {
@Test
public void testClearCredentialState_nullRequest() {
- assertThrows(NullPointerException.class,
- () -> mCredentialManager.clearCredentialState(null, null, mExecutor, result -> {
- }));
+ assertThrows(
+ NullPointerException.class,
+ () -> mCredentialManager.clearCredentialState(null, null, mExecutor, result -> {}));
}
@Test
public void testClearCredentialState_nullExecutor() {
- assertThrows(NullPointerException.class,
- () -> mCredentialManager.clearCredentialState(mClearRequest, null, null, result -> {
- }));
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ mCredentialManager.clearCredentialState(
+ mClearRequest, null, null, result -> {}));
}
@Test
public void testClearCredentialState_nullCallback() {
- assertThrows(NullPointerException.class,
- () -> mCredentialManager.clearCredentialState(mClearRequest, null, mExecutor,
- null));
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ mCredentialManager.clearCredentialState(
+ mClearRequest, null, mExecutor, null));
}
@Test
@@ -390,8 +421,8 @@ public class CredentialManagerTest {
final CancellationSignal cancellation = new CancellationSignal();
cancellation.cancel();
- mCredentialManager.clearCredentialState(mClearRequest, cancellation, mExecutor, result -> {
- });
+ mCredentialManager.clearCredentialState(
+ mClearRequest, cancellation, mExecutor, result -> {});
verify(mMockCredentialManagerService, never()).clearCredentialState(any(), any(), any());
}
@@ -403,8 +434,8 @@ public class CredentialManagerTest {
OutcomeReceiver<Void, ClearCredentialStateException> callback = mock(OutcomeReceiver.class);
- when(mMockCredentialManagerService.clearCredentialState(any(), any(), any())).thenReturn(
- serviceSignal);
+ when(mMockCredentialManagerService.clearCredentialState(any(), any(), any()))
+ .thenReturn(serviceSignal);
mCredentialManager.clearCredentialState(mClearRequest, cancellation, mExecutor, callback);
@@ -416,35 +447,38 @@ public class CredentialManagerTest {
@Test
public void testClearCredential_failed() throws RemoteException {
- ArgumentCaptor<IClearCredentialStateCallback> callbackCaptor = ArgumentCaptor.forClass(
- IClearCredentialStateCallback.class);
- ArgumentCaptor<ClearCredentialStateException> errorCaptor = ArgumentCaptor.forClass(
- ClearCredentialStateException.class);
+ ArgumentCaptor<IClearCredentialStateCallback> callbackCaptor =
+ ArgumentCaptor.forClass(IClearCredentialStateCallback.class);
+ ArgumentCaptor<ClearCredentialStateException> errorCaptor =
+ ArgumentCaptor.forClass(ClearCredentialStateException.class);
OutcomeReceiver<Void, ClearCredentialStateException> callback = mock(OutcomeReceiver.class);
- when(mMockCredentialManagerService.clearCredentialState(any(), callbackCaptor.capture(),
- any())).thenReturn(mock(ICancellationSignal.class));
+ when(mMockCredentialManagerService.clearCredentialState(
+ any(), callbackCaptor.capture(), any()))
+ .thenReturn(mock(ICancellationSignal.class));
mCredentialManager.clearCredentialState(mClearRequest, null, mExecutor, callback);
verify(mMockCredentialManagerService).clearCredentialState(any(), any(), eq(mPackageName));
- callbackCaptor.getValue().onError(ClearCredentialStateException.TYPE_UNKNOWN,
- "unknown error");
+ callbackCaptor
+ .getValue()
+ .onError(ClearCredentialStateException.TYPE_UNKNOWN, "unknown error");
verify(callback).onError(errorCaptor.capture());
- assertThat(errorCaptor.getValue().getType()).isEqualTo(
- ClearCredentialStateException.TYPE_UNKNOWN);
+ assertThat(errorCaptor.getValue().getType())
+ .isEqualTo(ClearCredentialStateException.TYPE_UNKNOWN);
}
@Test
public void testClearCredential_success() throws RemoteException {
- ArgumentCaptor<IClearCredentialStateCallback> callbackCaptor = ArgumentCaptor.forClass(
- IClearCredentialStateCallback.class);
+ ArgumentCaptor<IClearCredentialStateCallback> callbackCaptor =
+ ArgumentCaptor.forClass(IClearCredentialStateCallback.class);
OutcomeReceiver<Void, ClearCredentialStateException> callback = mock(OutcomeReceiver.class);
- when(mMockCredentialManagerService.clearCredentialState(any(), callbackCaptor.capture(),
- any())).thenReturn(mock(ICancellationSignal.class));
+ when(mMockCredentialManagerService.clearCredentialState(
+ any(), callbackCaptor.capture(), any()))
+ .thenReturn(mock(ICancellationSignal.class));
mCredentialManager.clearCredentialState(mClearRequest, null, mExecutor, callback);
verify(mMockCredentialManagerService).clearCredentialState(any(), any(), eq(mPackageName));
@@ -464,27 +498,32 @@ public class CredentialManagerTest {
@Test
public void testGetCredentialProviderServices_systemProviders() throws RemoteException {
- verifyGetCredentialProviderServices(CredentialManager.PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY);
+ verifyGetCredentialProviderServices(
+ CredentialManager.PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY);
}
@Test
public void testGetCredentialProviderServicesForTesting_allProviders() throws RemoteException {
- verifyGetCredentialProviderServicesForTesting(CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS);
+ verifyGetCredentialProviderServicesForTesting(
+ CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS);
}
@Test
public void testGetCredentialProviderServicesForTesting_userProviders() throws RemoteException {
- verifyGetCredentialProviderServicesForTesting(CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY);
+ verifyGetCredentialProviderServicesForTesting(
+ CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY);
}
@Test
- public void testGetCredentialProviderServicesForTesting_systemProviders() throws RemoteException {
- verifyGetCredentialProviderServicesForTesting(CredentialManager.PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY);
+ public void testGetCredentialProviderServicesForTesting_systemProviders()
+ throws RemoteException {
+ verifyGetCredentialProviderServicesForTesting(
+ CredentialManager.PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY);
}
private void verifyGetCredentialProviderServices(int testFilter) throws RemoteException {
- when(mMockCredentialManagerService.getCredentialProviderServices(
- TEST_USER_ID, testFilter)).thenReturn(TEST_CREDENTIAL_PROVIDER_INFO_LIST);
+ when(mMockCredentialManagerService.getCredentialProviderServices(TEST_USER_ID, testFilter))
+ .thenReturn(TEST_CREDENTIAL_PROVIDER_INFO_LIST);
List<CredentialProviderInfo> output =
mCredentialManager.getCredentialProviderServices(TEST_USER_ID, testFilter);
@@ -492,9 +531,10 @@ public class CredentialManagerTest {
assertThat(output).containsExactlyElementsIn(TEST_CREDENTIAL_PROVIDER_INFO_LIST);
}
- private void verifyGetCredentialProviderServicesForTesting(int testFilter) throws RemoteException {
- when(mMockCredentialManagerService.getCredentialProviderServicesForTesting(
- testFilter)).thenReturn(TEST_CREDENTIAL_PROVIDER_INFO_LIST);
+ private void verifyGetCredentialProviderServicesForTesting(int testFilter)
+ throws RemoteException {
+ when(mMockCredentialManagerService.getCredentialProviderServicesForTesting(testFilter))
+ .thenReturn(TEST_CREDENTIAL_PROVIDER_INFO_LIST);
List<CredentialProviderInfo> output =
mCredentialManager.getCredentialProviderServicesForTesting(testFilter);
@@ -504,41 +544,45 @@ public class CredentialManagerTest {
@Test
public void testSetEnabledProviders_nullProviders() {
- assertThrows(NullPointerException.class,
- () -> mCredentialManager.setEnabledProviders(null, 0, mExecutor, response -> {
- }));
-
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ mCredentialManager.setEnabledProviders(
+ null, null, 0, mExecutor, response -> {}));
}
@Test
public void testSetEnabledProviders_nullExecutor() {
- assertThrows(NullPointerException.class,
- () -> mCredentialManager.setEnabledProviders(List.of("foo"), 0, null, response -> {
- }));
-
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ mCredentialManager.setEnabledProviders(
+ List.of("foo"), List.of("foo"), 0, null, response -> {}));
}
@Test
public void testSetEnabledProviders_nullCallback() {
- assertThrows(NullPointerException.class,
- () -> mCredentialManager.setEnabledProviders(List.of("foo"), 0, mExecutor, null));
-
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ mCredentialManager.setEnabledProviders(
+ List.of("foo"), List.of("foo"), 0, mExecutor, null));
}
@Test
public void testSetEnabledProviders_failed() throws RemoteException {
OutcomeReceiver<Void, SetEnabledProvidersException> callback = mock(OutcomeReceiver.class);
- ArgumentCaptor<ISetEnabledProvidersCallback> callbackCaptor = ArgumentCaptor.forClass(
- ISetEnabledProvidersCallback.class);
- ArgumentCaptor<SetEnabledProvidersException> errorCaptor = ArgumentCaptor.forClass(
- SetEnabledProvidersException.class);
+ ArgumentCaptor<ISetEnabledProvidersCallback> callbackCaptor =
+ ArgumentCaptor.forClass(ISetEnabledProvidersCallback.class);
+ ArgumentCaptor<SetEnabledProvidersException> errorCaptor =
+ ArgumentCaptor.forClass(SetEnabledProvidersException.class);
final List<String> providers = List.of("foo", "bar");
final int userId = 0;
- mCredentialManager.setEnabledProviders(providers, userId, mExecutor, callback);
- verify(mMockCredentialManagerService).setEnabledProviders(eq(providers), eq(0),
- callbackCaptor.capture());
+ mCredentialManager.setEnabledProviders(providers, providers, userId, mExecutor, callback);
+ verify(mMockCredentialManagerService)
+ .setEnabledProviders(eq(providers), eq(providers), eq(0), callbackCaptor.capture());
final String errorType = "unknown";
final String errorMessage = "Unknown error";
@@ -553,15 +597,18 @@ public class CredentialManagerTest {
public void testSetEnabledProviders_success() throws RemoteException {
OutcomeReceiver<Void, SetEnabledProvidersException> callback = mock(OutcomeReceiver.class);
- ArgumentCaptor<ISetEnabledProvidersCallback> callbackCaptor = ArgumentCaptor.forClass(
- ISetEnabledProvidersCallback.class);
+ ArgumentCaptor<ISetEnabledProvidersCallback> callbackCaptor =
+ ArgumentCaptor.forClass(ISetEnabledProvidersCallback.class);
final List<String> providers = List.of("foo", "bar");
+ final List<String> primaryProviders = List.of("foo");
final int userId = 0;
- mCredentialManager.setEnabledProviders(providers, userId, mExecutor, callback);
+ mCredentialManager.setEnabledProviders(
+ primaryProviders, providers, userId, mExecutor, callback);
- verify(mMockCredentialManagerService).setEnabledProviders(eq(providers), eq(0),
- callbackCaptor.capture());
+ verify(mMockCredentialManagerService)
+ .setEnabledProviders(
+ eq(primaryProviders), eq(providers), eq(0), callbackCaptor.capture());
callbackCaptor.getValue().onResponse();
verify(callback).onResult(any());
@@ -569,27 +616,29 @@ public class CredentialManagerTest {
@Test
public void testRegisterCredentialDescription_nullRequest() {
- assertThrows(NullPointerException.class,
+ assertThrows(
+ NullPointerException.class,
() -> mCredentialManager.registerCredentialDescription(null));
}
@Test
public void testRegisterCredentialDescription_success() throws RemoteException {
mCredentialManager.registerCredentialDescription(mRegisterRequest);
- verify(mMockCredentialManagerService).registerCredentialDescription(same(mRegisterRequest),
- eq(mPackageName));
+ verify(mMockCredentialManagerService)
+ .registerCredentialDescription(same(mRegisterRequest), eq(mPackageName));
}
@Test
public void testUnregisterCredentialDescription_nullRequest() {
- assertThrows(NullPointerException.class,
+ assertThrows(
+ NullPointerException.class,
() -> mCredentialManager.unregisterCredentialDescription(null));
}
@Test
public void testUnregisterCredentialDescription_success() throws RemoteException {
mCredentialManager.unregisterCredentialDescription(mUnregisterRequest);
- verify(mMockCredentialManagerService).unregisterCredentialDescription(
- same(mUnregisterRequest), eq(mPackageName));
+ verify(mMockCredentialManagerService)
+ .unregisterCredentialDescription(same(mUnregisterRequest), eq(mPackageName));
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index ccbd46d79437..31af7aa78c69 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -217,13 +217,27 @@ fun PrimarySelectionCard(
text = stringResource(
if (hasSingleEntry) {
val singleEntryType = sortedUserNameToCredentialEntryList.firstOrNull()
- ?.sortedCredentialEntryList?.first()?.credentialType
+ ?.sortedCredentialEntryList?.firstOrNull()?.credentialType
if (singleEntryType == CredentialType.PASSKEY)
R.string.get_dialog_title_use_passkey_for
else if (singleEntryType == CredentialType.PASSWORD)
R.string.get_dialog_title_use_password_for
+ else if (authenticationEntryList.isNotEmpty())
+ R.string.get_dialog_title_unlock_options_for
else R.string.get_dialog_title_use_sign_in_for
- } else R.string.get_dialog_title_choose_sign_in_for,
+ } else {
+ if (authenticationEntryList.isNotEmpty() ||
+ sortedUserNameToCredentialEntryList.any { perNameEntryList ->
+ perNameEntryList.sortedCredentialEntryList.any { entry ->
+ entry.credentialType != CredentialType.PASSWORD &&
+ entry.credentialType != CredentialType.PASSKEY
+ }
+ }
+ )
+ R.string.get_dialog_title_choose_sign_in_for
+ else
+ R.string.get_dialog_title_choose_saved_sign_in_for
+ },
requestDisplayInfo.appName
),
)
diff --git a/services/core/java/com/android/server/input/InputFeatureFlagProvider.java b/services/core/java/com/android/server/input/InputFeatureFlagProvider.java
new file mode 100644
index 000000000000..d7efc05f8052
--- /dev/null
+++ b/services/core/java/com/android/server/input/InputFeatureFlagProvider.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2023 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.server.input;
+
+import android.os.SystemProperties;
+
+import java.util.Optional;
+
+/**
+ * A component of {@link InputManagerService} responsible for managing the input sysprop flags
+ *
+ * @hide
+ */
+@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+public final class InputFeatureFlagProvider {
+
+ // To disable Keyboard backlight control via Framework, run:
+ // 'adb shell setprop persist.input.keyboard_backlight_control.enabled false' (requires restart)
+ private static final boolean KEYBOARD_BACKLIGHT_CONTROL_ENABLED = SystemProperties.getBoolean(
+ "persist.input.keyboard.backlight_control.enabled", true);
+
+ // To disable Framework controlled keyboard backlight animation run:
+ // adb shell setprop persist.input.keyboard_backlight_animation.enabled false (requires restart)
+ private static final boolean KEYBOARD_BACKLIGHT_ANIMATION_ENABLED = SystemProperties.getBoolean(
+ "persist.input.keyboard.keyboard_backlight_animation.enabled", false);
+
+ private static Optional<Boolean> sKeyboardBacklightControlOverride = Optional.empty();
+ private static Optional<Boolean> sKeyboardBacklightAnimationOverride = Optional.empty();
+
+ public static boolean isKeyboardBacklightControlEnabled() {
+ return sKeyboardBacklightControlOverride.orElse(KEYBOARD_BACKLIGHT_CONTROL_ENABLED);
+ }
+
+ public static boolean isKeyboardBacklightAnimationEnabled() {
+ return sKeyboardBacklightAnimationOverride.orElse(KEYBOARD_BACKLIGHT_ANIMATION_ENABLED);
+ }
+
+ public static void setKeyboardBacklightControlEnabled(boolean enabled) {
+ sKeyboardBacklightControlOverride = Optional.of(enabled);
+ }
+
+ public static void setKeyboardBacklightAnimationEnabled(boolean enabled) {
+ sKeyboardBacklightAnimationOverride = Optional.of(enabled);
+ }
+
+ /**
+ * Clears all input feature flag overrides.
+ */
+ public static void clearOverrides() {
+ sKeyboardBacklightControlOverride = Optional.empty();
+ sKeyboardBacklightAnimationOverride = Optional.empty();
+ }
+}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 5f45f912a87a..23beb44f497c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -72,7 +72,6 @@ import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.VibrationEffect;
import android.os.vibrator.StepSegment;
@@ -157,11 +156,6 @@ public class InputManagerService extends IInputManager.Stub
private static final AdditionalDisplayInputProperties
DEFAULT_ADDITIONAL_DISPLAY_INPUT_PROPERTIES = new AdditionalDisplayInputProperties();
- // To disable Keyboard backlight control via Framework, run:
- // 'adb shell setprop persist.input.keyboard_backlight_control.enabled false' (requires restart)
- private static final boolean KEYBOARD_BACKLIGHT_CONTROL_ENABLED = SystemProperties.getBoolean(
- "persist.input.keyboard.backlight_control.enabled", true);
-
private final NativeInputManagerService mNative;
private final Context mContext;
@@ -431,10 +425,9 @@ public class InputManagerService extends IInputManager.Stub
mKeyboardLayoutManager = new KeyboardLayoutManager(mContext, mNative, mDataStore,
injector.getLooper());
mBatteryController = new BatteryController(mContext, mNative, injector.getLooper());
- mKeyboardBacklightController =
- KEYBOARD_BACKLIGHT_CONTROL_ENABLED ? new KeyboardBacklightController(mContext,
- mNative, mDataStore, injector.getLooper())
- : new KeyboardBacklightControllerInterface() {};
+ mKeyboardBacklightController = InputFeatureFlagProvider.isKeyboardBacklightControlEnabled()
+ ? new KeyboardBacklightController(mContext, mNative, mDataStore,
+ injector.getLooper()) : new KeyboardBacklightControllerInterface() {};
mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper());
mUseDevInputEventForAudioJack =
diff --git a/services/core/java/com/android/server/input/KeyboardBacklightController.java b/services/core/java/com/android/server/input/KeyboardBacklightController.java
index 48c346a2fe22..3ea19cb4063a 100644
--- a/services/core/java/com/android/server/input/KeyboardBacklightController.java
+++ b/services/core/java/com/android/server/input/KeyboardBacklightController.java
@@ -16,6 +16,7 @@
package com.android.server.input;
+import android.animation.ValueAnimator;
import android.annotation.BinderThread;
import android.content.Context;
import android.graphics.Color;
@@ -70,6 +71,8 @@ final class KeyboardBacklightController implements
private static final int MSG_INTERACTIVE_STATE_CHANGED = 6;
private static final int MAX_BRIGHTNESS = 255;
private static final int NUM_BRIGHTNESS_CHANGE_STEPS = 10;
+ private static final long TRANSITION_ANIMATION_DURATION_MILLIS = Duration.ofSeconds(
+ 1).toMillis();
private static final String UEVENT_KEYBOARD_BACKLIGHT_TAG = "kbd_backlight";
@@ -85,6 +88,7 @@ final class KeyboardBacklightController implements
@GuardedBy("mDataStore")
private final PersistentDataStore mDataStore;
private final Handler mHandler;
+ private final AnimatorFactory mAnimatorFactory;
// Always access on handler thread or need to lock this for synchronization.
private final SparseArray<KeyboardBacklightState> mKeyboardBacklights = new SparseArray<>(1);
// Maintains state if all backlights should be on or turned off
@@ -109,10 +113,17 @@ final class KeyboardBacklightController implements
KeyboardBacklightController(Context context, NativeInputManagerService nativeService,
PersistentDataStore dataStore, Looper looper) {
+ this(context, nativeService, dataStore, looper, ValueAnimator::ofInt);
+ }
+
+ @VisibleForTesting
+ KeyboardBacklightController(Context context, NativeInputManagerService nativeService,
+ PersistentDataStore dataStore, Looper looper, AnimatorFactory animatorFactory) {
mContext = context;
mNative = nativeService;
mDataStore = dataStore;
mHandler = new Handler(looper, this::handleMessage);
+ mAnimatorFactory = animatorFactory;
}
@Override
@@ -177,8 +188,7 @@ final class KeyboardBacklightController implements
} else {
newBrightnessLevel = Math.max(currBrightnessLevel - 1, 0);
}
- updateBacklightState(deviceId, keyboardBacklight, newBrightnessLevel,
- true /* isTriggeredByKeyPress */);
+ updateBacklightState(deviceId, newBrightnessLevel, true /* isTriggeredByKeyPress */);
synchronized (mDataStore) {
try {
@@ -203,8 +213,7 @@ final class KeyboardBacklightController implements
if (index < 0) {
index = Math.min(NUM_BRIGHTNESS_CHANGE_STEPS, -(index + 1));
}
- updateBacklightState(inputDevice.getId(), keyboardBacklight, index,
- false /* isTriggeredByKeyPress */);
+ updateBacklightState(inputDevice.getId(), index, false /* isTriggeredByKeyPress */);
if (DEBUG) {
Slog.d(TAG, "Restoring brightness level " + brightness.getAsInt());
}
@@ -217,14 +226,10 @@ final class KeyboardBacklightController implements
if (!mIsInteractive) {
return;
}
- if (!mIsBacklightOn) {
- mIsBacklightOn = true;
- for (int i = 0; i < mKeyboardBacklights.size(); i++) {
- int deviceId = mKeyboardBacklights.keyAt(i);
- KeyboardBacklightState state = mKeyboardBacklights.valueAt(i);
- updateBacklightState(deviceId, state.mLight, state.mBrightnessLevel,
- false /* isTriggeredByKeyPress */);
- }
+ mIsBacklightOn = true;
+ for (int i = 0; i < mKeyboardBacklights.size(); i++) {
+ KeyboardBacklightState state = mKeyboardBacklights.valueAt(i);
+ state.onBacklightStateChanged();
}
mHandler.removeMessages(MSG_NOTIFY_USER_INACTIVITY);
mHandler.sendEmptyMessageAtTime(MSG_NOTIFY_USER_INACTIVITY,
@@ -232,14 +237,10 @@ final class KeyboardBacklightController implements
}
private void handleUserInactivity() {
- if (mIsBacklightOn) {
- mIsBacklightOn = false;
- for (int i = 0; i < mKeyboardBacklights.size(); i++) {
- int deviceId = mKeyboardBacklights.keyAt(i);
- KeyboardBacklightState state = mKeyboardBacklights.valueAt(i);
- updateBacklightState(deviceId, state.mLight, state.mBrightnessLevel,
- false /* isTriggeredByKeyPress */);
- }
+ mIsBacklightOn = false;
+ for (int i = 0; i < mKeyboardBacklights.size(); i++) {
+ KeyboardBacklightState state = mKeyboardBacklights.valueAt(i);
+ state.onBacklightStateChanged();
}
}
@@ -310,7 +311,7 @@ final class KeyboardBacklightController implements
return;
}
// The keyboard backlight was added or changed.
- mKeyboardBacklights.put(deviceId, new KeyboardBacklightState(keyboardBacklight));
+ mKeyboardBacklights.put(deviceId, new KeyboardBacklightState(deviceId, keyboardBacklight));
restoreBacklightBrightness(inputDevice, keyboardBacklight);
}
@@ -372,21 +373,14 @@ final class KeyboardBacklightController implements
}
}
- private void updateBacklightState(int deviceId, Light light, int brightnessLevel,
+ private void updateBacklightState(int deviceId, int brightnessLevel,
boolean isTriggeredByKeyPress) {
KeyboardBacklightState state = mKeyboardBacklights.get(deviceId);
if (state == null) {
return;
}
- mNative.setLightColor(deviceId, light.getId(),
- mIsBacklightOn ? Color.argb(BRIGHTNESS_VALUE_FOR_LEVEL[brightnessLevel], 0, 0, 0)
- : 0);
- if (DEBUG) {
- Slog.d(TAG, "Changing state from " + state.mBrightnessLevel + " to " + brightnessLevel
- + "(isBacklightOn = " + mIsBacklightOn + ")");
- }
- state.mBrightnessLevel = brightnessLevel;
+ state.setBrightnessLevel(brightnessLevel);
synchronized (mKeyboardBacklightListenerRecords) {
for (int i = 0; i < mKeyboardBacklightListenerRecords.size(); i++) {
@@ -397,6 +391,10 @@ final class KeyboardBacklightController implements
deviceId, callbackState, isTriggeredByKeyPress);
}
}
+
+ if (DEBUG) {
+ Slog.d(TAG, "Changing state from " + state.mBrightnessLevel + " to " + brightnessLevel);
+ }
}
private void onKeyboardBacklightListenerDied(int pid) {
@@ -436,10 +434,7 @@ final class KeyboardBacklightController implements
@Override
public void dump(PrintWriter pw) {
IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
- ipw.println(
- TAG + ": " + mKeyboardBacklights.size() + " keyboard backlights, isBacklightOn = "
- + mIsBacklightOn);
-
+ ipw.println(TAG + ": " + mKeyboardBacklights.size() + " keyboard backlights");
ipw.increaseIndent();
for (int i = 0; i < mKeyboardBacklights.size(); i++) {
KeyboardBacklightState state = mKeyboardBacklights.valueAt(i);
@@ -448,6 +443,10 @@ final class KeyboardBacklightController implements
ipw.decreaseIndent();
}
+ private static boolean isAnimationEnabled() {
+ return InputFeatureFlagProvider.isKeyboardBacklightAnimationEnabled();
+ }
+
// A record of a registered Keyboard backlight listener from one process.
private class KeyboardBacklightListenerRecord implements IBinder.DeathRecipient {
public final int mPid;
@@ -478,14 +477,55 @@ final class KeyboardBacklightController implements
}
}
- private static class KeyboardBacklightState {
+ private class KeyboardBacklightState {
+ private final int mDeviceId;
private final Light mLight;
private int mBrightnessLevel;
+ private ValueAnimator mAnimator;
- KeyboardBacklightState(Light light) {
+ KeyboardBacklightState(int deviceId, Light light) {
+ mDeviceId = deviceId;
mLight = light;
}
+ private void onBacklightStateChanged() {
+ setBacklightValue(mIsBacklightOn ? BRIGHTNESS_VALUE_FOR_LEVEL[mBrightnessLevel] : 0);
+ }
+ private void setBrightnessLevel(int brightnessLevel) {
+ if (mIsBacklightOn) {
+ setBacklightValue(BRIGHTNESS_VALUE_FOR_LEVEL[brightnessLevel]);
+ }
+ mBrightnessLevel = brightnessLevel;
+ }
+
+ private void cancelAnimation() {
+ if (mAnimator != null && mAnimator.isRunning()) {
+ mAnimator.cancel();
+ }
+ }
+
+ private void setBacklightValue(int toValue) {
+ int fromValue = Color.alpha(mNative.getLightColor(mDeviceId, mLight.getId()));
+ if (fromValue == toValue) {
+ return;
+ }
+ if (isAnimationEnabled()) {
+ startAnimation(fromValue, toValue);
+ } else {
+ mNative.setLightColor(mDeviceId, mLight.getId(), Color.argb(toValue, 0, 0, 0));
+ }
+ }
+
+ private void startAnimation(int fromValue, int toValue) {
+ // Cancel any ongoing animation before starting a new one
+ cancelAnimation();
+ mAnimator = mAnimatorFactory.makeIntAnimator(fromValue, toValue);
+ mAnimator.addUpdateListener(
+ (animation) -> mNative.setLightColor(mDeviceId, mLight.getId(),
+ Color.argb((int) animation.getAnimatedValue(), 0, 0, 0)));
+ mAnimator.setDuration(TRANSITION_ANIMATION_DURATION_MILLIS).start();
+ }
+
@Override
public String toString() {
return "KeyboardBacklightState{Light=" + mLight.getId()
@@ -493,4 +533,9 @@ final class KeyboardBacklightController implements
+ "}";
}
}
+
+ @VisibleForTesting
+ interface AnimatorFactory {
+ ValueAnimator makeIntAnimator(int from, int to);
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 6793800364de..08d7d5b872ac 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -18,7 +18,6 @@ package com.android.server.credentials;
import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS;
import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN;
-import static android.Manifest.permission.LAUNCH_CREDENTIAL_SELECTOR;
import static android.content.Context.CREDENTIAL_SERVICE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -46,7 +45,6 @@ import android.credentials.ISetEnabledProvidersCallback;
import android.credentials.PrepareGetCredentialResponseInternal;
import android.credentials.RegisterCredentialDescriptionRequest;
import android.credentials.UnregisterCredentialDescriptionRequest;
-import android.credentials.ui.IntentFactory;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.IBinder;
@@ -69,6 +67,7 @@ import com.android.server.infra.AbstractMasterSystemService;
import com.android.server.infra.SecureSettingsServiceNameResolver;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
@@ -282,6 +281,18 @@ public final class CredentialManagerService
}
}
+ private Set<String> getPrimaryProvidersForUserId(int userId) {
+ final int resolvedUserId = ActivityManager.handleIncomingUser(
+ Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, false, false,
+ "getPrimaryProvidersForUserId", null);
+ SecureSettingsServiceNameResolver resolver = new SecureSettingsServiceNameResolver(
+ mContext, Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
+ /* isMultipleMode= */ true);
+ String[] serviceNames = resolver.readServiceNameList(resolvedUserId);
+ return new HashSet<String>(Arrays.asList(serviceNames));
+ }
+
@GuardedBy("mLock")
private List<CredentialManagerServiceImpl> getCredentialProviderServicesLocked(int userId) {
List<CredentialManagerServiceImpl> concatenatedServices = new ArrayList<>();
@@ -689,7 +700,8 @@ public final class CredentialManagerService
@Override
public void setEnabledProviders(
- List<String> providers, int userId, ISetEnabledProvidersCallback callback) {
+ List<String> primaryProviders, List<String> providers, int userId,
+ ISetEnabledProvidersCallback callback) {
if (!hasWriteSecureSettingsPermission()) {
try {
callback.onError(
@@ -710,17 +722,24 @@ public final class CredentialManagerService
"setEnabledProviders",
null);
- String storedValue = String.join(":", providers);
- if (!Settings.Secure.putStringForUser(
- getContext().getContentResolver(),
- Settings.Secure.CREDENTIAL_SERVICE,
- storedValue,
- userId)) {
- Slog.e(TAG, "Failed to store setting containing enabled providers");
+ boolean writeEnabledStatus =
+ Settings.Secure.putStringForUser(getContext().getContentResolver(),
+ Settings.Secure.CREDENTIAL_SERVICE,
+ String.join(":", providers),
+ userId);
+
+ boolean writePrimaryStatus =
+ Settings.Secure.putStringForUser(getContext().getContentResolver(),
+ Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
+ String.join(":", primaryProviders),
+ userId);
+
+ if (!writeEnabledStatus || !writePrimaryStatus) {
+ Slog.e(TAG, "Failed to store setting containing enabled or primary providers");
try {
callback.onError(
"failed_setting_store",
- "Failed to store setting containing enabled providers");
+ "Failed to store setting containing enabled or primary providers");
} catch (RemoteException e) {
Slog.e(TAG, "Issue with invoking error response: ", e);
return;
@@ -734,10 +753,6 @@ public final class CredentialManagerService
Slog.e(TAG, "Issue with invoking response: ", e);
// TODO: Propagate failure
}
-
- // Send an intent to the UI that we have new enabled providers.
- getContext().sendBroadcast(IntentFactory.createProviderUpdateIntent(),
- LAUNCH_CREDENTIAL_SELECTOR);
}
@Override
@@ -785,7 +800,8 @@ public final class CredentialManagerService
verifyGetProvidersPermission();
return CredentialProviderInfoFactory.getCredentialProviderServices(
- mContext, userId, providerFilter, getEnabledProviders());
+ mContext, userId, providerFilter, getEnabledProviders(),
+ getPrimaryProvidersForUserId(userId));
}
@Override
@@ -795,7 +811,8 @@ public final class CredentialManagerService
final int userId = UserHandle.getCallingUserId();
return CredentialProviderInfoFactory.getCredentialProviderServicesForTesting(
- mContext, userId, providerFilter, getEnabledProviders());
+ mContext, userId, providerFilter, getEnabledProviders(),
+ getPrimaryProvidersForUserId(userId));
}
@Override
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index c2b51029778e..b3812c9138de 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -36,6 +36,7 @@ import android.service.credentials.CredentialProviderInfoFactory;
import android.util.Slog;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
@@ -154,7 +155,9 @@ public class CredentialManagerUi {
mContext,
mUserId,
CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY,
- mEnabledProviders);
+ mEnabledProviders,
+ // Don't need primary providers here.
+ new HashSet<String>());
List<DisabledProviderData> disabledProviderDataList = allProviders.stream()
.filter(provider -> !provider.isEnabled())
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
index 64c05dc8ab84..272679280a62 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
@@ -16,6 +16,7 @@
package com.android.server.input
+import android.animation.ValueAnimator
import android.content.Context
import android.content.ContextWrapper
import android.graphics.Color
@@ -29,6 +30,7 @@ import android.os.UEventObserver
import android.os.test.TestLooper
import android.platform.test.annotations.Presubmit
import android.view.InputDevice
+import androidx.test.annotation.UiThreadTest
import androidx.test.core.app.ApplicationProvider
import com.android.server.input.KeyboardBacklightController.BRIGHTNESS_VALUE_FOR_LEVEL
import com.android.server.input.KeyboardBacklightController.USER_INACTIVITY_THRESHOLD_MILLIS
@@ -96,9 +98,11 @@ class KeyboardBacklightControllerTests {
private lateinit var context: Context
private lateinit var dataStore: PersistentDataStore
private lateinit var testLooper: TestLooper
+ private val totalLevels = BRIGHTNESS_VALUE_FOR_LEVEL.size
private var lightColorMap: HashMap<Int, Int> = HashMap()
private var lastBacklightState: KeyboardBacklightState? = null
private var sysfsNodeChanges = 0
+ private var lastAnimationValues = IntArray(2)
@Before
fun setup() {
@@ -115,8 +119,8 @@ class KeyboardBacklightControllerTests {
override fun finishWrite(fos: FileOutputStream?, success: Boolean) {}
})
testLooper = TestLooper()
- keyboardBacklightController =
- KeyboardBacklightController(context, native, dataStore, testLooper.looper)
+ keyboardBacklightController = KeyboardBacklightController(context, native, dataStore,
+ testLooper.looper, FakeAnimatorFactory())
InputManagerGlobal.resetInstance(iInputManager)
val inputManager = InputManager(context)
`when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager)
@@ -125,6 +129,10 @@ class KeyboardBacklightControllerTests {
val args = it.arguments
lightColorMap.put(args[1] as Int, args[2] as Int)
}
+ `when`(native.getLightColor(anyInt(), anyInt())).thenAnswer {
+ val args = it.arguments
+ lightColorMap.getOrDefault(args[1] as Int, 0)
+ }
lightColorMap.clear()
`when`(native.sysfsNodeChanged(any())).then {
sysfsNodeChanges++
@@ -138,271 +146,287 @@ class KeyboardBacklightControllerTests {
@Test
fun testKeyboardBacklightIncrementDecrement() {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+ BacklightAnimationFlag(false).use {
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- for (level in 1 until BRIGHTNESS_VALUE_FOR_LEVEL.size) {
+ for (level in 1 until totalLevels) {
+ incrementKeyboardBacklight(DEVICE_ID)
+ assertEquals(
+ "Light value for level $level mismatched",
+ Color.argb(BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0),
+ lightColorMap[LIGHT_ID]
+ )
+ assertEquals(
+ "Light value for level $level must be correctly stored in the datastore",
+ BRIGHTNESS_VALUE_FOR_LEVEL[level],
+ dataStore.getKeyboardBacklightBrightness(
+ keyboardWithBacklight.descriptor,
+ LIGHT_ID
+ ).asInt
+ )
+ }
+
+ // Increment above max level
incrementKeyboardBacklight(DEVICE_ID)
assertEquals(
- "Light value for level $level mismatched",
- Color.argb(BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0),
+ "Light value for max level mismatched",
+ Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
lightColorMap[LIGHT_ID]
)
assertEquals(
- "Light value for level $level must be correctly stored in the datastore",
- BRIGHTNESS_VALUE_FOR_LEVEL[level],
+ "Light value for max level must be correctly stored in the datastore",
+ MAX_BRIGHTNESS,
dataStore.getKeyboardBacklightBrightness(
- keyboardWithBacklight.descriptor,
- LIGHT_ID
+ keyboardWithBacklight.descriptor,
+ LIGHT_ID
).asInt
)
- }
- // Increment above max level
- incrementKeyboardBacklight(DEVICE_ID)
- assertEquals(
- "Light value for max level mismatched",
- Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
- assertEquals(
- "Light value for max level must be correctly stored in the datastore",
- MAX_BRIGHTNESS,
- dataStore.getKeyboardBacklightBrightness(
- keyboardWithBacklight.descriptor,
- LIGHT_ID
- ).asInt
- )
+ for (level in totalLevels - 2 downTo 0) {
+ decrementKeyboardBacklight(DEVICE_ID)
+ assertEquals(
+ "Light value for level $level mismatched",
+ Color.argb(BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0),
+ lightColorMap[LIGHT_ID]
+ )
+ assertEquals(
+ "Light value for level $level must be correctly stored in the datastore",
+ BRIGHTNESS_VALUE_FOR_LEVEL[level],
+ dataStore.getKeyboardBacklightBrightness(
+ keyboardWithBacklight.descriptor,
+ LIGHT_ID
+ ).asInt
+ )
+ }
- for (level in BRIGHTNESS_VALUE_FOR_LEVEL.size - 2 downTo 0) {
+ // Decrement below min level
decrementKeyboardBacklight(DEVICE_ID)
assertEquals(
- "Light value for level $level mismatched",
- Color.argb(BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0),
+ "Light value for min level mismatched",
+ Color.argb(0, 0, 0, 0),
lightColorMap[LIGHT_ID]
)
assertEquals(
- "Light value for level $level must be correctly stored in the datastore",
- BRIGHTNESS_VALUE_FOR_LEVEL[level],
+ "Light value for min level must be correctly stored in the datastore",
+ 0,
dataStore.getKeyboardBacklightBrightness(
- keyboardWithBacklight.descriptor,
- LIGHT_ID
+ keyboardWithBacklight.descriptor,
+ LIGHT_ID
).asInt
)
}
-
- // Decrement below min level
- decrementKeyboardBacklight(DEVICE_ID)
- assertEquals(
- "Light value for min level mismatched",
- Color.argb(0, 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
- assertEquals(
- "Light value for min level must be correctly stored in the datastore",
- 0,
- dataStore.getKeyboardBacklightBrightness(
- keyboardWithBacklight.descriptor,
- LIGHT_ID
- ).asInt
- )
}
@Test
fun testKeyboardWithoutBacklight() {
- val keyboardWithoutBacklight = createKeyboard(DEVICE_ID)
- val keyboardInputLight = createLight(LIGHT_ID, Light.LIGHT_TYPE_INPUT)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithoutBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardInputLight))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
- incrementKeyboardBacklight(DEVICE_ID)
- assertTrue("Non Keyboard backlights should not change", lightColorMap.isEmpty())
+ BacklightAnimationFlag(false).use {
+ val keyboardWithoutBacklight = createKeyboard(DEVICE_ID)
+ val keyboardInputLight = createLight(LIGHT_ID, Light.LIGHT_TYPE_INPUT)
+ `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithoutBacklight)
+ `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardInputLight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+ incrementKeyboardBacklight(DEVICE_ID)
+ assertTrue("Non Keyboard backlights should not change", lightColorMap.isEmpty())
+ }
}
@Test
fun testKeyboardWithMultipleLight() {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- val keyboardInputLight = createLight(SECOND_LIGHT_ID, Light.LIGHT_TYPE_INPUT)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(
- listOf(
- keyboardBacklight,
- keyboardInputLight
+ BacklightAnimationFlag(false).use {
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ val keyboardInputLight = createLight(SECOND_LIGHT_ID, Light.LIGHT_TYPE_INPUT)
+ `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(
+ listOf(
+ keyboardBacklight,
+ keyboardInputLight
+ )
)
- )
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- incrementKeyboardBacklight(DEVICE_ID)
- assertEquals("Only keyboard backlights should change", 1, lightColorMap.size)
- assertNotNull("Keyboard backlight should change", lightColorMap[LIGHT_ID])
- assertNull("Input lights should not change", lightColorMap[SECOND_LIGHT_ID])
+ incrementKeyboardBacklight(DEVICE_ID)
+ assertEquals("Only keyboard backlights should change", 1, lightColorMap.size)
+ assertNotNull("Keyboard backlight should change", lightColorMap[LIGHT_ID])
+ assertNull("Input lights should not change", lightColorMap[SECOND_LIGHT_ID])
+ }
}
@Test
fun testRestoreBacklightOnInputDeviceAdded() {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-
- for (level in 1 until BRIGHTNESS_VALUE_FOR_LEVEL.size) {
- dataStore.setKeyboardBacklightBrightness(
+ BacklightAnimationFlag(false).use {
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+
+ for (level in 1 until totalLevels) {
+ dataStore.setKeyboardBacklightBrightness(
keyboardWithBacklight.descriptor,
LIGHT_ID,
BRIGHTNESS_VALUE_FOR_LEVEL[level] - 1
- )
-
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- keyboardBacklightController.notifyUserActivity()
- testLooper.dispatchNext()
- assertEquals(
- "Keyboard backlight level should be restored to the level saved in the data " +
- "store",
+ )
+
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+ keyboardBacklightController.notifyUserActivity()
+ testLooper.dispatchNext()
+ assertEquals(
+ "Keyboard backlight level should be restored to the level saved in the " +
+ "data store",
Color.argb(BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0),
lightColorMap[LIGHT_ID]
- )
- keyboardBacklightController.onInputDeviceRemoved(DEVICE_ID)
+ )
+ keyboardBacklightController.onInputDeviceRemoved(DEVICE_ID)
+ }
}
}
@Test
fun testRestoreBacklightOnInputDeviceChanged() {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- dataStore.setKeyboardBacklightBrightness(
- keyboardWithBacklight.descriptor,
- LIGHT_ID,
- MAX_BRIGHTNESS
- )
+ BacklightAnimationFlag(false).use {
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ dataStore.setKeyboardBacklightBrightness(
+ keyboardWithBacklight.descriptor,
+ LIGHT_ID,
+ MAX_BRIGHTNESS
+ )
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- keyboardBacklightController.notifyUserActivity()
- testLooper.dispatchNext()
- assertTrue(
- "Keyboard backlight should not be changed until its added",
- lightColorMap.isEmpty()
- )
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+ keyboardBacklightController.notifyUserActivity()
+ testLooper.dispatchNext()
+ assertTrue(
+ "Keyboard backlight should not be changed until its added",
+ lightColorMap.isEmpty()
+ )
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
- keyboardBacklightController.onInputDeviceChanged(DEVICE_ID)
- keyboardBacklightController.notifyUserActivity()
- testLooper.dispatchNext()
- assertEquals(
- "Keyboard backlight level should be restored to the level saved in the data store",
- Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
+ `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceChanged(DEVICE_ID)
+ keyboardBacklightController.notifyUserActivity()
+ testLooper.dispatchNext()
+ assertEquals(
+ "Keyboard backlight level should be restored to the level saved in the data store",
+ Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
+ lightColorMap[LIGHT_ID]
+ )
+ }
}
@Test
fun testKeyboardBacklight_registerUnregisterListener() {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+ BacklightAnimationFlag(false).use {
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- // Register backlight listener
- val listener = KeyboardBacklightListener()
- keyboardBacklightController.registerKeyboardBacklightListener(listener, 0)
+ // Register backlight listener
+ val listener = KeyboardBacklightListener()
+ keyboardBacklightController.registerKeyboardBacklightListener(listener, 0)
- lastBacklightState = null
- keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
- testLooper.dispatchNext()
+ lastBacklightState = null
+ keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
+ testLooper.dispatchNext()
- assertEquals(
- "Backlight state device Id should be $DEVICE_ID",
- DEVICE_ID,
- lastBacklightState!!.deviceId
- )
- assertEquals(
- "Backlight state brightnessLevel should be " + 1,
- 1,
- lastBacklightState!!.brightnessLevel
- )
- assertEquals(
- "Backlight state maxBrightnessLevel should be " + (BRIGHTNESS_VALUE_FOR_LEVEL.size - 1),
- (BRIGHTNESS_VALUE_FOR_LEVEL.size - 1),
- lastBacklightState!!.maxBrightnessLevel
- )
- assertEquals(
- "Backlight state isTriggeredByKeyPress should be true",
- true,
- lastBacklightState!!.isTriggeredByKeyPress
- )
+ assertEquals(
+ "Backlight state device Id should be $DEVICE_ID",
+ DEVICE_ID,
+ lastBacklightState!!.deviceId
+ )
+ assertEquals(
+ "Backlight state brightnessLevel should be " + 1,
+ 1,
+ lastBacklightState!!.brightnessLevel
+ )
+ assertEquals(
+ "Backlight state maxBrightnessLevel should be " + (totalLevels - 1),
+ (totalLevels - 1),
+ lastBacklightState!!.maxBrightnessLevel
+ )
+ assertEquals(
+ "Backlight state isTriggeredByKeyPress should be true",
+ true,
+ lastBacklightState!!.isTriggeredByKeyPress
+ )
- // Unregister listener
- keyboardBacklightController.unregisterKeyboardBacklightListener(listener, 0)
+ // Unregister listener
+ keyboardBacklightController.unregisterKeyboardBacklightListener(listener, 0)
- lastBacklightState = null
- incrementKeyboardBacklight(DEVICE_ID)
+ lastBacklightState = null
+ incrementKeyboardBacklight(DEVICE_ID)
- assertNull("Listener should not receive any updates", lastBacklightState)
+ assertNull("Listener should not receive any updates", lastBacklightState)
+ }
}
@Test
fun testKeyboardBacklight_userActivity() {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
- dataStore.setKeyboardBacklightBrightness(
- keyboardWithBacklight.descriptor,
- LIGHT_ID,
- MAX_BRIGHTNESS
- )
+ BacklightAnimationFlag(false).use {
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ dataStore.setKeyboardBacklightBrightness(
+ keyboardWithBacklight.descriptor,
+ LIGHT_ID,
+ MAX_BRIGHTNESS
+ )
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- keyboardBacklightController.notifyUserActivity()
- testLooper.dispatchNext()
- assertEquals(
- "Keyboard backlight level should be restored to the level saved in the data store",
- Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+ keyboardBacklightController.notifyUserActivity()
+ testLooper.dispatchNext()
+ assertEquals(
+ "Keyboard backlight level should be restored to the level saved in the data store",
+ Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
+ lightColorMap[LIGHT_ID]
+ )
- testLooper.moveTimeForward(USER_INACTIVITY_THRESHOLD_MILLIS + 1000)
- testLooper.dispatchNext()
- assertEquals(
- "Keyboard backlight level should be turned off after inactivity",
- 0,
- lightColorMap[LIGHT_ID]
- )
+ testLooper.moveTimeForward(USER_INACTIVITY_THRESHOLD_MILLIS + 1000)
+ testLooper.dispatchNext()
+ assertEquals(
+ "Keyboard backlight level should be turned off after inactivity",
+ 0,
+ lightColorMap[LIGHT_ID]
+ )
+ }
}
@Test
fun testKeyboardBacklight_displayOnOff() {
- val keyboardWithBacklight = createKeyboard(DEVICE_ID)
- val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
- `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
- `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
- dataStore.setKeyboardBacklightBrightness(
- keyboardWithBacklight.descriptor,
- LIGHT_ID,
- MAX_BRIGHTNESS
- )
+ BacklightAnimationFlag(false).use {
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ dataStore.setKeyboardBacklightBrightness(
+ keyboardWithBacklight.descriptor,
+ LIGHT_ID,
+ MAX_BRIGHTNESS
+ )
- keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
- keyboardBacklightController.handleInteractiveStateChange(true /* isDisplayOn */)
- assertEquals(
- "Keyboard backlight level should be restored to the level saved in the data " +
- "store when display turned on",
- Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
- lightColorMap[LIGHT_ID]
- )
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+ keyboardBacklightController.handleInteractiveStateChange(true /* isDisplayOn */)
+ assertEquals(
+ "Keyboard backlight level should be restored to the level saved in the data " +
+ "store when display turned on",
+ Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
+ lightColorMap[LIGHT_ID]
+ )
- keyboardBacklightController.handleInteractiveStateChange(false /* isDisplayOn */)
- assertEquals(
- "Keyboard backlight level should be turned off after display is turned off",
- 0,
- lightColorMap[LIGHT_ID]
- )
+ keyboardBacklightController.handleInteractiveStateChange(false /* isDisplayOn */)
+ assertEquals(
+ "Keyboard backlight level should be turned off after display is turned off",
+ 0,
+ lightColorMap[LIGHT_ID]
+ )
+ }
}
@Test
@@ -463,6 +487,30 @@ class KeyboardBacklightControllerTests {
)
}
+ @Test
+ @UiThreadTest
+ fun testKeyboardBacklightAnimation_onChangeLevels() {
+ BacklightAnimationFlag(true).use {
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+ incrementKeyboardBacklight(DEVICE_ID)
+ assertEquals(
+ "Should start animation from level 0",
+ BRIGHTNESS_VALUE_FOR_LEVEL[0],
+ lastAnimationValues[0]
+ )
+ assertEquals(
+ "Should start animation to level 1",
+ BRIGHTNESS_VALUE_FOR_LEVEL[1],
+ lastAnimationValues[1]
+ )
+ }
+ }
+
inner class KeyboardBacklightListener : IKeyboardBacklightListener.Stub() {
override fun onBrightnessChanged(
deviceId: Int,
@@ -496,4 +544,22 @@ class KeyboardBacklightControllerTests {
val maxBrightnessLevel: Int,
val isTriggeredByKeyPress: Boolean
)
+
+ private inner class BacklightAnimationFlag constructor(enabled: Boolean) : AutoCloseable {
+ init {
+ InputFeatureFlagProvider.setKeyboardBacklightAnimationEnabled(enabled)
+ }
+
+ override fun close() {
+ InputFeatureFlagProvider.clearOverrides()
+ }
+ }
+
+ private inner class FakeAnimatorFactory : KeyboardBacklightController.AnimatorFactory {
+ override fun makeIntAnimator(from: Int, to: Int): ValueAnimator {
+ lastAnimationValues[0] = from
+ lastAnimationValues[1] = to
+ return ValueAnimator.ofInt(from, to)
+ }
+ }
}