summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java15
-rw-r--r--apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java6
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java13
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/Agent.java98
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java6
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java4
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java38
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java139
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java6
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/Scribe.java9
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/TareShellCommand.java112
-rw-r--r--core/api/current.txt7
-rw-r--r--core/java/android/hardware/face/FaceManager.java40
-rw-r--r--core/java/android/hardware/face/IFaceAuthenticatorsRegisteredCallback.aidl35
-rw-r--r--core/java/android/hardware/face/IFaceService.aidl9
-rw-r--r--core/java/android/hardware/fingerprint/IFingerprintService.aidl2
-rw-r--r--core/java/android/provider/DeviceConfig.java7
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java4
-rw-r--r--core/java/android/view/IWindowSession.aidl53
-rw-r--r--core/java/android/view/ViewRootImpl.java128
-rw-r--r--core/java/android/view/WindowlessWindowManager.java39
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java10
-rw-r--r--core/java/android/widget/Editor.java28
-rw-r--r--core/java/android/widget/SelectionActionModeHelper.java6
-rw-r--r--core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java21
-rw-r--r--core/res/res/layout/side_fps_toast.xml4
-rw-r--r--core/res/res/values-night/colors.xml5
-rw-r--r--core/res/res/values/colors.xml5
-rw-r--r--libs/WindowManager/Shell/res/animator/tv_window_menu_action_button_animator.xml (renamed from libs/WindowManager/Shell/res/animator/tv_pip_menu_action_button_animator.xml)0
-rw-r--r--libs/WindowManager/Shell/res/color/tv_window_menu_close_icon.xml (renamed from libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml)2
-rw-r--r--libs/WindowManager/Shell/res/color/tv_window_menu_close_icon_bg.xml (renamed from libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml)6
-rw-r--r--libs/WindowManager/Shell/res/color/tv_window_menu_icon.xml (renamed from libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml)8
-rw-r--r--libs/WindowManager/Shell/res/color/tv_window_menu_icon_bg.xml (renamed from libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml)4
-rw-r--r--libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml21
-rw-r--r--libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml2
-rw-r--r--libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml21
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_pip_menu.xml10
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml40
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml40
-rw-r--r--libs/WindowManager/Shell/res/values-tvdpi/dimen.xml16
-rw-r--r--libs/WindowManager/Shell/res/values/colors_tv.xml16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java)38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt193
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt50
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt194
-rw-r--r--libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp2
-rw-r--r--libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp8
-rw-r--r--libs/hwui/pipeline/skia/SkiaProfileRenderer.h9
-rw-r--r--libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp4
-rw-r--r--media/java/android/media/IMediaRouter2.aidl4
-rw-r--r--media/java/android/media/IMediaRouter2Manager.aidl4
-rw-r--r--media/java/android/media/MediaRouter2.java92
-rw-r--r--media/java/android/media/MediaRouter2Manager.java85
-rw-r--r--media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java189
-rw-r--r--media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java43
-rw-r--r--packages/SettingsLib/Spa/OWNERS4
-rw-r--r--packages/SettingsLib/Spa/build.gradle6
-rw-r--r--packages/SettingsLib/Spa/codelab/AndroidManifest.xml4
-rw-r--r--packages/SettingsLib/Spa/codelab/build.gradle4
-rw-r--r--packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/FooterPage.kt71
-rw-r--r--packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt5
-rw-r--r--packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt11
-rw-r--r--packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SliderPage.kt107
-rw-r--r--packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SwitchPreferencePage.kt129
-rw-r--r--packages/SettingsLib/Spa/spa/Android.bp5
-rw-r--r--packages/SettingsLib/Spa/spa/build.gradle16
-rw-r--r--packages/SettingsLib/Spa/spa/res/values-night/themes.xml4
-rw-r--r--packages/SettingsLib/Spa/spa/res/values/themes.xml6
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt180
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/RuntimeUtils.kt8
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt91
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt1
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt20
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt205
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt144
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt9
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt13
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt146
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt47
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Icon.kt38
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt185
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt47
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt52
-rw-r--r--packages/SettingsLib/Spa/tests/build.gradle8
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SwitchPreferenceTest.kt91
-rw-r--r--packages/SettingsLib/SpaPrivileged/Android.bp33
-rw-r--r--packages/SettingsLib/SpaPrivileged/AndroidManifest.xml18
-rw-r--r--packages/SettingsLib/SpaPrivileged/OWNERS1
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRepository.kt58
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt25
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt70
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java12
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java12
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LockIconViewController.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java86
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java92
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java4
-rw-r--r--proto/src/system_messages.proto4
-rw-r--r--services/core/java/com/android/server/DropBoxManagerService.java4
-rw-r--r--services/core/java/com/android/server/am/LmkdStatsReporter.java6
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java27
-rw-r--r--services/core/java/com/android/server/am/ProcessServiceRecord.java10
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java1
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java61
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricServiceRegistry.java235
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java94
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/FaceService.java221
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java71
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java30
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java5
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java5
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java373
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java72
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java37
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java5
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java5
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java30
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java42
-rw-r--r--services/core/java/com/android/server/display/brightness/BrightnessEvent.java157
-rw-r--r--services/core/java/com/android/server/display/color/ColorDisplayService.java4
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java12
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java319
-rw-r--r--services/core/java/com/android/server/wm/ActivityMetricsLogger.java28
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java12
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java13
-rw-r--r--services/core/java/com/android/server/wm/Session.java23
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java106
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java39
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java79
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java (renamed from services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java)9
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java165
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java51
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java161
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java41
-rw-r--r--services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java16
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java65
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl3
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt5
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt7
-rw-r--r--tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java23
151 files changed, 5292 insertions, 1496 deletions
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
index 1e2650dbfdc4..3a23b5491678 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
@@ -28,6 +28,7 @@ import android.util.Log;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -50,6 +51,14 @@ public class TypefaceSerializationPerfTest {
@Rule
public PerfManualStatusReporter mPerfManualStatusReporter = new PerfManualStatusReporter();
+ @Before
+ public void setUp() {
+ // Parse and load the preinstalled fonts in the test process so that:
+ // (1) Updated fonts do not affect test results.
+ // (2) Lazy-loading of fonts does not affect test results (esp. testSerializeFontMap).
+ Typeface.loadPreinstalledSystemFontMap();
+ }
+
@ManualBenchmarkState.ManualBenchmarkTest(
warmupDurationNs = WARMUP_DURATION_NS,
targetTestDurationNs = TARGET_TEST_DURATION_NS)
@@ -61,8 +70,12 @@ public class TypefaceSerializationPerfTest {
long elapsedTime = 0;
while (state.keepRunning(elapsedTime)) {
long startTime = System.nanoTime();
- Typeface.serializeFontMap(systemFontMap);
+ SharedMemory sharedMemory = Typeface.serializeFontMap(systemFontMap);
elapsedTime = System.nanoTime() - startTime;
+ sharedMemory.close();
+ android.util.Log.i(TAG,
+ "testSerializeFontMap isWarmingUp=" + state.isWarmingUp()
+ + " elapsedTime=" + elapsedTime);
}
}
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
index c92c6340a6b4..fb62920681de 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
@@ -153,9 +153,9 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase
final IWindowSession session = WindowManagerGlobal.getWindowSession();
while (state.keepRunning()) {
session.relayout(mWindow, mParams, mWidth, mHeight,
- mViewVisibility.getAsInt(), mFlags, mOutFrames,
- mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState, mOutControls,
- new Bundle());
+ mViewVisibility.getAsInt(), mFlags, 0 /* seq */, 0 /* lastSyncSeqId */,
+ mOutFrames, mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState,
+ mOutControls, new Bundle());
}
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 42e60e419de0..57c731757cc9 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -51,6 +51,7 @@ import android.util.Slog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
@@ -1668,7 +1669,8 @@ public final class JobStatus {
return readinessStatusWithConstraint(constraint, true);
}
- private boolean readinessStatusWithConstraint(int constraint, boolean value) {
+ @VisibleForTesting
+ boolean readinessStatusWithConstraint(int constraint, boolean value) {
boolean oldValue = false;
int satisfied = mSatisfiedConstraintsOfInterest;
switch (constraint) {
@@ -1704,6 +1706,15 @@ public final class JobStatus {
break;
}
+ // The flexibility constraint relies on other constraints to be satisfied.
+ // This function lacks the information to determine if flexibility will be satisfied.
+ // But for the purposes of this function it is still useful to know the jobs' readiness
+ // not including the flexibility constraint. If flexibility is the constraint in question
+ // we can proceed as normal.
+ if (constraint != CONSTRAINT_FLEXIBLE) {
+ satisfied |= CONSTRAINT_FLEXIBLE;
+ }
+
boolean toReturn = isReady(satisfied);
switch (constraint) {
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
index 8b8a57d248b9..e23860c1eca3 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -34,8 +34,6 @@ import static com.android.server.tare.TareUtils.getCurrentTimeMillis;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -46,6 +44,7 @@ import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArrayMap;
+import android.util.SparseSetArray;
import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
@@ -284,6 +283,7 @@ class Agent {
for (int i = 0; i < pkgNames.size(); ++i) {
final String pkgName = pkgNames.valueAt(i);
+ final boolean isVip = mIrs.isVip(userId, pkgName);
SparseArrayMap<String, OngoingEvent> ongoingEvents =
mCurrentOngoingEvents.get(userId, pkgName);
if (ongoingEvents != null) {
@@ -298,8 +298,8 @@ class Agent {
for (int n = 0; n < size; ++n) {
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
note.recalculateCosts(economicPolicy, userId, pkgName);
- final boolean isAffordable =
- isAffordableLocked(newBalance,
+ final boolean isAffordable = isVip
+ || isAffordableLocked(newBalance,
note.getCachedModifiedPrice(), note.getCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
@@ -313,6 +313,51 @@ class Agent {
}
@GuardedBy("mLock")
+ void onVipStatusChangedLocked(final int userId, @NonNull String pkgName) {
+ final long now = getCurrentTimeMillis();
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
+
+ final boolean isVip = mIrs.isVip(userId, pkgName);
+ SparseArrayMap<String, OngoingEvent> ongoingEvents =
+ mCurrentOngoingEvents.get(userId, pkgName);
+ if (ongoingEvents != null) {
+ mOngoingEventUpdater.reset(userId, pkgName, now, nowElapsed);
+ ongoingEvents.forEach(mOngoingEventUpdater);
+ }
+ final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes =
+ mActionAffordabilityNotes.get(userId, pkgName);
+ if (actionAffordabilityNotes != null) {
+ final int size = actionAffordabilityNotes.size();
+ final long newBalance =
+ mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance();
+ for (int n = 0; n < size; ++n) {
+ final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
+ note.recalculateCosts(economicPolicy, userId, pkgName);
+ final boolean isAffordable = isVip
+ || isAffordableLocked(newBalance,
+ note.getCachedModifiedPrice(), note.getCtp());
+ if (note.isCurrentlyAffordable() != isAffordable) {
+ note.setNewAffordability(isAffordable);
+ mIrs.postAffordabilityChanged(userId, pkgName, note);
+ }
+ }
+ }
+ scheduleBalanceCheckLocked(userId, pkgName);
+ }
+
+ @GuardedBy("mLock")
+ void onVipStatusChangedLocked(@NonNull SparseSetArray<String> pkgs) {
+ for (int u = pkgs.size() - 1; u >= 0; --u) {
+ final int userId = pkgs.keyAt(u);
+
+ for (int p = pkgs.sizeAt(u) - 1; p >= 0; --p) {
+ onVipStatusChangedLocked(userId, pkgs.valueAt(u, p));
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
private void onAnythingChangedLocked(final boolean updateOngoingEvents) {
final long now = getCurrentTimeMillis();
final long nowElapsed = SystemClock.elapsedRealtime();
@@ -349,11 +394,12 @@ class Agent {
if (actionAffordabilityNotes != null) {
final int size = actionAffordabilityNotes.size();
final long newBalance = getBalanceLocked(userId, pkgName);
+ final boolean isVip = mIrs.isVip(userId, pkgName);
for (int n = 0; n < size; ++n) {
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
note.recalculateCosts(economicPolicy, userId, pkgName);
- final boolean isAffordable =
- isAffordableLocked(newBalance,
+ final boolean isAffordable = isVip
+ || isAffordableLocked(newBalance,
note.getCachedModifiedPrice(), note.getCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
@@ -454,6 +500,14 @@ class Agent {
"Tried to adjust system balance for " + appToString(userId, pkgName));
return;
}
+ if (mIrs.isVip(userId, pkgName)) {
+ // This could happen if the app was made a VIP after it started performing actions.
+ // Continue recording the transaction for debugging purposes, but don't let it change
+ // any numbers.
+ transaction = new Ledger.Transaction(
+ transaction.startTimeMs, transaction.endTimeMs,
+ transaction.eventId, transaction.tag, 0 /* delta */, transaction.ctp);
+ }
final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
final long originalBalance = ledger.getCurrentBalance();
if (transaction.delta > 0
@@ -479,10 +533,11 @@ class Agent {
mActionAffordabilityNotes.get(userId, pkgName);
if (actionAffordabilityNotes != null) {
final long newBalance = ledger.getCurrentBalance();
+ final boolean isVip = mIrs.isVip(userId, pkgName);
for (int i = 0; i < actionAffordabilityNotes.size(); ++i) {
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
- final boolean isAffordable =
- isAffordableLocked(newBalance,
+ final boolean isAffordable = isVip
+ || isAffordableLocked(newBalance,
note.getCachedModifiedPrice(), note.getCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
@@ -606,13 +661,12 @@ class Agent {
}
/** Returns true if an app should be given credits in the general distributions. */
- private boolean shouldGiveCredits(@NonNull PackageInfo packageInfo) {
- final ApplicationInfo applicationInfo = packageInfo.applicationInfo;
+ private boolean shouldGiveCredits(@NonNull InstalledPackageInfo packageInfo) {
// Skip apps that wouldn't be doing any work. Giving them ARCs would be wasteful.
- if (applicationInfo == null || !applicationInfo.hasCode()) {
+ if (!packageInfo.hasCode) {
return false;
}
- final int userId = UserHandle.getUserId(packageInfo.applicationInfo.uid);
+ final int userId = UserHandle.getUserId(packageInfo.uid);
// No point allocating ARCs to the system. It can do whatever it wants.
return !mIrs.isSystem(userId, packageInfo.packageName);
}
@@ -623,15 +677,15 @@ class Agent {
@GuardedBy("mLock")
void distributeBasicIncomeLocked(int batteryLevel) {
- List<PackageInfo> pkgs = mIrs.getInstalledPackages();
+ final List<InstalledPackageInfo> pkgs = mIrs.getInstalledPackages();
final long now = getCurrentTimeMillis();
for (int i = 0; i < pkgs.size(); ++i) {
- final PackageInfo pkgInfo = pkgs.get(i);
+ final InstalledPackageInfo pkgInfo = pkgs.get(i);
if (!shouldGiveCredits(pkgInfo)) {
continue;
}
- final int userId = UserHandle.getUserId(pkgInfo.applicationInfo.uid);
+ final int userId = UserHandle.getUserId(pkgInfo.uid);
final String pkgName = pkgInfo.packageName;
final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
final long minBalance = mIrs.getMinBalanceLocked(userId, pkgName);
@@ -659,11 +713,11 @@ class Agent {
@GuardedBy("mLock")
void grantBirthrightsLocked(final int userId) {
- final List<PackageInfo> pkgs = mIrs.getInstalledPackages(userId);
+ final List<InstalledPackageInfo> pkgs = mIrs.getInstalledPackages(userId);
final long now = getCurrentTimeMillis();
for (int i = 0; i < pkgs.size(); ++i) {
- final PackageInfo packageInfo = pkgs.get(i);
+ final InstalledPackageInfo packageInfo = pkgs.get(i);
if (!shouldGiveCredits(packageInfo)) {
continue;
}
@@ -869,7 +923,7 @@ class Agent {
private void scheduleBalanceCheckLocked(final int userId, @NonNull final String pkgName) {
SparseArrayMap<String, OngoingEvent> ongoingEvents =
mCurrentOngoingEvents.get(userId, pkgName);
- if (ongoingEvents == null) {
+ if (ongoingEvents == null || mIrs.isVip(userId, pkgName)) {
// No ongoing transactions. No reason to schedule
mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
return;
@@ -1062,9 +1116,10 @@ class Agent {
note.setNewAffordability(true);
return;
}
+ final boolean isVip = mIrs.isVip(userId, pkgName);
note.recalculateCosts(economicPolicy, userId, pkgName);
- note.setNewAffordability(
- isAffordableLocked(getBalanceLocked(userId, pkgName),
+ note.setNewAffordability(isVip
+ || isAffordableLocked(getBalanceLocked(userId, pkgName),
note.getCachedModifiedPrice(), note.getCtp()));
mIrs.postAffordabilityChanged(userId, pkgName, note);
// Update ongoing alarm
@@ -1203,11 +1258,12 @@ class Agent {
if (actionAffordabilityNotes != null
&& actionAffordabilityNotes.size() > 0) {
final long newBalance = getBalanceLocked(userId, pkgName);
+ final boolean isVip = mIrs.isVip(userId, pkgName);
for (int i = 0; i < actionAffordabilityNotes.size(); ++i) {
final ActionAffordabilityNote note =
actionAffordabilityNotes.valueAt(i);
- final boolean isAffordable = isAffordableLocked(
+ final boolean isAffordable = isVip || isAffordableLocked(
newBalance, note.getCachedModifiedPrice(), note.getCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
index a46430feb688..aa66e92a091f 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
@@ -149,7 +149,6 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy {
private long mHardSatiatedConsumptionLimit;
private final KeyValueListParser mParser = new KeyValueListParser(',');
- private final InternalResourceService mInternalResourceService;
private final Injector mInjector;
private final SparseArray<Action> mActions = new SparseArray<>();
@@ -157,7 +156,6 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy {
AlarmManagerEconomicPolicy(InternalResourceService irs, Injector injector) {
super(irs);
- mInternalResourceService = irs;
mInjector = injector;
loadConstants("", null);
}
@@ -165,14 +163,14 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy {
@Override
void setup(@NonNull DeviceConfig.Properties properties) {
super.setup(properties);
- ContentResolver resolver = mInternalResourceService.getContext().getContentResolver();
+ ContentResolver resolver = mIrs.getContext().getContentResolver();
loadConstants(mInjector.getSettingsGlobalString(resolver, TARE_ALARM_MANAGER_CONSTANTS),
properties);
}
@Override
long getMinSatiatedBalance(final int userId, @NonNull final String pkgName) {
- if (mInternalResourceService.isPackageExempted(userId, pkgName)) {
+ if (mIrs.isPackageExempted(userId, pkgName)) {
return mMinSatiatedBalanceExempted;
}
// TODO: take other exemptions into account
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
index 0937e7ba4055..564ffb9c4169 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
@@ -169,9 +169,11 @@ public abstract class EconomicPolicy {
}
}
+ protected final InternalResourceService mIrs;
private static final Modifier[] COST_MODIFIER_BY_INDEX = new Modifier[NUM_COST_MODIFIERS];
EconomicPolicy(@NonNull InternalResourceService irs) {
+ mIrs = irs;
for (int mId : getCostModifiers()) {
initModifier(mId, irs);
}
@@ -240,7 +242,7 @@ public abstract class EconomicPolicy {
@NonNull
final Cost getCostOfAction(int actionId, int userId, @NonNull String pkgName) {
final Action action = getAction(actionId);
- if (action == null) {
+ if (action == null || mIrs.isVip(userId, pkgName)) {
return new Cost(0, 0);
}
long ctp = action.costToProduce;
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java
new file mode 100644
index 000000000000..da544bb6a2eb
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 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.tare;
+
+import android.annotation.NonNull;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.os.UserHandle;
+
+/** POJO to cache only the information about installed packages that TARE cares about. */
+class InstalledPackageInfo {
+ static final int NO_UID = -1;
+
+ public final int uid;
+ public final String packageName;
+ public final boolean hasCode;
+
+ InstalledPackageInfo(@NonNull PackageInfo packageInfo) {
+ final ApplicationInfo applicationInfo = packageInfo.applicationInfo;
+ this.uid = applicationInfo == null ? NO_UID : applicationInfo.uid;
+ this.packageName = packageInfo.packageName;
+ this.hasCode = applicationInfo != null && applicationInfo.hasCode();
+ }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 6d5c16021ea9..7a7d669ae229 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -48,6 +48,7 @@ import android.os.Handler;
import android.os.IDeviceIdleController;
import android.os.Looper;
import android.os.Message;
+import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -131,7 +132,7 @@ public class InternalResourceService extends SystemService {
@NonNull
@GuardedBy("mLock")
- private final List<PackageInfo> mPkgCache = new ArrayList<>();
+ private final List<InstalledPackageInfo> mPkgCache = new ArrayList<>();
/** Cached mapping of UIDs (for all users) to a list of packages in the UID. */
@GuardedBy("mLock")
@@ -149,6 +150,9 @@ public class InternalResourceService extends SystemService {
@GuardedBy("mLock")
private ArraySet<String> mExemptedApps = new ArraySet<>();
+ @GuardedBy("mLock")
+ private final SparseArrayMap<String, Boolean> mVipOverrides = new SparseArrayMap<>();
+
private volatile boolean mIsEnabled;
private volatile int mBootPhase;
private volatile boolean mExemptListLoaded;
@@ -303,7 +307,7 @@ public class InternalResourceService extends SystemService {
}
@NonNull
- List<PackageInfo> getInstalledPackages() {
+ List<InstalledPackageInfo> getInstalledPackages() {
synchronized (mLock) {
return mPkgCache;
}
@@ -311,13 +315,12 @@ public class InternalResourceService extends SystemService {
/** Returns the installed packages for the specified user. */
@NonNull
- List<PackageInfo> getInstalledPackages(final int userId) {
- final List<PackageInfo> userPkgs = new ArrayList<>();
+ List<InstalledPackageInfo> getInstalledPackages(final int userId) {
+ final List<InstalledPackageInfo> userPkgs = new ArrayList<>();
synchronized (mLock) {
for (int i = 0; i < mPkgCache.size(); ++i) {
- final PackageInfo packageInfo = mPkgCache.get(i);
- if (packageInfo.applicationInfo != null
- && UserHandle.getUserId(packageInfo.applicationInfo.uid) == userId) {
+ final InstalledPackageInfo packageInfo = mPkgCache.get(i);
+ if (UserHandle.getUserId(packageInfo.uid) == userId) {
userPkgs.add(packageInfo);
}
}
@@ -369,6 +372,21 @@ public class InternalResourceService extends SystemService {
return UserHandle.isCore(getUid(userId, pkgName));
}
+ boolean isVip(final int userId, @NonNull String pkgName) {
+ synchronized (mLock) {
+ final Boolean override = mVipOverrides.get(userId, pkgName);
+ if (override != null) {
+ return override;
+ }
+ }
+ if (isSystem(userId, pkgName)) {
+ // The government, I mean the system, can create ARCs as it needs to in order to
+ // operate.
+ return true;
+ }
+ return false;
+ }
+
void onBatteryLevelChanged() {
synchronized (mLock) {
final int newBatteryLevel = getCurrentBatteryLevel();
@@ -451,7 +469,7 @@ public class InternalResourceService extends SystemService {
mPackageToUidCache.add(userId, pkgName, uid);
}
synchronized (mLock) {
- mPkgCache.add(packageInfo);
+ mPkgCache.add(new InstalledPackageInfo(packageInfo));
mUidToPackageCache.add(uid, pkgName);
// TODO: only do this when the user first launches the app (app leaves stopped state)
mAgent.grantBirthrightLocked(userId, pkgName);
@@ -471,9 +489,10 @@ public class InternalResourceService extends SystemService {
}
synchronized (mLock) {
mUidToPackageCache.remove(uid, pkgName);
+ mVipOverrides.delete(userId, pkgName);
for (int i = 0; i < mPkgCache.size(); ++i) {
- PackageInfo pkgInfo = mPkgCache.get(i);
- if (UserHandle.getUserId(pkgInfo.applicationInfo.uid) == userId
+ final InstalledPackageInfo pkgInfo = mPkgCache.get(i);
+ if (UserHandle.getUserId(pkgInfo.uid) == userId
&& pkgName.equals(pkgInfo.packageName)) {
mPkgCache.remove(i);
break;
@@ -496,20 +515,24 @@ public class InternalResourceService extends SystemService {
void onUserAdded(final int userId) {
synchronized (mLock) {
- mPkgCache.addAll(
- mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId));
+ final List<PackageInfo> pkgs =
+ mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
+ for (int i = pkgs.size() - 1; i >= 0; --i) {
+ mPkgCache.add(new InstalledPackageInfo(pkgs.get(i)));
+ }
mAgent.grantBirthrightsLocked(userId);
}
}
void onUserRemoved(final int userId) {
synchronized (mLock) {
+ mVipOverrides.delete(userId);
ArrayList<String> removedPkgs = new ArrayList<>();
for (int i = mPkgCache.size() - 1; i >= 0; --i) {
- PackageInfo pkgInfo = mPkgCache.get(i);
- if (UserHandle.getUserId(pkgInfo.applicationInfo.uid) == userId) {
+ final InstalledPackageInfo pkgInfo = mPkgCache.get(i);
+ if (UserHandle.getUserId(pkgInfo.uid) == userId) {
removedPkgs.add(pkgInfo.packageName);
- mUidToPackageCache.remove(pkgInfo.applicationInfo.uid);
+ mUidToPackageCache.remove(pkgInfo.uid);
mPkgCache.remove(i);
break;
}
@@ -659,8 +682,11 @@ public class InternalResourceService extends SystemService {
LocalServices.getService(UserManagerInternal.class);
final int[] userIds = userManagerInternal.getUserIds();
for (int userId : userIds) {
- mPkgCache.addAll(
- mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId));
+ final List<PackageInfo> pkgs =
+ mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
+ for (int i = pkgs.size() - 1; i >= 0; --i) {
+ mPkgCache.add(new InstalledPackageInfo(pkgs.get(i)));
+ }
}
}
@@ -881,6 +907,15 @@ public class InternalResourceService extends SystemService {
Binder.restoreCallingIdentity(identityToken);
}
}
+
+ @Override
+ public int handleShellCommand(@NonNull ParcelFileDescriptor in,
+ @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
+ @NonNull String[] args) {
+ return (new TareShellCommand(InternalResourceService.this)).exec(
+ this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(),
+ args);
+ }
}
private final class LocalService implements EconomyManagerInternal {
@@ -932,9 +967,9 @@ public class InternalResourceService extends SystemService {
if (!mIsEnabled) {
return true;
}
- if (isSystem(userId, pkgName)) {
+ if (isVip(userId, pkgName)) {
// The government, I mean the system, can create ARCs as it needs to in order to
- // operate.
+ // allow VIPs to operate.
return true;
}
// TODO: take temp-allowlist into consideration
@@ -960,7 +995,7 @@ public class InternalResourceService extends SystemService {
if (!mIsEnabled) {
return FOREVER_MS;
}
- if (isSystem(userId, pkgName)) {
+ if (isVip(userId, pkgName)) {
return FOREVER_MS;
}
long totalCostPerSecond = 0;
@@ -1131,6 +1166,47 @@ public class InternalResourceService extends SystemService {
}
}
+ // Shell command infrastructure
+ int executeClearVip(@NonNull PrintWriter pw) {
+ synchronized (mLock) {
+ final SparseSetArray<String> changedPkgs = new SparseSetArray<>();
+ for (int u = mVipOverrides.numMaps() - 1; u >= 0; --u) {
+ final int userId = mVipOverrides.keyAt(u);
+
+ for (int p = mVipOverrides.numElementsForKeyAt(u) - 1; p >= 0; --p) {
+ changedPkgs.add(userId, mVipOverrides.keyAt(u, p));
+ }
+ }
+ mVipOverrides.clear();
+ if (mIsEnabled) {
+ mAgent.onVipStatusChangedLocked(changedPkgs);
+ }
+ }
+ pw.println("Cleared all VIP statuses");
+ return TareShellCommand.COMMAND_SUCCESS;
+ }
+
+ int executeSetVip(@NonNull PrintWriter pw,
+ int userId, @NonNull String pkgName, @Nullable Boolean newVipState) {
+ final boolean changed;
+ synchronized (mLock) {
+ final boolean wasVip = isVip(userId, pkgName);
+ if (newVipState == null) {
+ mVipOverrides.delete(userId, pkgName);
+ } else {
+ mVipOverrides.add(userId, pkgName, newVipState);
+ }
+ changed = isVip(userId, pkgName) != wasVip;
+ if (mIsEnabled && changed) {
+ mAgent.onVipStatusChangedLocked(userId, pkgName);
+ }
+ }
+ pw.println(appToString(userId, pkgName) + " VIP status set to " + newVipState + "."
+ + " Final VIP state changed? " + changed);
+ return TareShellCommand.COMMAND_SUCCESS;
+ }
+
+ // Dump infrastructure
private static void dumpHelp(PrintWriter pw) {
pw.println("Resource Economy (economy) dump options:");
pw.println(" [-h|--help] [package] ...");
@@ -1168,6 +1244,29 @@ public class InternalResourceService extends SystemService {
pw.print("Exempted apps", mExemptedApps);
pw.println();
+ boolean printedVips = false;
+ pw.println();
+ pw.print("VIPs:");
+ for (int u = 0; u < mVipOverrides.numMaps(); ++u) {
+ final int userId = mVipOverrides.keyAt(u);
+
+ for (int p = 0; p < mVipOverrides.numElementsForKeyAt(u); ++p) {
+ final String pkgName = mVipOverrides.keyAt(u, p);
+
+ printedVips = true;
+ pw.println();
+ pw.print(appToString(userId, pkgName));
+ pw.print("=");
+ pw.print(mVipOverrides.valueAt(u, p));
+ }
+ }
+ if (printedVips) {
+ pw.println();
+ } else {
+ pw.print(" None");
+ }
+ pw.println();
+
pw.println();
mCompleteEconomicPolicy.dump(pw);
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
index e7db1adc859e..03c5fdd63250 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
@@ -151,7 +151,6 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
private long mHardSatiatedConsumptionLimit;
private final KeyValueListParser mParser = new KeyValueListParser(',');
- private final InternalResourceService mInternalResourceService;
private final Injector mInjector;
private final SparseArray<Action> mActions = new SparseArray<>();
@@ -159,7 +158,6 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
JobSchedulerEconomicPolicy(InternalResourceService irs, Injector injector) {
super(irs);
- mInternalResourceService = irs;
mInjector = injector;
loadConstants("", null);
}
@@ -167,14 +165,14 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
@Override
void setup(@NonNull DeviceConfig.Properties properties) {
super.setup(properties);
- ContentResolver resolver = mInternalResourceService.getContext().getContentResolver();
+ final ContentResolver resolver = mIrs.getContext().getContentResolver();
loadConstants(mInjector.getSettingsGlobalString(resolver, TARE_JOB_SCHEDULER_CONSTANTS),
properties);
}
@Override
long getMinSatiatedBalance(final int userId, @NonNull final String pkgName) {
- if (mInternalResourceService.isPackageExempted(userId, pkgName)) {
+ if (mIrs.isPackageExempted(userId, pkgName)) {
return mMinSatiatedBalanceExempted;
}
// TODO: take other exemptions into account
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
index 8f7657e6c414..ed915cd88ebe 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -22,7 +22,6 @@ import static com.android.server.tare.TareUtils.appToString;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.pm.PackageInfo;
import android.os.Environment;
import android.os.UserHandle;
import android.util.ArraySet;
@@ -210,11 +209,11 @@ public class Scribe {
mRemainingConsumableCakes = 0;
final SparseArray<ArraySet<String>> installedPackagesPerUser = new SparseArray<>();
- final List<PackageInfo> installedPackages = mIrs.getInstalledPackages();
+ final List<InstalledPackageInfo> installedPackages = mIrs.getInstalledPackages();
for (int i = 0; i < installedPackages.size(); ++i) {
- final PackageInfo packageInfo = installedPackages.get(i);
- if (packageInfo.applicationInfo != null) {
- final int userId = UserHandle.getUserId(packageInfo.applicationInfo.uid);
+ final InstalledPackageInfo packageInfo = installedPackages.get(i);
+ if (packageInfo.uid != InstalledPackageInfo.NO_UID) {
+ final int userId = UserHandle.getUserId(packageInfo.uid);
ArraySet<String> pkgsForUser = installedPackagesPerUser.get(userId);
if (pkgsForUser == null) {
pkgsForUser = new ArraySet<>();
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/TareShellCommand.java b/apex/jobscheduler/service/java/com/android/server/tare/TareShellCommand.java
new file mode 100644
index 000000000000..5e380b408d01
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/tare/TareShellCommand.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 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.tare;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+
+import com.android.modules.utils.BasicShellCommandHandler;
+
+import java.io.PrintWriter;
+
+/**
+ * Shell command handler for TARE.
+ */
+public class TareShellCommand extends BasicShellCommandHandler {
+ static final int COMMAND_ERROR = -1;
+ static final int COMMAND_SUCCESS = 0;
+
+ private final InternalResourceService mIrs;
+
+ public TareShellCommand(@NonNull InternalResourceService irs) {
+ mIrs = irs;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ final PrintWriter pw = getOutPrintWriter();
+ try {
+ switch (cmd != null ? cmd : "") {
+ case "clear-vip":
+ return runClearVip(pw);
+ case "set-vip":
+ return runSetVip(pw);
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ } catch (Exception e) {
+ pw.println("Exception: " + e);
+ }
+ return COMMAND_ERROR;
+ }
+
+ @Override
+ public void onHelp() {
+ final PrintWriter pw = getOutPrintWriter();
+
+ pw.println("TARE commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println(" clear-vip");
+ pw.println(" Clears all VIP settings resulting from previous calls using `set-vip` and");
+ pw.println(" resets them all to default.");
+ pw.println(" set-vip <USER_ID> <PACKAGE> <true|false|default>");
+ pw.println(" Designate the app as a Very Important Package or not. A VIP is allowed to");
+ pw.println(" do as much work as it wants, regardless of TARE state.");
+ pw.println(" The user ID must be an explicit user ID. USER_ALL, CURRENT, etc. are not");
+ pw.println(" supported.");
+ pw.println();
+ }
+
+ private void checkPermission(@NonNull String operation) throws Exception {
+ final int perm = mIrs.getContext()
+ .checkCallingOrSelfPermission(Manifest.permission.CHANGE_APP_IDLE_STATE);
+ if (perm != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Uid " + Binder.getCallingUid()
+ + " not permitted to " + operation);
+ }
+ }
+
+ private int runClearVip(@NonNull PrintWriter pw) throws Exception {
+ checkPermission("clear vip");
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return mIrs.executeClearVip(pw);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private int runSetVip(@NonNull PrintWriter pw) throws Exception {
+ checkPermission("modify vip");
+
+ final int userId = Integer.parseInt(getNextArgRequired());
+ final String pkgName = getNextArgRequired();
+ final String vipState = getNextArgRequired();
+ final Boolean isVip = "default".equals(vipState) ? null : Boolean.valueOf(vipState);
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return mIrs.executeSetVip(pw, userId, pkgName, isVip);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index ea2ae0c9e15f..4734e8a75de6 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -23189,9 +23189,10 @@ package android.media {
public abstract static class MediaRouter2.RouteCallback {
ctor public MediaRouter2.RouteCallback();
- method public void onRoutesAdded(@NonNull java.util.List<android.media.MediaRoute2Info>);
- method public void onRoutesChanged(@NonNull java.util.List<android.media.MediaRoute2Info>);
- method public void onRoutesRemoved(@NonNull java.util.List<android.media.MediaRoute2Info>);
+ method @Deprecated public void onRoutesAdded(@NonNull java.util.List<android.media.MediaRoute2Info>);
+ method @Deprecated public void onRoutesChanged(@NonNull java.util.List<android.media.MediaRoute2Info>);
+ method @Deprecated public void onRoutesRemoved(@NonNull java.util.List<android.media.MediaRoute2Info>);
+ method public void onRoutesUpdated(@NonNull java.util.List<android.media.MediaRoute2Info>);
}
public class MediaRouter2.RoutingController {
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 7092e43596ec..7247ef77afb4 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -29,6 +29,7 @@ import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.CryptoObject;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.os.Binder;
@@ -674,6 +675,45 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
}
/**
+ * Forwards BiometricStateListener to FaceService.
+ *
+ * @param listener new BiometricStateListener being added
+ * @hide
+ */
+ public void registerBiometricStateListener(@NonNull BiometricStateListener listener) {
+ try {
+ mService.registerBiometricStateListener(listener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Adds a callback that gets called when the service registers all of the face
+ * authenticators (HALs).
+ *
+ * If the face authenticators are already registered when the callback is added, the
+ * callback is invoked immediately.
+ *
+ * The callback is automatically removed after it's invoked.
+ *
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ public void addAuthenticatorsRegisteredCallback(
+ IFaceAuthenticatorsRegisteredCallback callback) {
+ if (mService != null) {
+ try {
+ mService.addAuthenticatorsRegisteredCallback(callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ Slog.w(TAG, "addAuthenticatorsRegisteredCallback(): Service not connected!");
+ }
+ }
+
+ /**
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
diff --git a/core/java/android/hardware/face/IFaceAuthenticatorsRegisteredCallback.aidl b/core/java/android/hardware/face/IFaceAuthenticatorsRegisteredCallback.aidl
new file mode 100644
index 000000000000..78f978d21ed7
--- /dev/null
+++ b/core/java/android/hardware/face/IFaceAuthenticatorsRegisteredCallback.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 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 android.hardware.face;
+
+import android.hardware.face.FaceSensorPropertiesInternal;
+import java.util.List;
+
+/**
+ * Callback to notify FaceManager that FaceService has registered all of the
+ * face authenticators (HALs).
+ * See {@link android.hardware.face.IFaceService#registerAuthenticators}.
+ *
+ * @hide
+ */
+oneway interface IFaceAuthenticatorsRegisteredCallback {
+ /**
+ * Notifies FaceManager that all of the face authenticators have been registered.
+ *
+ * @param sensors A consolidated list of sensor properties for all of the authenticators.
+ */
+ void onAllAuthenticatorsRegistered(in List<FaceSensorPropertiesInternal> sensors);
+}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 369248edd580..9b56f43a0f22 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -17,9 +17,11 @@ package android.hardware.face;
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.biometrics.IBiometricStateListener;
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.hardware.face.IFaceServiceReceiver;
import android.hardware.face.Face;
import android.hardware.face.FaceSensorPropertiesInternal;
@@ -163,4 +165,11 @@ interface IFaceService {
// hidlSensors must be non-null and empty. See AuthService.java
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
void registerAuthenticators(in List<FaceSensorPropertiesInternal> hidlSensors);
+
+ // Adds a callback which gets called when the service registers all of the face
+ // authenticators. The callback is automatically removed after it's invoked.
+ void addAuthenticatorsRegisteredCallback(IFaceAuthenticatorsRegisteredCallback callback);
+
+ // Registers BiometricStateListener.
+ void registerBiometricStateListener(IBiometricStateListener listener);
}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index cc7ed183ed64..1ba9a0471c88 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -202,8 +202,10 @@ interface IFingerprintService {
void setSidefpsController(in ISidefpsController controller);
// Registers BiometricStateListener.
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
void registerBiometricStateListener(IBiometricStateListener listener);
// Sends a power button pressed event to all listeners.
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
oneway void onPowerPressed();
}
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 2483f99a07ec..b7adcb84102d 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -755,6 +755,13 @@ public final class DeviceConfig {
public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE = "vendor_system_native";
/**
+ * Namespace for Vendor System Native Boot related features.
+ *
+ * @hide
+ */
+ public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT = "vendor_system_native_boot";
+
+ /**
* Namespace for memory safety related features (e.g. MTE)
*
* @hide
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 4fbf4b528fe9..bc8822cfb3e9 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1153,8 +1153,8 @@ public abstract class WallpaperService extends Service {
mLayout.surfaceInsets.set(0, 0, 0, 0);
}
final int relayoutResult = mSession.relayout(mWindow, mLayout, mWidth, mHeight,
- View.VISIBLE, 0, mWinFrames, mMergedConfiguration, mSurfaceControl,
- mInsetsState, mTempControls, mSyncSeqIdBundle);
+ View.VISIBLE, 0, 0, 0, mWinFrames, mMergedConfiguration,
+ mSurfaceControl, mInsetsState, mTempControls, mSyncSeqIdBundle);
final int transformHint = SurfaceControl.rotationToBufferTransform(
(mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 3016473fa040..afcec6639dcf 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -75,41 +75,42 @@ interface IWindowSession {
* @param requestedWidth The width the window wants to be.
* @param requestedHeight The height the window wants to be.
* @param viewVisibility Window root view's visibility.
- * @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING},
- * {@link WindowManagerGlobal#RELAYOUT_DEFER_SURFACE_DESTROY}.
- * @param outFrame Rect in which is placed the new position/size on
- * screen.
- * @param outContentInsets Rect in which is placed the offsets from
- * <var>outFrame</var> in which the content of the window should be
- * placed. This can be used to modify the window layout to ensure its
- * contents are visible to the user, taking into account system windows
- * like the status bar or a soft keyboard.
- * @param outVisibleInsets Rect in which is placed the offsets from
- * <var>outFrame</var> in which the window is actually completely visible
- * to the user. This can be used to temporarily scroll the window's
- * contents to make sure the user can see it. This is different than
- * <var>outContentInsets</var> in that these insets change transiently,
- * so complex relayout of the window should not happen based on them.
- * @param outOutsets Rect in which is placed the dead area of the screen that we would like to
- * treat as real display. Example of such area is a chin in some models of wearable devices.
- * @param outBackdropFrame Rect which is used draw the resizing background during a resize
- * operation.
+ * @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING}.
+ * @param seq The calling sequence of {@link #relayout} and {@link #relayoutAsync}.
+ * @param lastSyncSeqId The last SyncSeqId that the client applied.
+ * @param outFrames The window frames used by the client side for layout.
* @param outMergedConfiguration New config container that holds global, override and merged
- * config for window, if it is now becoming visible and the merged configuration has changed
- * since it was last displayed.
- * @param outSurface Object in which is placed the new display surface.
+ * config for window, if it is now becoming visible and the merged
+ * config has changed since it was last displayed.
+ * @param outSurfaceControl Object in which is placed the new display surface.
* @param insetsState The current insets state in the system.
- *
- * @return int Result flags: {@link WindowManagerGlobal#RELAYOUT_SHOW_FOCUS},
- * {@link WindowManagerGlobal#RELAYOUT_FIRST_TIME}.
+ * @param activeControls Objects which allow controlling {@link InsetsSource}s.
+ * @param bundle A temporary object to obtain the latest SyncSeqId.
+ * @return int Result flags, defined in {@link WindowManagerGlobal}.
*/
int relayout(IWindow window, in WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility,
- int flags, out ClientWindowFrames outFrames,
+ int flags, int seq, int lastSyncSeqId, out ClientWindowFrames outFrames,
out MergedConfiguration outMergedConfiguration, out SurfaceControl outSurfaceControl,
out InsetsState insetsState, out InsetsSourceControl[] activeControls,
out Bundle bundle);
+ /**
+ * Similar to {@link #relayout} but this is an oneway method which doesn't return anything.
+ *
+ * @param window The window being modified.
+ * @param attrs If non-null, new attributes to apply to the window.
+ * @param requestedWidth The width the window wants to be.
+ * @param requestedHeight The height the window wants to be.
+ * @param viewVisibility Window root view's visibility.
+ * @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING}.
+ * @param seq The calling sequence of {@link #relayout} and {@link #relayoutAsync}.
+ * @param lastSyncSeqId The last SyncSeqId that the client applied.
+ */
+ oneway void relayoutAsync(IWindow window, in WindowManager.LayoutParams attrs,
+ int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq,
+ int lastSyncSeqId);
+
/*
* Notify the window manager that an application is relaunching and
* windows should be prepared for replacement.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f79f0d49959d..dd47bcdb2a63 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -75,6 +75,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
@@ -707,6 +708,8 @@ public final class ViewRootImpl implements ViewParent,
final Rect mPendingBackDropFrame = new Rect();
boolean mPendingAlwaysConsumeSystemBars;
+ private int mRelayoutSeq;
+ private final Rect mWinFrameInScreen = new Rect();
private final InsetsState mTempInsets = new InsetsState();
private final InsetsSourceControl[] mTempControls = new InsetsSourceControl[SIZE];
private final WindowConfiguration mTempWinConfig = new WindowConfiguration();
@@ -3364,20 +3367,6 @@ public final class ViewRootImpl implements ViewParent,
}
}
} else {
- // If a relayout isn't going to happen, we still need to check if this window can draw
- // when mCheckIfCanDraw is set. This is because it means we had a sync in the past, but
- // have not been told by WMS that the sync is complete and that we can continue to draw
- if (mCheckIfCanDraw) {
- try {
- cancelDraw = mWindowSession.cancelDraw(mWindow);
- cancelReason = "wm_sync";
- if (DEBUG_BLAST) {
- Log.d(mTag, "cancelDraw returned " + cancelDraw);
- }
- } catch (RemoteException e) {
- }
- }
-
// Not the first pass and no window/insets/visibility change but the window
// may have moved and we need check that and if so to update the left and right
// in the attach info. We translate only the window frame since on window move
@@ -3386,6 +3375,20 @@ public final class ViewRootImpl implements ViewParent,
maybeHandleWindowMove(frame);
}
+ if (!mRelayoutRequested && mCheckIfCanDraw) {
+ // We had a sync previously, but we didn't call IWindowSession#relayout in this
+ // traversal. So we don't know if the sync is complete that we can continue to draw.
+ // Here invokes cancelDraw to obtain the information.
+ try {
+ cancelDraw = mWindowSession.cancelDraw(mWindow);
+ cancelReason = "wm_sync";
+ if (DEBUG_BLAST) {
+ Log.d(mTag, "cancelDraw returned " + cancelDraw);
+ }
+ } catch (RemoteException e) {
+ }
+ }
+
if (surfaceSizeChanged || surfaceReplaced || surfaceCreated || windowAttributesChanged) {
// If the surface has been replaced, there's a chance the bounds layer is not parented
// to the new layer. When updating bounds layer, also reparent to the main VRI
@@ -8151,7 +8154,43 @@ public final class ViewRootImpl implements ViewParent,
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
- mRelayoutRequested = true;
+ final WindowConfiguration winConfigFromAm = getConfiguration().windowConfiguration;
+ final WindowConfiguration winConfigFromWm =
+ mLastReportedMergedConfiguration.getGlobalConfiguration().windowConfiguration;
+ final WindowConfiguration winConfig = getCompatWindowConfiguration();
+ final int measuredWidth = mView.getMeasuredWidth();
+ final int measuredHeight = mView.getMeasuredHeight();
+ final boolean relayoutAsync;
+ if (LOCAL_LAYOUT && !mFirst && viewVisibility == mViewVisibility
+ && mWindowAttributes.type != TYPE_APPLICATION_STARTING
+ && mSyncSeqId <= mLastSyncSeqId
+ && winConfigFromAm.diff(winConfigFromWm, false /* compareUndefined */) == 0) {
+ final InsetsState state = mInsetsController.getState();
+ final Rect displayCutoutSafe = mTempRect;
+ state.getDisplayCutoutSafe(displayCutoutSafe);
+ mWindowLayout.computeFrames(mWindowAttributes.forRotation(winConfig.getRotation()),
+ state, displayCutoutSafe, winConfig.getBounds(), winConfig.getWindowingMode(),
+ measuredWidth, measuredHeight, mInsetsController.getRequestedVisibilities(),
+ 1f /* compatScale */, mTmpFrames);
+ mWinFrameInScreen.set(mTmpFrames.frame);
+ if (mTranslator != null) {
+ mTranslator.translateRectInAppWindowToScreen(mWinFrameInScreen);
+ }
+
+ // If the position and the size of the frame are both changed, it will trigger a BLAST
+ // sync, and we still need to call relayout to obtain the syncSeqId. Otherwise, we just
+ // need to send attributes via relayoutAsync.
+ final Rect oldFrame = mWinFrame;
+ final Rect newFrame = mTmpFrames.frame;
+ final boolean positionChanged =
+ newFrame.top != oldFrame.top || newFrame.left != oldFrame.left;
+ final boolean sizeChanged =
+ newFrame.width() != oldFrame.width() || newFrame.height() != oldFrame.height();
+ relayoutAsync = !positionChanged || !sizeChanged;
+ } else {
+ relayoutAsync = false;
+ }
+
float appScale = mAttachInfo.mApplicationScale;
boolean restore = false;
if (params != null && mTranslator != null) {
@@ -8173,26 +8212,47 @@ public final class ViewRootImpl implements ViewParent,
}
}
- final int requestedWidth = (int) (mView.getMeasuredWidth() * appScale + 0.5f);
- final int requestedHeight = (int) (mView.getMeasuredHeight() * appScale + 0.5f);
+ final int requestedWidth = (int) (measuredWidth * appScale + 0.5f);
+ final int requestedHeight = (int) (measuredHeight * appScale + 0.5f);
+ int relayoutResult = 0;
+ mRelayoutSeq++;
+ if (relayoutAsync) {
+ mWindowSession.relayoutAsync(mWindow, params,
+ requestedWidth, requestedHeight, viewVisibility,
+ insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mRelayoutSeq,
+ mLastSyncSeqId);
+ } else {
+ relayoutResult = mWindowSession.relayout(mWindow, params,
+ requestedWidth, requestedHeight, viewVisibility,
+ insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mRelayoutSeq,
+ mLastSyncSeqId, mTmpFrames, mPendingMergedConfiguration, mSurfaceControl,
+ mTempInsets, mTempControls, mRelayoutBundle);
+ mRelayoutRequested = true;
+ final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid");
+ if (maybeSyncSeqId > 0) {
+ mSyncSeqId = maybeSyncSeqId;
+ }
+ mWinFrameInScreen.set(mTmpFrames.frame);
+ if (mTranslator != null) {
+ mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
+ mTranslator.translateRectInScreenToAppWindow(mTmpFrames.displayFrame);
+ mTranslator.translateRectInScreenToAppWindow(mTmpFrames.attachedFrame);
+ mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
+ mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
+ }
+ mInvSizeCompatScale = 1f / mTmpFrames.sizeCompatScale;
+ mInsetsController.onStateChanged(mTempInsets);
+ mInsetsController.onControlsChanged(mTempControls);
- int relayoutResult = mWindowSession.relayout(mWindow, params,
- requestedWidth, requestedHeight, viewVisibility,
- insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
- mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
- mTempControls, mRelayoutBundle);
- final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid");
- if (maybeSyncSeqId > 0) {
- mSyncSeqId = maybeSyncSeqId;
+ mPendingAlwaysConsumeSystemBars =
+ (relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
}
- mInvSizeCompatScale = 1f / mTmpFrames.sizeCompatScale;
final int transformHint = SurfaceControl.rotationToBufferTransform(
(mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
- final WindowConfiguration winConfig = getCompatWindowConfiguration();
WindowLayout.computeSurfaceSize(mWindowAttributes, winConfig.getMaxBounds(), requestedWidth,
- requestedHeight, mTmpFrames.frame, mPendingDragResizing, mSurfaceSize);
+ requestedHeight, mWinFrameInScreen, mPendingDragResizing, mSurfaceSize);
final boolean transformHintChanged = transformHint != mLastTransformHint;
final boolean sizeChanged = !mLastSurfaceSize.equals(mSurfaceSize);
@@ -8239,23 +8299,11 @@ public final class ViewRootImpl implements ViewParent,
destroySurface();
}
- mPendingAlwaysConsumeSystemBars =
- (relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
-
if (restore) {
params.restore();
}
- if (mTranslator != null) {
- mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
- mTranslator.translateRectInScreenToAppWindow(mTmpFrames.displayFrame);
- mTranslator.translateRectInScreenToAppWindow(mTmpFrames.attachedFrame);
- mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
- mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
- }
setFrame(mTmpFrames.frame);
- mInsetsController.onStateChanged(mTempInsets);
- mInsetsController.onControlsChanged(mTempControls);
return relayoutResult;
}
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index d55c838c3e53..1ec17d00e99a 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -286,10 +286,11 @@ public class WindowlessWindowManager implements IWindowSession {
@Override
public int relayout(IWindow window, WindowManager.LayoutParams inAttrs,
- int requestedWidth, int requestedHeight, int viewFlags, int flags,
- ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
- SurfaceControl outSurfaceControl, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls, Bundle outSyncSeqIdBundle) {
+ int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
+ int lastSyncSeqId, ClientWindowFrames outFrames,
+ MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl,
+ InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
+ Bundle outSyncSeqIdBundle) {
final State state;
synchronized (this) {
state = mStateForWindow.get(window.asBinder());
@@ -309,15 +310,23 @@ public class WindowlessWindowManager implements IWindowSession {
if (viewFlags == View.VISIBLE) {
t.setOpaque(sc, isOpaque(attrs)).show(sc).apply();
- outSurfaceControl.copyFrom(sc, "WindowlessWindowManager.relayout");
+ if (outSurfaceControl != null) {
+ outSurfaceControl.copyFrom(sc, "WindowlessWindowManager.relayout");
+ }
} else {
t.hide(sc).apply();
- outSurfaceControl.release();
+ if (outSurfaceControl != null) {
+ outSurfaceControl.release();
+ }
+ }
+ if (outFrames != null) {
+ outFrames.frame.set(0, 0, attrs.width, attrs.height);
+ outFrames.displayFrame.set(outFrames.frame);
}
- outFrames.frame.set(0, 0, attrs.width, attrs.height);
- outFrames.displayFrame.set(outFrames.frame);
- mergedConfiguration.setConfiguration(mConfiguration, mConfiguration);
+ if (outMergedConfiguration != null) {
+ outMergedConfiguration.setConfiguration(mConfiguration, mConfiguration);
+ }
if ((attrChanges & WindowManager.LayoutParams.FLAGS_CHANGED) != 0
&& state.mInputChannelToken != null) {
@@ -335,7 +344,7 @@ public class WindowlessWindowManager implements IWindowSession {
}
}
- if (mInsetsState != null) {
+ if (outInsetsState != null && mInsetsState != null) {
outInsetsState.set(mInsetsState);
}
@@ -343,6 +352,16 @@ public class WindowlessWindowManager implements IWindowSession {
}
@Override
+ public void relayoutAsync(IWindow window, WindowManager.LayoutParams inAttrs,
+ int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
+ int lastSyncSeqId) {
+ relayout(window, inAttrs, requestedWidth, requestedHeight, viewFlags, flags, seq,
+ lastSyncSeqId, null /* outFrames */, null /* outMergedConfiguration */,
+ null /* outSurfaceControl */, null /* outInsetsState */,
+ null /* outActiveControls */, null /* outSyncSeqIdBundle */);
+ }
+
+ @Override
public void prepareToReplaceWindows(android.os.IBinder appToken, boolean childrenOnly) {
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 0c03d109f649..a01decca5224 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -18,7 +18,6 @@ package android.view.inputmethod;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
-import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS;
import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_IMMEDIATE;
import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_MONITOR;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.DISPLAY_ID;
@@ -2086,15 +2085,6 @@ public final class InputMethodManager {
Log.w(TAG, "Ignoring startStylusHandwriting: View's window does not have focus.");
return;
}
- if (mServedInputConnection != null && getDelegate().hasActiveConnection(view)) {
- // TODO (b/210039666): optimize CURSOR_UPDATE_IMMEDIATE.
- // TODO (b/210039666): Pipe IME displayId from InputBindResult and use it here.
- // instead of mDisplayId.
- mServedInputConnection.requestCursorUpdatesFromImm(
- CURSOR_UPDATE_IMMEDIATE | CURSOR_UPDATE_MONITOR,
- CURSOR_UPDATE_FILTER_EDITOR_BOUNDS,
- mDisplayId);
- }
mServiceInvoker.startStylusHandwriting(mClient);
// TODO(b/210039666): do we need any extra work for supporting non-native
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index b233e5453c05..b21c5b35e24b 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -226,6 +226,8 @@ public class Editor {
final UndoInputFilter mUndoInputFilter = new UndoInputFilter(this);
boolean mAllowUndo = true;
+ private int mLastToolType = MotionEvent.TOOL_TYPE_UNKNOWN;
+
private final MetricsLogger mMetricsLogger = new MetricsLogger();
// Cursor Controllers.
@@ -1732,6 +1734,9 @@ public class Editor {
@VisibleForTesting
public void onTouchEvent(MotionEvent event) {
final boolean filterOutEvent = shouldFilterOutTouchEvent(event);
+
+ mLastToolType = event.getToolType(event.getActionIndex());
+
mLastButtonState = event.getButtonState();
if (filterOutEvent) {
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
@@ -1784,7 +1789,7 @@ public class Editor {
}
private void showFloatingToolbar() {
- if (mTextActionMode != null) {
+ if (mTextActionMode != null && showUIForFingerInput()) {
// Delay "show" so it doesn't interfere with click confirmations
// or double-clicks that could "dismiss" the floating toolbar.
int delay = ViewConfiguration.getDoubleTapTimeout();
@@ -1864,7 +1869,8 @@ public class Editor {
final CursorController cursorController = mTextView.hasSelection()
? getSelectionController() : getInsertionController();
if (cursorController != null && !cursorController.isActive()
- && !cursorController.isCursorBeingModified()) {
+ && !cursorController.isCursorBeingModified()
+ && showUIForFingerInput()) {
cursorController.show();
}
}
@@ -2515,6 +2521,10 @@ public class Editor {
return false;
}
+ if (!showUIForFingerInput()) {
+ return false;
+ }
+
ActionMode.Callback actionModeCallback = new TextActionModeCallback(actionMode);
mTextActionMode = mTextView.startActionMode(actionModeCallback, ActionMode.TYPE_FLOATING);
registerOnBackInvokedCallback();
@@ -2667,7 +2677,7 @@ public class Editor {
mTextView.postDelayed(mShowSuggestionRunnable,
ViewConfiguration.getDoubleTapTimeout());
} else if (hasInsertionController()) {
- if (shouldInsertCursor) {
+ if (shouldInsertCursor && showUIForFingerInput()) {
getInsertionController().show();
} else {
getInsertionController().hide();
@@ -5397,7 +5407,8 @@ public class Editor {
final PointF showPosInView = new PointF();
final boolean shouldShow = checkForTransforms() /*check not rotated and compute scale*/
&& !tooLargeTextForMagnifier()
- && obtainMagnifierShowCoordinates(event, showPosInView);
+ && obtainMagnifierShowCoordinates(event, showPosInView)
+ && showUIForFingerInput();
if (shouldShow) {
// Make the cursor visible and stop blinking.
mRenderCursorRegardlessTiming = true;
@@ -6343,6 +6354,15 @@ public class Editor {
}
}
+ /**
+ * Returns true when need to show UIs, e.g. floating toolbar, etc, for finger based interaction.
+ *
+ * @return true if UIs need to show for finger interaciton. false if UIs are not necessary.
+ */
+ public boolean showUIForFingerInput() {
+ return mLastToolType != MotionEvent.TOOL_TYPE_MOUSE;
+ }
+
/** Controller for the insertion cursor. */
@VisibleForTesting
public class InsertionPointCursorController implements CursorController {
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index a0ec48bc6beb..54a415cfa01b 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -301,7 +301,11 @@ public final class SelectionActionModeHelper {
final SelectionModifierCursorController controller = mEditor.getSelectionController();
if (controller != null
&& (mTextView.isTextSelectable() || mTextView.isTextEditable())) {
- controller.show();
+ if (mEditor.showUIForFingerInput()) {
+ controller.show();
+ } else {
+ controller.hide();
+ }
}
if (result != null) {
switch (actionMode) {
diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
index 2bef10f1aee5..b63ce1b22cdb 100644
--- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
@@ -968,27 +968,6 @@ public final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub
});
}
- /**
- * Dispatches {@link InputConnection#requestCursorUpdates(int)}.
- *
- * <p>This method is intended to be called only from {@link InputMethodManager}.</p>
- * @param cursorUpdateMode the mode for {@link InputConnection#requestCursorUpdates(int, int)}
- * @param cursorUpdateFilter the filter for
- * {@link InputConnection#requestCursorUpdates(int, int)}
- * @param imeDisplayId displayId on which IME is displayed.
- */
- @Dispatching(cancellable = true)
- public void requestCursorUpdatesFromImm(int cursorUpdateMode, int cursorUpdateFilter,
- int imeDisplayId) {
- final int currentSessionId = mCurrentSessionId.get();
- dispatchWithTracing("requestCursorUpdatesFromImm", () -> {
- if (currentSessionId != mCurrentSessionId.get()) {
- return; // cancelled
- }
- requestCursorUpdatesInternal(cursorUpdateMode, cursorUpdateFilter, imeDisplayId);
- });
- }
-
@Dispatching(cancellable = true)
@Override
public void requestCursorUpdates(InputConnectionCommandHeader header, int cursorUpdateMode,
diff --git a/core/res/res/layout/side_fps_toast.xml b/core/res/res/layout/side_fps_toast.xml
index 58b8cc9438ae..96860b050393 100644
--- a/core/res/res/layout/side_fps_toast.xml
+++ b/core/res/res/layout/side_fps_toast.xml
@@ -20,13 +20,14 @@
android:layout_height="wrap_content"
android:minWidth="350dp"
android:layout_gravity="center"
- android:theme="?attr/alertDialogTheme">
+ android:background="@color/side_fps_toast_background">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/fp_power_button_enrollment_title"
android:singleLine="true"
android:ellipsize="end"
+ android:textColor="@color/side_fps_text_color"
android:paddingLeft="20dp"/>
<Space
android:layout_width="wrap_content"
@@ -39,5 +40,6 @@
android:text="@string/fp_power_button_enrollment_button_text"
android:paddingRight="20dp"
style="?android:attr/buttonBarNegativeButtonStyle"
+ android:textColor="@color/side_fps_button_color"
android:maxLines="1"/>
</LinearLayout> \ No newline at end of file
diff --git a/core/res/res/values-night/colors.xml b/core/res/res/values-night/colors.xml
index e2db49cfa657..88171ce368f9 100644
--- a/core/res/res/values-night/colors.xml
+++ b/core/res/res/values-night/colors.xml
@@ -41,4 +41,9 @@
<!-- Lily Language Picker language item view colors -->
<color name="language_picker_item_text_color">#F1F3F4</color>
<color name="language_picker_item_text_color_secondary">#BDC1C6</color>
+
+ <!-- Color for side fps toast dark theme-->
+ <color name="side_fps_toast_background">#2E3132</color>
+ <color name="side_fps_text_color">#EFF1F2</color>
+ <color name="side_fps_button_color">#33B9DB</color>
</resources>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index faf48f3c3fdc..ac083277b787 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -454,4 +454,9 @@
<!-- Lily Language Picker language item view colors -->
<color name="language_picker_item_text_color">#202124</color>
<color name="language_picker_item_text_color_secondary">#5F6368</color>
+
+ <!-- Color for side fps toast light theme -->
+ <color name="side_fps_toast_background">#F7F9FA</color>
+ <color name="side_fps_text_color">#191C1D</color>
+ <color name="side_fps_button_color">#00677E</color>
</resources>
diff --git a/libs/WindowManager/Shell/res/animator/tv_pip_menu_action_button_animator.xml b/libs/WindowManager/Shell/res/animator/tv_window_menu_action_button_animator.xml
index 7475abac4695..7475abac4695 100644
--- a/libs/WindowManager/Shell/res/animator/tv_pip_menu_action_button_animator.xml
+++ b/libs/WindowManager/Shell/res/animator/tv_window_menu_action_button_animator.xml
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml b/libs/WindowManager/Shell/res/color/tv_window_menu_close_icon.xml
index ce8640df0093..67467bbc72ae 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml
+++ b/libs/WindowManager/Shell/res/color/tv_window_menu_close_icon.xml
@@ -15,5 +15,5 @@
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@color/tv_pip_menu_icon_unfocused" />
+ <item android:color="@color/tv_window_menu_icon_unfocused" />
</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml b/libs/WindowManager/Shell/res/color/tv_window_menu_close_icon_bg.xml
index 4f5e63dac5c0..4182bfeefa1b 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml
+++ b/libs/WindowManager/Shell/res/color/tv_window_menu_close_icon_bg.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ Copyright (C) 2022 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.
@@ -16,6 +16,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true"
- android:color="@color/tv_pip_menu_icon_bg_focused" />
- <item android:color="@color/tv_pip_menu_icon_bg_unfocused" />
+ android:color="@color/tv_window_menu_close_icon_bg_focused" />
+ <item android:color="@color/tv_window_menu_close_icon_bg_unfocused" />
</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml b/libs/WindowManager/Shell/res/color/tv_window_menu_icon.xml
index 275870450493..45205d2a7138 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml
+++ b/libs/WindowManager/Shell/res/color/tv_window_menu_icon.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ Copyright (C) 2022 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.
@@ -16,8 +16,8 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true"
- android:color="@color/tv_pip_menu_icon_focused" />
+ android:color="@color/tv_window_menu_icon_focused" />
<item android:state_enabled="false"
- android:color="@color/tv_pip_menu_icon_disabled" />
- <item android:color="@color/tv_pip_menu_icon_unfocused" />
+ android:color="@color/tv_window_menu_icon_disabled" />
+ <item android:color="@color/tv_window_menu_icon_unfocused" />
</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml b/libs/WindowManager/Shell/res/color/tv_window_menu_icon_bg.xml
index 6cbf66f00df7..1bd26e1d6583 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml
+++ b/libs/WindowManager/Shell/res/color/tv_window_menu_icon_bg.xml
@@ -16,6 +16,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true"
- android:color="@color/tv_pip_menu_close_icon_bg_focused" />
- <item android:color="@color/tv_pip_menu_close_icon_bg_unfocused" />
+ android:color="@color/tv_window_menu_icon_bg_focused" />
+ <item android:color="@color/tv_window_menu_icon_bg_unfocused" />
</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml b/libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml
deleted file mode 100644
index 1938f4562e97..000000000000
--- a/libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2020 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.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <corners android:radius="@dimen/pip_menu_button_radius" />
- <solid android:color="@color/tv_pip_menu_icon_bg" />
-</shape> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
index 846fdb3e8a58..7085a2c72c86 100644
--- a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
+++ b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android"
- android:exitFadeDuration="@integer/pip_menu_fade_animation_duration">
+ android:exitFadeDuration="@integer/tv_window_menu_fade_animation_duration">
<item android:state_activated="true">
<shape android:shape="rectangle">
<corners android:radius="@dimen/pip_menu_border_corner_radius" />
diff --git a/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml b/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml
new file mode 100644
index 000000000000..2dba37daf059
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/tv_window_menu_button_radius" />
+ <solid android:color="@color/tv_window_menu_icon_bg" />
+</shape> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index 2d50d3f1392d..8533a5994d33 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -64,14 +64,14 @@
android:layout_width="@dimen/pip_menu_button_wrapper_margin"
android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
- <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ <com.android.wm.shell.common.TvWindowMenuActionButton
android:id="@+id/tv_pip_menu_fullscreen_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/pip_ic_fullscreen_white"
android:text="@string/pip_fullscreen" />
- <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ <com.android.wm.shell.common.TvWindowMenuActionButton
android:id="@+id/tv_pip_menu_close_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -80,14 +80,14 @@
<!-- More TvPipMenuActionButtons may be added here at runtime. -->
- <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ <com.android.wm.shell.common.TvWindowMenuActionButton
android:id="@+id/tv_pip_menu_move_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/pip_ic_move_white"
android:text="@string/pip_move" />
- <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ <com.android.wm.shell.common.TvWindowMenuActionButton
android:id="@+id/tv_pip_menu_expand_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -145,7 +145,7 @@
android:layout_margin="@dimen/pip_menu_outer_space_frame"
android:background="@drawable/tv_pip_menu_border"/>
- <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ <com.android.wm.shell.common.TvWindowMenuActionButton
android:id="@+id/tv_pip_menu_done_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml
deleted file mode 100644
index db96d8de4094..000000000000
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2020 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.
--->
-<!-- Layout for TvPipMenuActionButton -->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/button"
- android:layout_width="@dimen/pip_menu_button_size"
- android:layout_height="@dimen/pip_menu_button_size"
- android:padding="@dimen/pip_menu_button_margin"
- android:stateListAnimator="@animator/tv_pip_menu_action_button_animator"
- android:focusable="true">
-
- <View android:id="@+id/background"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:duplicateParentState="true"
- android:background="@drawable/tv_pip_button_bg"/>
-
- <ImageView android:id="@+id/icon"
- android:layout_width="@dimen/pip_menu_icon_size"
- android:layout_height="@dimen/pip_menu_icon_size"
- android:layout_gravity="center"
- android:duplicateParentState="true"
- android:tint="@color/tv_pip_menu_icon" />
-</FrameLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
new file mode 100644
index 000000000000..c4dbd39c729a
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+<!-- Layout for TvWindowMenuActionButton -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/button"
+ android:layout_width="@dimen/tv_window_menu_button_size"
+ android:layout_height="@dimen/tv_window_menu_button_size"
+ android:padding="@dimen/tv_window_menu_button_margin"
+ android:stateListAnimator="@animator/tv_window_menu_action_button_animator"
+ android:focusable="true">
+
+ <View android:id="@+id/background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:duplicateParentState="true"
+ android:background="@drawable/tv_window_button_bg"/>
+
+ <ImageView android:id="@+id/icon"
+ android:layout_width="@dimen/tv_window_menu_icon_size"
+ android:layout_height="@dimen/tv_window_menu_icon_size"
+ android:layout_gravity="center"
+ android:duplicateParentState="true"
+ android:tint="@color/tv_window_menu_icon" />
+</FrameLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index 02e726fbc3bf..b45b9ec0c457 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -15,20 +15,20 @@
limitations under the License.
-->
<resources>
- <!-- The dimensions to user for picture-in-picture action buttons. -->
- <dimen name="pip_menu_button_size">48dp</dimen>
- <dimen name="pip_menu_button_radius">20dp</dimen>
- <dimen name="pip_menu_icon_size">20dp</dimen>
- <dimen name="pip_menu_button_margin">4dp</dimen>
- <dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
- <dimen name="pip_menu_border_width">4dp</dimen>
- <integer name="pip_menu_fade_animation_duration">500</integer>
+ <!-- The dimensions to use for tv window menu action buttons. -->
+ <dimen name="tv_window_menu_button_size">48dp</dimen>
+ <dimen name="tv_window_menu_button_radius">20dp</dimen>
+ <dimen name="tv_window_menu_icon_size">20dp</dimen>
+ <dimen name="tv_window_menu_button_margin">4dp</dimen>
+ <integer name="tv_window_menu_fade_animation_duration">500</integer>
<!-- The pip menu front border corner radius is 2dp smaller than
the background corner radius to hide the background from
showing through. -->
<dimen name="pip_menu_border_corner_radius">4dp</dimen>
<dimen name="pip_menu_background_corner_radius">6dp</dimen>
+ <dimen name="pip_menu_border_width">4dp</dimen>
<dimen name="pip_menu_outer_space">24dp</dimen>
+ <dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
<!-- outer space minus border width -->
<dimen name="pip_menu_outer_space_frame">20dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/colors_tv.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml
index fa90fe36b545..3e71c1010278 100644
--- a/libs/WindowManager/Shell/res/values/colors_tv.xml
+++ b/libs/WindowManager/Shell/res/values/colors_tv.xml
@@ -15,13 +15,15 @@
~ limitations under the License.
-->
<resources>
- <color name="tv_pip_menu_icon_focused">#0E0E0F</color>
- <color name="tv_pip_menu_icon_unfocused">#F8F9FA</color>
- <color name="tv_pip_menu_icon_disabled">#80868B</color>
- <color name="tv_pip_menu_close_icon_bg_focused">#D93025</color>
- <color name="tv_pip_menu_close_icon_bg_unfocused">#D69F261F</color>
- <color name="tv_pip_menu_icon_bg_focused">#E8EAED</color>
- <color name="tv_pip_menu_icon_bg_unfocused">#990E0E0F</color>
+ <color name="tv_window_menu_icon_focused">#0E0E0F</color>
+ <color name="tv_window_menu_icon_unfocused">#F8F9FA</color>
+
+ <color name="tv_window_menu_icon_disabled">#80868B</color>
+ <color name="tv_window_menu_close_icon_bg_focused">#D93025</color>
+ <color name="tv_window_menu_close_icon_bg_unfocused">#D69F261F</color>
+ <color name="tv_window_menu_icon_bg_focused">#E8EAED</color>
+ <color name="tv_window_menu_icon_bg_unfocused">#990E0E0F</color>
+
<color name="tv_pip_menu_focus_border">#E8EAED</color>
<color name="tv_pip_menu_background">#1E232C</color>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
index a09aab666a31..572e3335eb11 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip.tv;
+package com.android.wm.shell.common;
import android.content.Context;
import android.content.res.TypedArray;
@@ -28,33 +28,32 @@ import android.widget.RelativeLayout;
import com.android.wm.shell.R;
/**
- * A View that represents Pip Menu action button, such as "Fullscreen" and "Close" as well custom
- * (provided by the application in Pip) and media buttons.
+ * A common action button for TV window menu layouts.
*/
-public class TvPipMenuActionButton extends RelativeLayout implements View.OnClickListener {
+public class TvWindowMenuActionButton extends RelativeLayout implements View.OnClickListener {
private final ImageView mIconImageView;
private final View mButtonBackgroundView;
private final View mButtonView;
private OnClickListener mOnClickListener;
- public TvPipMenuActionButton(Context context) {
+ public TvWindowMenuActionButton(Context context) {
this(context, null, 0, 0);
}
- public TvPipMenuActionButton(Context context, AttributeSet attrs) {
+ public TvWindowMenuActionButton(Context context, AttributeSet attrs) {
this(context, attrs, 0, 0);
}
- public TvPipMenuActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ public TvWindowMenuActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
- public TvPipMenuActionButton(
+ public TvWindowMenuActionButton(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
final LayoutInflater inflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- inflater.inflate(R.layout.tv_pip_menu_action_button, this);
+ inflater.inflate(R.layout.tv_window_menu_action_button, this);
mIconImageView = findViewById(R.id.icon);
mButtonView = findViewById(R.id.button);
@@ -129,20 +128,27 @@ public class TvPipMenuActionButton extends RelativeLayout implements View.OnClic
return mButtonView.isEnabled();
}
- void setIsCustomCloseAction(boolean isCustomCloseAction) {
+ /**
+ * Marks this button as a custom close action button.
+ * This changes the style of the action button to highlight that this action finishes the
+ * Picture-in-Picture activity.
+ *
+ * @param isCustomCloseAction sets or unsets this button as a custom close action button.
+ */
+ public void setIsCustomCloseAction(boolean isCustomCloseAction) {
mIconImageView.setImageTintList(
getResources().getColorStateList(
- isCustomCloseAction ? R.color.tv_pip_menu_close_icon
- : R.color.tv_pip_menu_icon));
+ isCustomCloseAction ? R.color.tv_window_menu_close_icon
+ : R.color.tv_window_menu_icon));
mButtonBackgroundView.setBackgroundTintList(getResources()
- .getColorStateList(isCustomCloseAction ? R.color.tv_pip_menu_close_icon_bg
- : R.color.tv_pip_menu_icon_bg));
+ .getColorStateList(isCustomCloseAction ? R.color.tv_window_menu_close_icon_bg
+ : R.color.tv_window_menu_icon_bg));
}
@Override
public String toString() {
if (mButtonView.getContentDescription() == null) {
- return TvPipMenuActionButton.class.getSimpleName();
+ return TvWindowMenuActionButton.class.getSimpleName();
}
return mButtonView.getContentDescription().toString();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 57d3a44ed2af..4d7c8465bcc2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -56,6 +56,7 @@ import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
+import com.android.wm.shell.common.TvWindowMenuActionButton;
import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -79,7 +80,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
private final LinearLayout mActionButtonsContainer;
private final View mMenuFrameView;
- private final List<TvPipMenuActionButton> mAdditionalButtons = new ArrayList<>();
+ private final List<TvWindowMenuActionButton> mAdditionalButtons = new ArrayList<>();
private final View mPipFrameView;
private final View mPipView;
private final TextView mEduTextView;
@@ -94,7 +95,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
private final ImageView mArrowRight;
private final ImageView mArrowDown;
private final ImageView mArrowLeft;
- private final TvPipMenuActionButton mA11yDoneButton;
+ private final TvWindowMenuActionButton mA11yDoneButton;
private final ScrollView mScrollView;
private final HorizontalScrollView mHorizontalScrollView;
@@ -104,8 +105,8 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
private boolean mMoveMenuIsVisible;
private boolean mButtonMenuIsVisible;
- private final TvPipMenuActionButton mExpandButton;
- private final TvPipMenuActionButton mCloseButton;
+ private final TvWindowMenuActionButton mExpandButton;
+ private final TvWindowMenuActionButton mCloseButton;
private boolean mSwitchingOrientation;
@@ -166,7 +167,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
mResizeAnimationDuration = context.getResources().getInteger(
R.integer.config_pipResizeAnimationDuration);
mPipMenuFadeAnimationDuration = context.getResources()
- .getInteger(R.integer.pip_menu_fade_animation_duration);
+ .getInteger(R.integer.tv_window_menu_fade_animation_duration);
mPipMenuOuterSpace = context.getResources()
.getDimensionPixelSize(R.dimen.pip_menu_outer_space);
@@ -568,7 +569,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
if (actionsNumber > buttonsNumber) {
// Add buttons until we have enough to display all the actions.
while (actionsNumber > buttonsNumber) {
- TvPipMenuActionButton button = new TvPipMenuActionButton(mContext);
+ TvWindowMenuActionButton button = new TvWindowMenuActionButton(mContext);
button.setOnClickListener(this);
mActionButtonsContainer.addView(button,
@@ -591,7 +592,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
// "Assign" actions to the buttons.
for (int index = 0; index < actionsNumber; index++) {
final RemoteAction action = actions.get(index);
- final TvPipMenuActionButton button = mAdditionalButtons.get(index);
+ final TvWindowMenuActionButton button = mAdditionalButtons.get(index);
// Remove action if it matches the custom close action.
if (PipUtils.remoteActionsMatch(action, closeAction)) {
@@ -607,7 +608,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
}
}
- private void setActionForButton(RemoteAction action, TvPipMenuActionButton button,
+ private void setActionForButton(RemoteAction action, TvWindowMenuActionButton button,
Handler mainHandler) {
button.setVisibility(View.VISIBLE); // Ensure the button is visible.
if (action.getContentDescription().length() > 0) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index b70bde3a64ee..7b498e4f54ec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -246,7 +246,7 @@ public class TaskSnapshotWindow {
window.setOuter(snapshotSurface);
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
- session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0,
+ session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, 0, 0,
tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState,
tmpControls, new Bundle());
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
index 1c587a2078cf..0a54b8c016fb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
@@ -49,7 +49,10 @@ abstract class BaseTest @JvmOverloads constructor(
) {
init {
testSpec.setIsTablet(
- WindowManagerStateHelper(instrumentation).currentState.wmState.isTablet
+ WindowManagerStateHelper(
+ instrumentation,
+ clearCacheAfterParsing = false
+ ).currentState.wmState.isTablet
)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
new file mode 100644
index 000000000000..da954d97aec2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.appWindowBecomesVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test quick switch to split pair from another app.
+ *
+ * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromAnotherApp`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+ val thirdApp = SplitScreenHelper.getNonResizeable(instrumentation)
+
+ // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
+ @Before
+ open fun before() {
+ Assume.assumeTrue(tapl.isTablet)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ eachRun {
+ primaryApp.launchViaIntent(wmHelper)
+ // TODO(b/231399940): Use recent shortcut to enter split.
+ tapl.launchedAppState.taskbar
+ .openAllApps()
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+
+ thirdApp.launchViaIntent(wmHelper)
+ wmHelper.StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withWindowSurfaceAppeared(thirdApp)
+ .waitForAndVerify()
+ }
+ }
+ transitions {
+ tapl.launchedAppState.quickSwitchToPreviousApp()
+ SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+
+ @Presubmit
+ @Test
+ fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp, splitLeftTop = false)
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ secondaryApp, splitLeftTop = true)
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun entireScreenCovered() =
+ super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() =
+ super.navBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() =
+ super.navBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() =
+ super.navBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() =
+ super.statusBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() =
+ super.statusBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() =
+ super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() =
+ super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes =
+ listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index f69eb8f06182..db89ff52178b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -26,10 +26,8 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
import org.junit.Assume
@@ -41,7 +39,7 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test switch back to split pair after go home
+ * Test quick switch to split pair from home.
*
* To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromHome`
*/
@@ -59,30 +57,30 @@ class SwitchBackToSplitFromHome(testSpec: FlickerTestParameter) : SplitScreenBas
}
override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- setup {
- eachRun {
- primaryApp.launchViaIntent(wmHelper)
- // TODO(b/231399940): Use recent shortcut to enter split.
- tapl.launchedAppState.taskbar
- .openAllApps()
- .getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ get() = {
+ super.transition(this)
+ setup {
+ eachRun {
+ primaryApp.launchViaIntent(wmHelper)
+ // TODO(b/231399940): Use recent shortcut to enter split.
+ tapl.launchedAppState.taskbar
+ .openAllApps()
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+
+ tapl.goHome()
+ wmHelper.StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
+ }
+ }
+ transitions {
+ tapl.workspace.quickSwitchToPreviousApp()
SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-
- tapl.goHome()
- wmHelper.StateSyncBuilder()
- .withAppTransitionIdle()
- .withHomeActivityVisible()
- .waitForAndVerify()
}
}
- transitions {
- tapl.workspace.quickSwitchToPreviousApp()
- SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
- }
- }
@Presubmit
@Test
@@ -90,7 +88,7 @@ class SwitchBackToSplitFromHome(testSpec: FlickerTestParameter) : SplitScreenBas
@Presubmit
@Test
- fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+ fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
@Presubmit
@Test
@@ -108,7 +106,7 @@ class SwitchBackToSplitFromHome(testSpec: FlickerTestParameter) : SplitScreenBas
@Presubmit
@Test
- fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+ fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
new file mode 100644
index 000000000000..c23cdb610671
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.appWindowBecomesVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test switch back to split pair from recent.
+ *
+ * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromRecent`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+
+ // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
+ @Before
+ open fun before() {
+ Assume.assumeTrue(tapl.isTablet)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ eachRun {
+ primaryApp.launchViaIntent(wmHelper)
+ // TODO(b/231399940): Use recent shortcut to enter split.
+ tapl.launchedAppState.taskbar
+ .openAllApps()
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+
+ tapl.goHome()
+ wmHelper.StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
+ }
+ }
+ transitions {
+ tapl.workspace.switchToOverview()
+ .currentTask
+ .open()
+ SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+
+ @Presubmit
+ @Test
+ fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp, splitLeftTop = false)
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ secondaryApp, splitLeftTop = true)
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun entireScreenCovered() =
+ super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() =
+ super.navBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() =
+ super.navBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() =
+ super.navBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() =
+ super.statusBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() =
+ super.statusBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() =
+ super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() =
+ super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes =
+ listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+ }
+ }
+}
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 2aca41e41905..62e42b8e1863 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -112,7 +112,7 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw(
if (CC_UNLIKELY(Properties::showDirtyRegions ||
ProfileType::None != Properties::getProfileType())) {
SkCanvas* profileCanvas = surface->getCanvas();
- SkiaProfileRenderer profileRenderer(profileCanvas);
+ SkiaProfileRenderer profileRenderer(profileCanvas, frame.width(), frame.height());
profiler->draw(profileRenderer);
}
diff --git a/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp b/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp
index 492c39f1288c..81cfc5d93419 100644
--- a/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp
+++ b/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp
@@ -33,13 +33,5 @@ void SkiaProfileRenderer::drawRects(const float* rects, int count, const SkPaint
}
}
-uint32_t SkiaProfileRenderer::getViewportWidth() {
- return mCanvas->imageInfo().width();
-}
-
-uint32_t SkiaProfileRenderer::getViewportHeight() {
- return mCanvas->imageInfo().height();
-}
-
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaProfileRenderer.h b/libs/hwui/pipeline/skia/SkiaProfileRenderer.h
index dc8420f4e01b..96d2a5e58139 100644
--- a/libs/hwui/pipeline/skia/SkiaProfileRenderer.h
+++ b/libs/hwui/pipeline/skia/SkiaProfileRenderer.h
@@ -23,18 +23,21 @@ namespace uirenderer {
class SkiaProfileRenderer : public IProfileRenderer {
public:
- explicit SkiaProfileRenderer(SkCanvas* canvas) : mCanvas(canvas) {}
+ explicit SkiaProfileRenderer(SkCanvas* canvas, uint32_t width, uint32_t height)
+ : mCanvas(canvas), mWidth(width), mHeight(height) {}
void drawRect(float left, float top, float right, float bottom, const SkPaint& paint) override;
void drawRects(const float* rects, int count, const SkPaint& paint) override;
- uint32_t getViewportWidth() override;
- uint32_t getViewportHeight() override;
+ uint32_t getViewportWidth() override { return mWidth; }
+ uint32_t getViewportHeight() override { return mHeight; }
virtual ~SkiaProfileRenderer() {}
private:
// Does not have ownership.
SkCanvas* mCanvas;
+ uint32_t mWidth;
+ uint32_t mHeight;
};
} /* namespace uirenderer */
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index 905d46e58014..53a4c60cf8a8 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -88,7 +88,9 @@ IRenderPipeline::DrawResult SkiaVulkanPipeline::draw(
if (CC_UNLIKELY(Properties::showDirtyRegions ||
ProfileType::None != Properties::getProfileType())) {
SkCanvas* profileCanvas = backBuffer->getCanvas();
- SkiaProfileRenderer profileRenderer(profileCanvas);
+ SkAutoCanvasRestore saver(profileCanvas, true);
+ profileCanvas->concat(mVkSurface->getCurrentPreTransform());
+ SkiaProfileRenderer profileRenderer(profileCanvas, frame.width(), frame.height());
profiler->draw(profileRenderer);
}
diff --git a/media/java/android/media/IMediaRouter2.aidl b/media/java/android/media/IMediaRouter2.aidl
index fe15f0e67b1d..29bfd1acae17 100644
--- a/media/java/android/media/IMediaRouter2.aidl
+++ b/media/java/android/media/IMediaRouter2.aidl
@@ -26,9 +26,7 @@ import android.os.Bundle;
oneway interface IMediaRouter2 {
void notifyRouterRegistered(in List<MediaRoute2Info> currentRoutes,
in RoutingSessionInfo currentSystemSessionInfo);
- void notifyRoutesAdded(in List<MediaRoute2Info> routes);
- void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
- void notifyRoutesChanged(in List<MediaRoute2Info> routes);
+ void notifyRoutesUpdated(in List<MediaRoute2Info> routes);
void notifySessionCreated(int requestId, in @nullable RoutingSessionInfo sessionInfo);
void notifySessionInfoChanged(in RoutingSessionInfo sessionInfo);
void notifySessionReleased(in RoutingSessionInfo sessionInfo);
diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl
index 71dc2a781ba9..9f3c3ff89032 100644
--- a/media/java/android/media/IMediaRouter2Manager.aidl
+++ b/media/java/android/media/IMediaRouter2Manager.aidl
@@ -30,8 +30,6 @@ oneway interface IMediaRouter2Manager {
void notifySessionReleased(in RoutingSessionInfo session);
void notifyDiscoveryPreferenceChanged(String packageName,
in RouteDiscoveryPreference discoveryPreference);
- void notifyRoutesAdded(in List<MediaRoute2Info> routes);
- void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
- void notifyRoutesChanged(in List<MediaRoute2Info> routes);
+ void notifyRoutesUpdated(in List<MediaRoute2Info> routes);
void notifyRequestFailed(int requestId, int reason);
}
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index a7a21e7a2013..26cb9f8e9ee1 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -132,7 +132,7 @@ public final class MediaRouter2 {
/**
* Stores an auxiliary copy of {@link #mFilteredRoutes} at the time of the last route callback
* dispatch. This is only used to determine what callback a route should be assigned to (added,
- * removed, changed) in {@link #dispatchFilteredRoutesChangedLocked(List)}.
+ * removed, changed) in {@link #dispatchFilteredRoutesUpdatedOnHandler(List)}.
*/
private volatile ArrayMap<String, MediaRoute2Info> mPreviousRoutes = new ArrayMap<>();
@@ -820,7 +820,7 @@ public final class MediaRouter2 {
}
}
- void dispatchFilteredRoutesChangedLocked(List<MediaRoute2Info> newRoutes) {
+ void dispatchFilteredRoutesUpdatedOnHandler(List<MediaRoute2Info> newRoutes) {
List<MediaRoute2Info> addedRoutes = new ArrayList<>();
List<MediaRoute2Info> removedRoutes = new ArrayList<>();
List<MediaRoute2Info> changedRoutes = new ArrayList<>();
@@ -863,29 +863,16 @@ public final class MediaRouter2 {
if (!changedRoutes.isEmpty()) {
notifyRoutesChanged(changedRoutes);
}
- }
- void addRoutesOnHandler(List<MediaRoute2Info> routes) {
- synchronized (mLock) {
- for (MediaRoute2Info route : routes) {
- mRoutes.put(route.getId(), route);
- }
- updateFilteredRoutesLocked();
+ // Note: We don't notify clients of changes in route ordering.
+ if (!addedRoutes.isEmpty() || !removedRoutes.isEmpty() || !changedRoutes.isEmpty()) {
+ notifyRoutesUpdated(newRoutes);
}
}
- void removeRoutesOnHandler(List<MediaRoute2Info> routes) {
- synchronized (mLock) {
- for (MediaRoute2Info route : routes) {
- mRoutes.remove(route.getId());
- }
- updateFilteredRoutesLocked();
- }
- }
-
- void changeRoutesOnHandler(List<MediaRoute2Info> routes) {
- List<MediaRoute2Info> changedRoutes = new ArrayList<>();
+ void updateRoutesOnHandler(List<MediaRoute2Info> routes) {
synchronized (mLock) {
+ mRoutes.clear();
for (MediaRoute2Info route : routes) {
mRoutes.put(route.getId(), route);
}
@@ -900,8 +887,10 @@ public final class MediaRouter2 {
Collections.unmodifiableList(
filterRoutesWithCompositePreferenceLocked(List.copyOf(mRoutes.values())));
mHandler.sendMessage(
- obtainMessage(MediaRouter2::dispatchFilteredRoutesChangedLocked,
- this, mFilteredRoutes));
+ obtainMessage(
+ MediaRouter2::dispatchFilteredRoutesUpdatedOnHandler,
+ this,
+ mFilteredRoutes));
}
/**
@@ -1211,6 +1200,14 @@ public final class MediaRouter2 {
}
}
+ private void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
+ for (RouteCallbackRecord record : mRouteCallbackRecords) {
+ List<MediaRoute2Info> filteredRoutes =
+ filterRoutesWithIndividualPreference(routes, record.mPreference);
+ record.mExecutor.execute(() -> record.mRouteCallback.onRoutesUpdated(filteredRoutes));
+ }
+ }
+
private void notifyPreferredFeaturesChanged(List<String> features) {
for (RouteCallbackRecord record : mRouteCallbackRecords) {
record.mExecutor.execute(
@@ -1246,29 +1243,44 @@ public final class MediaRouter2 {
/** Callback for receiving events about media route discovery. */
public abstract static class RouteCallback {
/**
- * Called when routes are added. Whenever you registers a callback, this will be invoked
- * with known routes.
+ * Called when routes are added. Whenever you register a callback, this will be invoked with
+ * known routes.
*
* @param routes the list of routes that have been added. It's never empty.
+ * @deprecated Use {@link #onRoutesUpdated(List)} instead.
*/
+ @Deprecated
public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {}
/**
* Called when routes are removed.
*
* @param routes the list of routes that have been removed. It's never empty.
+ * @deprecated Use {@link #onRoutesUpdated(List)} instead.
*/
+ @Deprecated
public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {}
/**
- * Called when routes are changed. For example, it is called when the route's name or volume
- * have been changed.
+ * Called when the properties of one or more existing routes are changed. For example, it is
+ * called when a route's name or volume have changed.
*
* @param routes the list of routes that have been changed. It's never empty.
+ * @deprecated Use {@link #onRoutesUpdated(List)} instead.
*/
+ @Deprecated
public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
/**
+ * Called when the route list is updated, which can happen when routes are added, removed,
+ * or modified. It will also be called when a route callback is registered.
+ *
+ * @param routes the updated list of routes filtered by the callback's individual discovery
+ * preferences.
+ */
+ public void onRoutesUpdated(@NonNull List<MediaRoute2Info> routes) {}
+
+ /**
* Called when the client app's preferred features are changed. When this is called, it is
* recommended to {@link #getRoutes()} to get the routes that are currently available to the
* app.
@@ -1985,21 +1997,9 @@ public final class MediaRouter2 {
}
@Override
- public void notifyRoutesAdded(List<MediaRoute2Info> routes) {
- mHandler.sendMessage(
- obtainMessage(MediaRouter2::addRoutesOnHandler, MediaRouter2.this, routes));
- }
-
- @Override
- public void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
- mHandler.sendMessage(
- obtainMessage(MediaRouter2::removeRoutesOnHandler, MediaRouter2.this, routes));
- }
-
- @Override
- public void notifyRoutesChanged(List<MediaRoute2Info> routes) {
+ public void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
mHandler.sendMessage(
- obtainMessage(MediaRouter2::changeRoutesOnHandler, MediaRouter2.this, routes));
+ obtainMessage(MediaRouter2::updateRoutesOnHandler, MediaRouter2.this, routes));
}
@Override
@@ -2047,17 +2047,7 @@ public final class MediaRouter2 {
class ManagerCallback implements MediaRouter2Manager.Callback {
@Override
- public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {
- updateAllRoutesFromManager();
- }
-
- @Override
- public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {
- updateAllRoutesFromManager();
- }
-
- @Override
- public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {
+ public void onRoutesUpdated() {
updateAllRoutesFromManager();
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 44c0b54546be..8afc7d999d2e 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -546,37 +546,15 @@ public final class MediaRouter2Manager {
}
}
- void addRoutesOnHandler(List<MediaRoute2Info> routes) {
+ void updateRoutesOnHandler(@NonNull List<MediaRoute2Info> routes) {
synchronized (mRoutesLock) {
+ mRoutes.clear();
for (MediaRoute2Info route : routes) {
mRoutes.put(route.getId(), route);
}
}
- if (routes.size() > 0) {
- notifyRoutesAdded(routes);
- }
- }
- void removeRoutesOnHandler(List<MediaRoute2Info> routes) {
- synchronized (mRoutesLock) {
- for (MediaRoute2Info route : routes) {
- mRoutes.remove(route.getId());
- }
- }
- if (routes.size() > 0) {
- notifyRoutesRemoved(routes);
- }
- }
-
- void changeRoutesOnHandler(List<MediaRoute2Info> routes) {
- synchronized (mRoutesLock) {
- for (MediaRoute2Info route : routes) {
- mRoutes.put(route.getId(), route);
- }
- }
- if (routes.size() > 0) {
- notifyRoutesChanged(routes);
- }
+ notifyRoutesUpdated();
}
void createSessionOnHandler(int requestId, RoutingSessionInfo sessionInfo) {
@@ -650,24 +628,9 @@ public final class MediaRouter2Manager {
notifySessionUpdated(sessionInfo);
}
- private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
- for (CallbackRecord record: mCallbackRecords) {
- record.mExecutor.execute(
- () -> record.mCallback.onRoutesAdded(routes));
- }
- }
-
- private void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
+ private void notifyRoutesUpdated() {
for (CallbackRecord record: mCallbackRecords) {
- record.mExecutor.execute(
- () -> record.mCallback.onRoutesRemoved(routes));
- }
- }
-
- private void notifyRoutesChanged(List<MediaRoute2Info> routes) {
- for (CallbackRecord record: mCallbackRecords) {
- record.mExecutor.execute(
- () -> record.mCallback.onRoutesChanged(routes));
+ record.mExecutor.execute(() -> record.mCallback.onRoutesUpdated());
}
}
@@ -963,23 +926,12 @@ public final class MediaRouter2Manager {
* Interface for receiving events about media routing changes.
*/
public interface Callback {
- /**
- * Called when routes are added.
- * @param routes the list of routes that have been added. It's never empty.
- */
- default void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {}
/**
- * Called when routes are removed.
- * @param routes the list of routes that have been removed. It's never empty.
+ * Called when the routes list changes. This includes adding, modifying, or removing
+ * individual routes.
*/
- default void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {}
-
- /**
- * Called when routes are changed.
- * @param routes the list of routes that have been changed. It's never empty.
- */
- default void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
+ default void onRoutesUpdated() {}
/**
* Called when a session is changed.
@@ -1115,21 +1067,12 @@ public final class MediaRouter2Manager {
}
@Override
- public void notifyRoutesAdded(List<MediaRoute2Info> routes) {
- mHandler.sendMessage(obtainMessage(MediaRouter2Manager::addRoutesOnHandler,
- MediaRouter2Manager.this, routes));
- }
-
- @Override
- public void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
- mHandler.sendMessage(obtainMessage(MediaRouter2Manager::removeRoutesOnHandler,
- MediaRouter2Manager.this, routes));
- }
-
- @Override
- public void notifyRoutesChanged(List<MediaRoute2Info> routes) {
- mHandler.sendMessage(obtainMessage(MediaRouter2Manager::changeRoutesOnHandler,
- MediaRouter2Manager.this, routes));
+ public void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
+ mHandler.sendMessage(
+ obtainMessage(
+ MediaRouter2Manager::updateRoutesOnHandler,
+ MediaRouter2Manager.this,
+ routes));
}
}
}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 4086dec99218..37c836762da0 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -32,7 +32,6 @@ import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_I
import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID_FIXED_VOLUME;
import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID_SPECIAL_FEATURE;
import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID_VARIABLE_VOLUME;
-import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_NAME2;
import static com.android.mediaroutertest.StubMediaRoute2ProviderService.VOLUME_MAX;
import static org.junit.Assert.assertEquals;
@@ -56,10 +55,10 @@ import android.media.RoutingSessionInfo;
import android.os.Bundle;
import android.text.TextUtils;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.PollingCheck;
@@ -69,6 +68,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -115,7 +115,7 @@ public class MediaRouter2ManagerTest {
@Before
public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.MEDIA_CONTENT_CONTROL,
Manifest.permission.MODIFY_AUDIO_ROUTING);
@@ -170,51 +170,95 @@ public class MediaRouter2ManagerTest {
}
@Test
- public void testOnRoutesRemovedAndAdded() throws Exception {
- RouteCallback routeCallback = new RouteCallback() {};
- mRouteCallbacks.add(routeCallback);
- mRouter2.registerRouteCallback(mExecutor, routeCallback,
- new RouteDiscoveryPreference.Builder(FEATURES_ALL, true).build());
+ public void testOnRoutesUpdated() throws Exception {
+ final String routeId0 = "routeId0";
+ final String routeName0 = "routeName0";
+ final String routeId1 = "routeId1";
+ final String routeName1 = "routeName1";
+ final List<String> features = Collections.singletonList("customFeature");
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
+ final int newConnectionState = MediaRoute2Info.CONNECTION_STATE_CONNECTED;
+
+ final List<MediaRoute2Info> routes = new ArrayList<>();
+ routes.add(new MediaRoute2Info.Builder(routeId0, routeName0).addFeatures(features).build());
+ routes.add(new MediaRoute2Info.Builder(routeId1, routeName1).addFeatures(features).build());
- CountDownLatch removedLatch = new CountDownLatch(1);
CountDownLatch addedLatch = new CountDownLatch(1);
+ CountDownLatch changedLatch = new CountDownLatch(1);
+ CountDownLatch removedLatch = new CountDownLatch(1);
- addManagerCallback(new MediaRouter2Manager.Callback() {
- @Override
- public void onRoutesRemoved(List<MediaRoute2Info> routes) {
- assertTrue(routes.size() > 0);
- for (MediaRoute2Info route : routes) {
- if (route.getOriginalId().equals(ROUTE_ID2)
- && route.getName().equals(ROUTE_NAME2)) {
- removedLatch.countDown();
+ addManagerCallback(
+ new MediaRouter2Manager.Callback() {
+ @Override
+ public void onRoutesUpdated() {
+ if (addedLatch.getCount() == 1
+ && checkRoutesMatch(mManager.getAllRoutes(), routes)) {
+ addedLatch.countDown();
+ } else if (changedLatch.getCount() == 1
+ && checkRoutesMatch(
+ mManager.getAllRoutes(), routes.subList(1, 2))) {
+ changedLatch.countDown();
+ } else if (removedLatch.getCount() == 1
+ && checkRoutesRemoved(mManager.getAllRoutes(), routes)) {
+ removedLatch.countDown();
+ }
}
- }
- }
- @Override
- public void onRoutesAdded(List<MediaRoute2Info> routes) {
- assertTrue(routes.size() > 0);
- if (removedLatch.getCount() > 0) {
- return;
- }
- for (MediaRoute2Info route : routes) {
- if (route.getOriginalId().equals(ROUTE_ID2)
- && route.getName().equals(ROUTE_NAME2)) {
- addedLatch.countDown();
- }
- }
- }
- });
+ });
- MediaRoute2Info routeToRemove = routes.get(ROUTE_ID2);
- assertNotNull(routeToRemove);
+ mService.addRoutes(routes);
+ assertTrue(
+ "Added routes not found or onRoutesUpdated() never called.",
+ addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- mService.removeRoute(ROUTE_ID2);
- assertTrue(removedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ MediaRoute2Info newRoute2 =
+ new MediaRoute2Info.Builder(routes.get(1))
+ .setConnectionState(newConnectionState)
+ .build();
+ routes.set(1, newRoute2);
+ mService.addRoute(routes.get(1));
+ assertTrue(
+ "Modified route not found or onRoutesUpdated() never called.",
+ changedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ List<String> routeIds = new ArrayList<>();
+ routeIds.add(routeId0);
+ routeIds.add(routeId1);
+
+ mService.removeRoutes(routeIds);
+ assertTrue(
+ "Removed routes not found or onRoutesUpdated() never called.",
+ removedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
- mService.addRoute(routeToRemove);
- assertTrue(addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ private static boolean checkRoutesMatch(
+ List<MediaRoute2Info> routesReceived, List<MediaRoute2Info> expectedRoutes) {
+ for (MediaRoute2Info expectedRoute : expectedRoutes) {
+ MediaRoute2Info matchingRoute =
+ routesReceived.stream()
+ .filter(r -> r.getOriginalId().equals(expectedRoute.getOriginalId()))
+ .findFirst()
+ .orElse(null);
+
+ if (matchingRoute == null) {
+ return false;
+ }
+ assertTrue(TextUtils.equals(expectedRoute.getName(), matchingRoute.getName()));
+ assertEquals(expectedRoute.getFeatures(), matchingRoute.getFeatures());
+ assertEquals(expectedRoute.getConnectionState(), matchingRoute.getConnectionState());
+ }
+
+ return true;
+ }
+
+ private static boolean checkRoutesRemoved(
+ List<MediaRoute2Info> routesReceived, List<MediaRoute2Info> routesRemoved) {
+ for (MediaRoute2Info removedRoute : routesRemoved) {
+ if (routesReceived.stream()
+ .anyMatch(r -> r.getOriginalId().equals(removedRoute.getOriginalId()))) {
+ return false;
+ }
+ }
+ return true;
}
@Test
@@ -874,28 +918,31 @@ public class MediaRouter2ManagerTest {
// A dummy callback is required to send route feature info.
RouteCallback routeCallback = new RouteCallback() {};
- MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
- @Override
- public void onRoutesAdded(List<MediaRoute2Info> routes) {
- for (MediaRoute2Info route : routes) {
- if (!route.isSystemRoute()
- && hasMatchingFeature(route.getFeatures(), preference
- .getPreferredFeatures())) {
- addedLatch.countDown();
- break;
+ MediaRouter2Manager.Callback managerCallback =
+ new MediaRouter2Manager.Callback() {
+ @Override
+ public void onRoutesUpdated() {
+ List<MediaRoute2Info> routes = mManager.getAllRoutes();
+ for (MediaRoute2Info route : routes) {
+ if (!route.isSystemRoute()
+ && hasMatchingFeature(
+ route.getFeatures(),
+ preference.getPreferredFeatures())) {
+ addedLatch.countDown();
+ break;
+ }
+ }
}
- }
- }
- @Override
- public void onDiscoveryPreferenceChanged(String packageName,
- RouteDiscoveryPreference discoveryPreference) {
- if (TextUtils.equals(mPackageName, packageName)
- && Objects.equals(preference, discoveryPreference)) {
- preferenceLatch.countDown();
- }
- }
- };
+ @Override
+ public void onDiscoveryPreferenceChanged(
+ String packageName, RouteDiscoveryPreference discoveryPreference) {
+ if (TextUtils.equals(mPackageName, packageName)
+ && Objects.equals(preference, discoveryPreference)) {
+ preferenceLatch.countDown();
+ }
+ }
+ };
mManager.registerCallback(mExecutor, managerCallback);
mRouter2.registerRouteCallback(mExecutor, routeCallback, preference);
@@ -923,15 +970,17 @@ public class MediaRouter2ManagerTest {
void awaitOnRouteChangedManager(Runnable task, String routeId,
Predicate<MediaRoute2Info> predicate) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
- MediaRouter2Manager.Callback callback = new MediaRouter2Manager.Callback() {
- @Override
- public void onRoutesChanged(List<MediaRoute2Info> changed) {
- MediaRoute2Info route = createRouteMap(changed).get(routeId);
- if (route != null && predicate.test(route)) {
- latch.countDown();
- }
- }
- };
+ MediaRouter2Manager.Callback callback =
+ new MediaRouter2Manager.Callback() {
+ @Override
+ public void onRoutesUpdated() {
+ MediaRoute2Info route =
+ createRouteMap(mManager.getAllRoutes()).get(routeId);
+ if (route != null && predicate.test(route)) {
+ latch.countDown();
+ }
+ }
+ };
mManager.registerCallback(mExecutor, callback);
try {
task.run();
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
index a51e3714b6f7..a7ae5f45b795 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
@@ -30,7 +30,9 @@ import android.os.Bundle;
import android.os.IBinder;
import android.text.TextUtils;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -146,19 +148,44 @@ public class StubMediaRoute2ProviderService extends MediaRoute2ProviderService {
* they have the same route id.
*/
public void addRoute(@NonNull MediaRoute2Info route) {
- Objects.requireNonNull(route, "route must not be null");
- mRoutes.put(route.getOriginalId(), route);
- publishRoutes();
+ addRoutes(Collections.singletonList(route));
}
/**
- * Removes a route and publishes it.
+ * Adds a list of routes and publishes it. It will replace existing routes with matching ids.
+ *
+ * @param routes list of routes to be added.
*/
+ public void addRoutes(@NonNull List<MediaRoute2Info> routes) {
+ Objects.requireNonNull(routes, "Routes must not be null.");
+ for (MediaRoute2Info route : routes) {
+ Objects.requireNonNull(route, "Route must not be null");
+ mRoutes.put(route.getOriginalId(), route);
+ }
+ publishRoutes();
+ }
+
+ /** Removes a route and publishes it. */
public void removeRoute(@NonNull String routeId) {
- Objects.requireNonNull(routeId, "routeId must not be null");
- MediaRoute2Info route = mRoutes.get(routeId);
- if (route != null) {
- mRoutes.remove(routeId);
+ removeRoutes(Collections.singletonList(routeId));
+ }
+
+ /**
+ * Removes a list of routes and publishes the changes.
+ *
+ * @param routes list of route ids to be removed.
+ */
+ public void removeRoutes(@NonNull List<String> routes) {
+ Objects.requireNonNull(routes, "Routes must not be null");
+ boolean hasRemovedRoutes = false;
+ for (String routeId : routes) {
+ MediaRoute2Info route = mRoutes.get(routeId);
+ if (route != null) {
+ mRoutes.remove(routeId);
+ hasRemovedRoutes = true;
+ }
+ }
+ if (hasRemovedRoutes) {
publishRoutes();
}
}
diff --git a/packages/SettingsLib/Spa/OWNERS b/packages/SettingsLib/Spa/OWNERS
index b352b045c352..288787241caa 100644
--- a/packages/SettingsLib/Spa/OWNERS
+++ b/packages/SettingsLib/Spa/OWNERS
@@ -1,6 +1,6 @@
+set noparent
+
chaohuiw@google.com
hanxu@google.com
kellyz@google.com
pierreqian@google.com
-
-per-file *.xml = set noparent
diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle
index 8c97eca548b5..d38013679ad4 100644
--- a/packages/SettingsLib/Spa/build.gradle
+++ b/packages/SettingsLib/Spa/build.gradle
@@ -16,9 +16,9 @@
buildscript {
ext {
- minSdk_version = 31
- compose_version = '1.2.0-alpha04'
- compose_material3_version = '1.0.0-alpha06'
+ spa_min_sdk = 31
+ jetpack_compose_version = '1.2.0-alpha04'
+ jetpack_compose_material3_version = '1.0.0-alpha06'
}
}
plugins {
diff --git a/packages/SettingsLib/Spa/codelab/AndroidManifest.xml b/packages/SettingsLib/Spa/codelab/AndroidManifest.xml
index 9a89e5efdddb..36b93134bdcb 100644
--- a/packages/SettingsLib/Spa/codelab/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/codelab/AndroidManifest.xml
@@ -13,14 +13,14 @@
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.
- -->
+-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.settingslib.spa.codelab">
<application
android:label="@string/app_name"
android:supportsRtl="true"
- android:theme="@style/Theme.SettingsLib.Compose.DayNight">
+ android:theme="@style/Theme.SpaLib.DayNight">
<activity
android:name="com.android.settingslib.spa.codelab.MainActivity"
android:exported="true">
diff --git a/packages/SettingsLib/Spa/codelab/build.gradle b/packages/SettingsLib/Spa/codelab/build.gradle
index 5251ddd8c01d..169ecf08aeac 100644
--- a/packages/SettingsLib/Spa/codelab/build.gradle
+++ b/packages/SettingsLib/Spa/codelab/build.gradle
@@ -25,7 +25,7 @@ android {
defaultConfig {
applicationId "com.android.settingslib.spa.codelab"
- minSdk minSdk_version
+ minSdk spa_min_sdk
targetSdk 33
versionCode 1
versionName "1.0"
@@ -52,7 +52,7 @@ android {
compose true
}
composeOptions {
- kotlinCompilerExtensionVersion compose_version
+ kotlinCompilerExtensionVersion jetpack_compose_version
}
packagingOptions {
resources {
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/FooterPage.kt b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/FooterPage.kt
new file mode 100644
index 000000000000..9fcbff843203
--- /dev/null
+++ b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/FooterPage.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.codelab.page
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.ui.Footer
+
+object FooterPageProvider : SettingsPageProvider {
+ override val name = "Footer"
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ FooterPage()
+ }
+
+ @Composable
+ fun EntryItem() {
+ Preference(object : PreferenceModel {
+ override val title = "Sample Footer"
+ override val onClick = navigator(name)
+ })
+ }
+}
+
+@Composable
+private fun FooterPage() {
+ Column(Modifier.verticalScroll(rememberScrollState())) {
+ Preference(remember {
+ object : PreferenceModel {
+ override val title = "Some Preference"
+ override val summary = stateOf("Some summary")
+ }
+ })
+ Footer(footerText = "Footer text always at the end of page.")
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun FooterPagePreview() {
+ SettingsTheme {
+ FooterPage()
+ }
+}
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt
index 5adbc32c9ece..57a69c4be22f 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt
+++ b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt
@@ -50,8 +50,11 @@ private fun HomePage() {
)
PreferencePageProvider.EntryItem()
-
+ SwitchPreferencePageProvider.EntryItem()
ArgumentPageProvider.EntryItem(stringParam = "foo", intParam = 0)
+
+ SliderPageProvider.EntryItem()
+ FooterPageProvider.EntryItem()
}
}
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt
index da278d1f1e7a..54c588ab18f0 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt
+++ b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt
@@ -21,10 +21,19 @@ import com.android.settingslib.spa.framework.api.SettingsPageRepository
object Destinations {
const val Home = "Home"
const val Preference = "Preference"
+ const val SwitchPreference = "SwitchPreference"
const val Argument = "Argument"
+ const val Slider = "Slider"
}
val codelabPageRepository = SettingsPageRepository(
- allPages = listOf(HomePageProvider, PreferencePageProvider, ArgumentPageProvider),
+ allPages = listOf(
+ HomePageProvider,
+ PreferencePageProvider,
+ SwitchPreferencePageProvider,
+ ArgumentPageProvider,
+ SliderPageProvider,
+ FooterPageProvider,
+ ),
startDestination = Destinations.Home,
)
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SliderPage.kt b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SliderPage.kt
new file mode 100644
index 000000000000..6e965813afc4
--- /dev/null
+++ b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SliderPage.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.codelab.page
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AccessAlarm
+import androidx.compose.material.icons.outlined.MusicNote
+import androidx.compose.material.icons.outlined.MusicOff
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.ui.SettingsSlider
+import com.android.settingslib.spa.widget.ui.SettingsSliderModel
+
+object SliderPageProvider : SettingsPageProvider {
+ override val name = Destinations.Slider
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ SliderPage()
+ }
+
+ @Composable
+ fun EntryItem() {
+ Preference(object : PreferenceModel {
+ override val title = "Sample Slider"
+ override val onClick = navigator(Destinations.Slider)
+ })
+ }
+}
+
+@Composable
+private fun SliderPage() {
+ Column(Modifier.verticalScroll(rememberScrollState())) {
+ SettingsSlider(object : SettingsSliderModel {
+ override val title = "Slider"
+ override val initValue = 40
+ })
+
+ SettingsSlider(object : SettingsSliderModel {
+ override val title = "Slider with icon"
+ override val initValue = 30
+ override val onValueChangeFinished = {
+ println("onValueChangeFinished")
+ }
+ override val icon = Icons.Outlined.AccessAlarm
+ })
+
+ val initValue = 0
+ var icon by remember { mutableStateOf(Icons.Outlined.MusicOff) }
+ var sliderPosition by remember { mutableStateOf(initValue) }
+ SettingsSlider(object : SettingsSliderModel {
+ override val title = "Slider with changeable icon"
+ override val initValue = initValue
+ override val onValueChange = { it: Int ->
+ sliderPosition = it
+ icon = if (it > 0) Icons.Outlined.MusicNote else Icons.Outlined.MusicOff
+ }
+ override val onValueChangeFinished = {
+ println("onValueChangeFinished: the value is $sliderPosition")
+ }
+ override val icon = icon
+ })
+
+ SettingsSlider(object : SettingsSliderModel {
+ override val title = "Slider with steps"
+ override val initValue = 2
+ override val valueRange = 1..5
+ override val showSteps = true
+ })
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun SliderPagePreview() {
+ SettingsTheme {
+ SliderPage()
+ }
+}
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SwitchPreferencePage.kt b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SwitchPreferencePage.kt
new file mode 100644
index 000000000000..b566afa84d42
--- /dev/null
+++ b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SwitchPreferencePage.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.codelab.page
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.produceState
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.SwitchPreference
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import kotlinx.coroutines.delay
+
+object SwitchPreferencePageProvider : SettingsPageProvider {
+ override val name = Destinations.SwitchPreference
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ SwitchPreferencePage()
+ }
+
+ @Composable
+ fun EntryItem() {
+ Preference(object : PreferenceModel {
+ override val title = "Sample SwitchPreference"
+ override val onClick = navigator(Destinations.SwitchPreference)
+ })
+ }
+}
+
+@Composable
+private fun SwitchPreferencePage() {
+ Column(Modifier.verticalScroll(rememberScrollState())) {
+ SampleSwitchPreference()
+ SampleSwitchPreferenceWithSummary()
+ SampleSwitchPreferenceWithAsyncSummary()
+ SampleNotChangeableSwitchPreference()
+ }
+}
+
+@Composable
+private fun SampleSwitchPreference() {
+ val checked = rememberSaveable { mutableStateOf(false) }
+ SwitchPreference(remember {
+ object : SwitchPreferenceModel {
+ override val title = "SwitchPreference"
+ override val checked = checked
+ override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+ }
+ })
+}
+
+@Composable
+private fun SampleSwitchPreferenceWithSummary() {
+ val checked = rememberSaveable { mutableStateOf(true) }
+ SwitchPreference(remember {
+ object : SwitchPreferenceModel {
+ override val title = "SwitchPreference"
+ override val summary = stateOf("With summary")
+ override val checked = checked
+ override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+ }
+ })
+}
+
+@Composable
+private fun SampleSwitchPreferenceWithAsyncSummary() {
+ val checked = rememberSaveable { mutableStateOf(true) }
+ val summary = produceState(initialValue = " ") {
+ delay(1000L)
+ value = "Async summary"
+ }
+ SwitchPreference(remember {
+ object : SwitchPreferenceModel {
+ override val title = "SwitchPreference"
+ override val summary = summary
+ override val checked = checked
+ override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+ }
+ })
+}
+
+@Composable
+private fun SampleNotChangeableSwitchPreference() {
+ val checked = rememberSaveable { mutableStateOf(true) }
+ SwitchPreference(remember {
+ object : SwitchPreferenceModel {
+ override val title = "SwitchPreference"
+ override val summary = stateOf("Not changeable")
+ override val changeable = stateOf(false)
+ override val checked = checked
+ override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+ }
+ })
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun SwitchPreferencePagePreview() {
+ SettingsTheme {
+ SwitchPreferencePage()
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 463c0765943f..3c8d91edc05b 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -31,6 +31,9 @@ android_library {
"androidx.navigation_navigation-compose",
"com.google.android.material_material",
],
- kotlincflags: ["-Xjvm-default=all"],
+ kotlincflags: [
+ "-Xjvm-default=all",
+ "-Xopt-in=kotlin.RequiresOptIn",
+ ],
min_sdk_version: "31",
}
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 60794c88d00d..ad69da314735 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -24,7 +24,7 @@ android {
compileSdk 33
defaultConfig {
- minSdk minSdk_version
+ minSdk spa_min_sdk
targetSdk 33
}
@@ -43,13 +43,13 @@ android {
}
kotlinOptions {
jvmTarget = '1.8'
- freeCompilerArgs = ["-Xjvm-default=all"]
+ freeCompilerArgs = ["-Xjvm-default=all", "-Xopt-in=kotlin.RequiresOptIn"]
}
buildFeatures {
compose true
}
composeOptions {
- kotlinCompilerExtensionVersion compose_version
+ kotlinCompilerExtensionVersion jetpack_compose_version
}
packagingOptions {
resources {
@@ -59,11 +59,11 @@ android {
}
dependencies {
- api "androidx.compose.material3:material3:$compose_material3_version"
- api "androidx.compose.material:material-icons-extended:$compose_version"
- api "androidx.compose.runtime:runtime-livedata:$compose_version"
- api "androidx.compose.ui:ui-tooling-preview:$compose_version"
+ api "androidx.compose.material3:material3:$jetpack_compose_material3_version"
+ api "androidx.compose.material:material-icons-extended:$jetpack_compose_version"
+ api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
+ api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
api 'androidx.navigation:navigation-compose:2.5.0'
api 'com.google.android.material:material:1.6.1'
- debugApi "androidx.compose.ui:ui-tooling:$compose_version"
+ debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
}
diff --git a/packages/SettingsLib/Spa/spa/res/values-night/themes.xml b/packages/SettingsLib/Spa/spa/res/values-night/themes.xml
index 8b52b507bdd9..67dd2b0cc5e0 100644
--- a/packages/SettingsLib/Spa/spa/res/values-night/themes.xml
+++ b/packages/SettingsLib/Spa/spa/res/values-night/themes.xml
@@ -13,8 +13,8 @@
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.
- -->
+-->
<resources>
- <style name="Theme.SettingsLib.Compose.DayNight" />
+ <style name="Theme.SpaLib.DayNight" />
</resources>
diff --git a/packages/SettingsLib/Spa/spa/res/values/themes.xml b/packages/SettingsLib/Spa/spa/res/values/themes.xml
index 01f9ea592f6d..e0e5fc211ec6 100644
--- a/packages/SettingsLib/Spa/spa/res/values/themes.xml
+++ b/packages/SettingsLib/Spa/spa/res/values/themes.xml
@@ -13,15 +13,15 @@
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.
- -->
+-->
<resources>
- <style name="Theme.SettingsLib.Compose" parent="Theme.Material3.DayNight.NoActionBar">
+ <style name="Theme.SpaLib" parent="Theme.Material3.DayNight.NoActionBar">
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
- <style name="Theme.SettingsLib.Compose.DayNight">
+ <style name="Theme.SpaLib.DayNight">
<item name="android:windowLightStatusBar">true</item>
</style>
</resources>
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt
new file mode 100644
index 000000000000..ae325f8862eb
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.framework.compose
+
+import android.graphics.drawable.Animatable
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Drawable
+import android.os.Handler
+import android.os.Looper
+import android.view.View
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.RememberObserver
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.asAndroidColorFilter
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.nativeCanvas
+import androidx.compose.ui.graphics.painter.BitmapPainter
+import androidx.compose.ui.graphics.painter.ColorPainter
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.withSave
+import androidx.compose.ui.unit.LayoutDirection
+import kotlin.math.roundToInt
+
+/**
+ * *************************************************************************************************
+ * This file was forked from
+ * https://github.com/google/accompanist/blob/main/drawablepainter/src/main/java/com/google/accompanist/drawablepainter/DrawablePainter.kt
+ * and will be removed once it lands in AndroidX.
+ */
+
+private val MAIN_HANDLER by lazy(LazyThreadSafetyMode.NONE) {
+ Handler(Looper.getMainLooper())
+}
+
+/**
+ * A [Painter] which draws an Android [Drawable] and supports [Animatable] drawables. Instances
+ * should be remembered to be able to start and stop [Animatable] animations.
+ *
+ * Instances are usually retrieved from [rememberDrawablePainter].
+ */
+class DrawablePainter(
+ val drawable: Drawable
+) : Painter(), RememberObserver {
+ private var drawInvalidateTick by mutableStateOf(0)
+ private var drawableIntrinsicSize by mutableStateOf(drawable.intrinsicSize)
+
+ private val callback: Drawable.Callback by lazy {
+ object : Drawable.Callback {
+ override fun invalidateDrawable(d: Drawable) {
+ // Update the tick so that we get re-drawn
+ drawInvalidateTick++
+ // Update our intrinsic size too
+ drawableIntrinsicSize = drawable.intrinsicSize
+ }
+
+ override fun scheduleDrawable(d: Drawable, what: Runnable, time: Long) {
+ MAIN_HANDLER.postAtTime(what, time)
+ }
+
+ override fun unscheduleDrawable(d: Drawable, what: Runnable) {
+ MAIN_HANDLER.removeCallbacks(what)
+ }
+ }
+ }
+
+ init {
+ if (drawable.intrinsicWidth >= 0 && drawable.intrinsicHeight >= 0) {
+ // Update the drawable's bounds to match the intrinsic size
+ drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
+ }
+ }
+
+ override fun onRemembered() {
+ drawable.callback = callback
+ drawable.setVisible(true, true)
+ if (drawable is Animatable) drawable.start()
+ }
+
+ override fun onAbandoned() = onForgotten()
+
+ override fun onForgotten() {
+ if (drawable is Animatable) drawable.stop()
+ drawable.setVisible(false, false)
+ drawable.callback = null
+ }
+
+ override fun applyAlpha(alpha: Float): Boolean {
+ drawable.alpha = (alpha * 255).roundToInt().coerceIn(0, 255)
+ return true
+ }
+
+ override fun applyColorFilter(colorFilter: ColorFilter?): Boolean {
+ drawable.colorFilter = colorFilter?.asAndroidColorFilter()
+ return true
+ }
+
+ override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean =
+ drawable.setLayoutDirection(
+ when (layoutDirection) {
+ LayoutDirection.Ltr -> View.LAYOUT_DIRECTION_LTR
+ LayoutDirection.Rtl -> View.LAYOUT_DIRECTION_RTL
+ }
+ )
+
+ override val intrinsicSize: Size get() = drawableIntrinsicSize
+
+ override fun DrawScope.onDraw() {
+ drawIntoCanvas { canvas ->
+ // Reading this ensures that we invalidate when invalidateDrawable() is called
+ drawInvalidateTick
+
+ // Update the Drawable's bounds
+ drawable.setBounds(0, 0, size.width.roundToInt(), size.height.roundToInt())
+
+ canvas.withSave {
+ drawable.draw(canvas.nativeCanvas)
+ }
+ }
+ }
+}
+
+/**
+ * Remembers [Drawable] wrapped up as a [Painter]. This function attempts to un-wrap the
+ * drawable contents and use Compose primitives where possible.
+ *
+ * If the provided [drawable] is `null`, an empty no-op painter is returned.
+ *
+ * This function tries to dispatch lifecycle events to [drawable] as much as possible from
+ * within Compose.
+ *
+ * @sample com.google.accompanist.sample.drawablepainter.BasicSample
+ */
+@Composable
+fun rememberDrawablePainter(drawable: Drawable?): Painter = remember(drawable) {
+ when (drawable) {
+ null -> EmptyPainter
+ is BitmapDrawable -> BitmapPainter(drawable.bitmap.asImageBitmap())
+ is ColorDrawable -> ColorPainter(Color(drawable.color))
+ // Since the DrawablePainter will be remembered and it implements RememberObserver, it
+ // will receive the necessary events
+ else -> DrawablePainter(drawable.mutate())
+ }
+}
+
+private val Drawable.intrinsicSize: Size
+ get() = when {
+ // Only return a finite size if the drawable has an intrinsic size
+ intrinsicWidth >= 0 && intrinsicHeight >= 0 -> {
+ Size(width = intrinsicWidth.toFloat(), height = intrinsicHeight.toFloat())
+ }
+ else -> Size.Unspecified
+ }
+
+internal object EmptyPainter : Painter() {
+ override val intrinsicSize: Size get() = Size.Unspecified
+ override fun DrawScope.onDraw() {}
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/RuntimeUtils.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/RuntimeUtils.kt
index 7c8608da6724..ba8854653b0b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/RuntimeUtils.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/RuntimeUtils.kt
@@ -16,9 +16,17 @@
package com.android.settingslib.spa.framework.compose
+import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+
+@Composable
+fun <T> rememberContext(constructor: (Context) -> T): T {
+ val context = LocalContext.current
+ return remember(context) { constructor(context) }
+}
/**
* Remember the [State] initialized with the [this].
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt
new file mode 100644
index 000000000000..27fdc916a434
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.framework.theme
+
+import android.content.Context
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+
+data class SettingsColorScheme(
+ val background: Color = Color.Unspecified,
+ val categoryTitle: Color = Color.Unspecified,
+ val surface: Color = Color.Unspecified,
+ val surfaceHeader: Color = Color.Unspecified,
+ val secondaryText: Color = Color.Unspecified,
+ val primaryContainer: Color = Color.Unspecified,
+ val onPrimaryContainer: Color = Color.Unspecified,
+)
+
+internal val LocalColorScheme = staticCompositionLocalOf { SettingsColorScheme() }
+
+@Composable
+internal fun settingsColorScheme(isDarkTheme: Boolean): SettingsColorScheme {
+ val context = LocalContext.current
+ return remember(isDarkTheme) {
+ when {
+ isDarkTheme -> dynamicDarkColorScheme(context)
+ else -> dynamicLightColorScheme(context)
+ }
+ }
+}
+
+/**
+ * Creates a light dynamic color scheme.
+ *
+ * Use this function to create a color scheme based off the system wallpaper. If the developer
+ * changes the wallpaper this color scheme will change accordingly. This dynamic scheme is a
+ * light theme variant.
+ *
+ * @param context The context required to get system resource data.
+ */
+private fun dynamicLightColorScheme(context: Context): SettingsColorScheme {
+ val tonalPalette = dynamicTonalPalette(context)
+ return SettingsColorScheme(
+ background = tonalPalette.neutral95,
+ categoryTitle = tonalPalette.primary40,
+ surface = tonalPalette.neutral99,
+ surfaceHeader = tonalPalette.neutral90,
+ secondaryText = tonalPalette.neutralVariant30,
+ primaryContainer = tonalPalette.primary90,
+ onPrimaryContainer = tonalPalette.neutral10,
+ )
+}
+
+/**
+ * Creates a dark dynamic color scheme.
+ *
+ * Use this function to create a color scheme based off the system wallpaper. If the developer
+ * changes the wallpaper this color scheme will change accordingly. This dynamic scheme is a dark
+ * theme variant.
+ *
+ * @param context The context required to get system resource data.
+ */
+private fun dynamicDarkColorScheme(context: Context): SettingsColorScheme {
+ val tonalPalette = dynamicTonalPalette(context)
+ return SettingsColorScheme(
+ background = tonalPalette.neutral10,
+ categoryTitle = tonalPalette.primary90,
+ surface = tonalPalette.neutral20,
+ surfaceHeader = tonalPalette.neutral30,
+ secondaryText = tonalPalette.neutralVariant80,
+ primaryContainer = tonalPalette.secondary90,
+ onPrimaryContainer = tonalPalette.neutral10,
+ )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 9a67c12dfd0b..e1ca69bd7940 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -20,6 +20,7 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.ui.unit.dp
object SettingsDimension {
+ val itemIconSize = 24.dp
val itemIconContainerSize = 72.dp
val itemPaddingStart = 24.dp
val itemPaddingEnd = 16.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
index 29998258ed37..e6fa74e34cc8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
@@ -19,6 +19,8 @@ package com.android.settingslib.spa.framework.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.ReadOnlyComposable
/**
* The Material 3 Theme for Settings.
@@ -26,9 +28,21 @@ import androidx.compose.runtime.Composable
@Composable
fun SettingsTheme(content: @Composable () -> Unit) {
val isDarkTheme = isSystemInDarkTheme()
- val colorScheme = materialColorScheme(isDarkTheme)
+ val settingsColorScheme = settingsColorScheme(isDarkTheme)
+ val colorScheme = materialColorScheme(isDarkTheme).copy(
+ background = settingsColorScheme.background,
+ )
- MaterialTheme(colorScheme = colorScheme) {
- content()
+ CompositionLocalProvider(LocalColorScheme provides settingsColorScheme(isDarkTheme)) {
+ MaterialTheme(colorScheme = colorScheme, typography = rememberSettingsTypography()) {
+ content()
+ }
}
}
+
+object SettingsTheme {
+ val colorScheme: SettingsColorScheme
+ @Composable
+ @ReadOnlyComposable
+ get() = LocalColorScheme.current
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt
new file mode 100644
index 000000000000..f81f5e734fb4
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.framework.theme
+
+import android.R
+import android.content.Context
+import androidx.annotation.ColorRes
+import androidx.annotation.DoNotInline
+import androidx.compose.ui.graphics.Color
+
+/**
+ * Tonal Palette structure in Material.
+ *
+ * A tonal palette is comprised of 5 tonal ranges. Each tonal range includes the 13 stops, or
+ * tonal swatches.
+ *
+ * Tonal range names are:
+ * - Neutral (N)
+ * - Neutral variant (NV)
+ * - Primary (P)
+ * - Secondary (S)
+ * - Tertiary (T)
+ */
+internal class SettingsTonalPalette(
+ // The neutral tonal range from the generated dynamic color palette.
+ // Ordered from the lightest shade [neutral100] to the darkest shade [neutral0].
+ val neutral100: Color,
+ val neutral99: Color,
+ val neutral95: Color,
+ val neutral90: Color,
+ val neutral80: Color,
+ val neutral70: Color,
+ val neutral60: Color,
+ val neutral50: Color,
+ val neutral40: Color,
+ val neutral30: Color,
+ val neutral20: Color,
+ val neutral10: Color,
+ val neutral0: Color,
+
+ // The neutral variant tonal range, sometimes called "neutral 2", from the
+ // generated dynamic color palette.
+ // Ordered from the lightest shade [neutralVariant100] to the darkest shade [neutralVariant0].
+ val neutralVariant100: Color,
+ val neutralVariant99: Color,
+ val neutralVariant95: Color,
+ val neutralVariant90: Color,
+ val neutralVariant80: Color,
+ val neutralVariant70: Color,
+ val neutralVariant60: Color,
+ val neutralVariant50: Color,
+ val neutralVariant40: Color,
+ val neutralVariant30: Color,
+ val neutralVariant20: Color,
+ val neutralVariant10: Color,
+ val neutralVariant0: Color,
+
+ // The primary tonal range from the generated dynamic color palette.
+ // Ordered from the lightest shade [primary100] to the darkest shade [primary0].
+ val primary100: Color,
+ val primary99: Color,
+ val primary95: Color,
+ val primary90: Color,
+ val primary80: Color,
+ val primary70: Color,
+ val primary60: Color,
+ val primary50: Color,
+ val primary40: Color,
+ val primary30: Color,
+ val primary20: Color,
+ val primary10: Color,
+ val primary0: Color,
+
+ // The secondary tonal range from the generated dynamic color palette.
+ // Ordered from the lightest shade [secondary100] to the darkest shade [secondary0].
+ val secondary100: Color,
+ val secondary99: Color,
+ val secondary95: Color,
+ val secondary90: Color,
+ val secondary80: Color,
+ val secondary70: Color,
+ val secondary60: Color,
+ val secondary50: Color,
+ val secondary40: Color,
+ val secondary30: Color,
+ val secondary20: Color,
+ val secondary10: Color,
+ val secondary0: Color,
+
+ // The tertiary tonal range from the generated dynamic color palette.
+ // Ordered from the lightest shade [tertiary100] to the darkest shade [tertiary0].
+ val tertiary100: Color,
+ val tertiary99: Color,
+ val tertiary95: Color,
+ val tertiary90: Color,
+ val tertiary80: Color,
+ val tertiary70: Color,
+ val tertiary60: Color,
+ val tertiary50: Color,
+ val tertiary40: Color,
+ val tertiary30: Color,
+ val tertiary20: Color,
+ val tertiary10: Color,
+ val tertiary0: Color,
+)
+
+/** Dynamic colors in Material. */
+internal fun dynamicTonalPalette(context: Context) = SettingsTonalPalette(
+ // The neutral tonal range from the generated dynamic color palette.
+ neutral100 = ColorResourceHelper.getColor(context, R.color.system_neutral1_0),
+ neutral99 = ColorResourceHelper.getColor(context, R.color.system_neutral1_10),
+ neutral95 = ColorResourceHelper.getColor(context, R.color.system_neutral1_50),
+ neutral90 = ColorResourceHelper.getColor(context, R.color.system_neutral1_100),
+ neutral80 = ColorResourceHelper.getColor(context, R.color.system_neutral1_200),
+ neutral70 = ColorResourceHelper.getColor(context, R.color.system_neutral1_300),
+ neutral60 = ColorResourceHelper.getColor(context, R.color.system_neutral1_400),
+ neutral50 = ColorResourceHelper.getColor(context, R.color.system_neutral1_500),
+ neutral40 = ColorResourceHelper.getColor(context, R.color.system_neutral1_600),
+ neutral30 = ColorResourceHelper.getColor(context, R.color.system_neutral1_700),
+ neutral20 = ColorResourceHelper.getColor(context, R.color.system_neutral1_800),
+ neutral10 = ColorResourceHelper.getColor(context, R.color.system_neutral1_900),
+ neutral0 = ColorResourceHelper.getColor(context, R.color.system_neutral1_1000),
+
+ // The neutral variant tonal range, sometimes called "neutral 2", from the
+ // generated dynamic color palette.
+ neutralVariant100 = ColorResourceHelper.getColor(context, R.color.system_neutral2_0),
+ neutralVariant99 = ColorResourceHelper.getColor(context, R.color.system_neutral2_10),
+ neutralVariant95 = ColorResourceHelper.getColor(context, R.color.system_neutral2_50),
+ neutralVariant90 = ColorResourceHelper.getColor(context, R.color.system_neutral2_100),
+ neutralVariant80 = ColorResourceHelper.getColor(context, R.color.system_neutral2_200),
+ neutralVariant70 = ColorResourceHelper.getColor(context, R.color.system_neutral2_300),
+ neutralVariant60 = ColorResourceHelper.getColor(context, R.color.system_neutral2_400),
+ neutralVariant50 = ColorResourceHelper.getColor(context, R.color.system_neutral2_500),
+ neutralVariant40 = ColorResourceHelper.getColor(context, R.color.system_neutral2_600),
+ neutralVariant30 = ColorResourceHelper.getColor(context, R.color.system_neutral2_700),
+ neutralVariant20 = ColorResourceHelper.getColor(context, R.color.system_neutral2_800),
+ neutralVariant10 = ColorResourceHelper.getColor(context, R.color.system_neutral2_900),
+ neutralVariant0 = ColorResourceHelper.getColor(context, R.color.system_neutral2_1000),
+
+ // The primary tonal range from the generated dynamic color palette.
+ primary100 = ColorResourceHelper.getColor(context, R.color.system_accent1_0),
+ primary99 = ColorResourceHelper.getColor(context, R.color.system_accent1_10),
+ primary95 = ColorResourceHelper.getColor(context, R.color.system_accent1_50),
+ primary90 = ColorResourceHelper.getColor(context, R.color.system_accent1_100),
+ primary80 = ColorResourceHelper.getColor(context, R.color.system_accent1_200),
+ primary70 = ColorResourceHelper.getColor(context, R.color.system_accent1_300),
+ primary60 = ColorResourceHelper.getColor(context, R.color.system_accent1_400),
+ primary50 = ColorResourceHelper.getColor(context, R.color.system_accent1_500),
+ primary40 = ColorResourceHelper.getColor(context, R.color.system_accent1_600),
+ primary30 = ColorResourceHelper.getColor(context, R.color.system_accent1_700),
+ primary20 = ColorResourceHelper.getColor(context, R.color.system_accent1_800),
+ primary10 = ColorResourceHelper.getColor(context, R.color.system_accent1_900),
+ primary0 = ColorResourceHelper.getColor(context, R.color.system_accent1_1000),
+
+ // The secondary tonal range from the generated dynamic color palette.
+ secondary100 = ColorResourceHelper.getColor(context, R.color.system_accent2_0),
+ secondary99 = ColorResourceHelper.getColor(context, R.color.system_accent2_10),
+ secondary95 = ColorResourceHelper.getColor(context, R.color.system_accent2_50),
+ secondary90 = ColorResourceHelper.getColor(context, R.color.system_accent2_100),
+ secondary80 = ColorResourceHelper.getColor(context, R.color.system_accent2_200),
+ secondary70 = ColorResourceHelper.getColor(context, R.color.system_accent2_300),
+ secondary60 = ColorResourceHelper.getColor(context, R.color.system_accent2_400),
+ secondary50 = ColorResourceHelper.getColor(context, R.color.system_accent2_500),
+ secondary40 = ColorResourceHelper.getColor(context, R.color.system_accent2_600),
+ secondary30 = ColorResourceHelper.getColor(context, R.color.system_accent2_700),
+ secondary20 = ColorResourceHelper.getColor(context, R.color.system_accent2_800),
+ secondary10 = ColorResourceHelper.getColor(context, R.color.system_accent2_900),
+ secondary0 = ColorResourceHelper.getColor(context, R.color.system_accent2_1000),
+
+ // The tertiary tonal range from the generated dynamic color palette.
+ tertiary100 = ColorResourceHelper.getColor(context, R.color.system_accent3_0),
+ tertiary99 = ColorResourceHelper.getColor(context, R.color.system_accent3_10),
+ tertiary95 = ColorResourceHelper.getColor(context, R.color.system_accent3_50),
+ tertiary90 = ColorResourceHelper.getColor(context, R.color.system_accent3_100),
+ tertiary80 = ColorResourceHelper.getColor(context, R.color.system_accent3_200),
+ tertiary70 = ColorResourceHelper.getColor(context, R.color.system_accent3_300),
+ tertiary60 = ColorResourceHelper.getColor(context, R.color.system_accent3_400),
+ tertiary50 = ColorResourceHelper.getColor(context, R.color.system_accent3_500),
+ tertiary40 = ColorResourceHelper.getColor(context, R.color.system_accent3_600),
+ tertiary30 = ColorResourceHelper.getColor(context, R.color.system_accent3_700),
+ tertiary20 = ColorResourceHelper.getColor(context, R.color.system_accent3_800),
+ tertiary10 = ColorResourceHelper.getColor(context, R.color.system_accent3_900),
+ tertiary0 = ColorResourceHelper.getColor(context, R.color.system_accent3_1000),
+)
+
+private object ColorResourceHelper {
+ @DoNotInline
+ fun getColor(context: Context, @ColorRes id: Int): Color {
+ return Color(context.resources.getColor(id, context.theme))
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt
new file mode 100644
index 000000000000..07f09ba95ca3
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.framework.theme
+
+import androidx.compose.material3.Typography
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.em
+import androidx.compose.ui.unit.sp
+
+private class SettingsTypography {
+ private val brand = FontFamily.Default
+ private val plain = FontFamily.Default
+
+ val typography = Typography(
+ displayLarge = TextStyle(
+ fontFamily = brand,
+ fontWeight = FontWeight.Normal,
+ fontSize = 57.sp,
+ lineHeight = 64.sp,
+ letterSpacing = (-0.2).sp
+ ),
+ displayMedium = TextStyle(
+ fontFamily = brand,
+ fontWeight = FontWeight.Normal,
+ fontSize = 45.sp,
+ lineHeight = 52.sp,
+ letterSpacing = 0.0.sp
+ ),
+ displaySmall = TextStyle(
+ fontFamily = brand,
+ fontWeight = FontWeight.Normal,
+ fontSize = 36.sp,
+ lineHeight = 44.sp,
+ letterSpacing = 0.0.sp
+ ),
+ headlineLarge = TextStyle(
+ fontFamily = brand,
+ fontWeight = FontWeight.Normal,
+ fontSize = 32.sp,
+ lineHeight = 40.sp,
+ letterSpacing = 0.0.sp
+ ),
+ headlineMedium = TextStyle(
+ fontFamily = brand,
+ fontWeight = FontWeight.Normal,
+ fontSize = 28.sp,
+ lineHeight = 36.sp,
+ letterSpacing = 0.0.sp
+ ),
+ headlineSmall = TextStyle(
+ fontFamily = brand,
+ fontWeight = FontWeight.Normal,
+ fontSize = 24.sp,
+ lineHeight = 32.sp,
+ letterSpacing = 0.0.sp
+ ),
+ titleLarge = TextStyle(
+ fontFamily = brand,
+ fontWeight = FontWeight.Normal,
+ fontSize = 22.sp,
+ lineHeight = 28.sp,
+ letterSpacing = 0.02.em,
+ ),
+ titleMedium = TextStyle(
+ fontFamily = brand,
+ fontWeight = FontWeight.Normal,
+ fontSize = 20.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.02.em,
+ ),
+ titleSmall = TextStyle(
+ fontFamily = brand,
+ fontWeight = FontWeight.Normal,
+ fontSize = 18.sp,
+ lineHeight = 20.sp,
+ letterSpacing = 0.02.em,
+ ),
+ bodyLarge = TextStyle(
+ fontFamily = plain,
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.01.em,
+ ),
+ bodyMedium = TextStyle(
+ fontFamily = plain,
+ fontWeight = FontWeight.Normal,
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ letterSpacing = 0.01.em,
+ ),
+ bodySmall = TextStyle(
+ fontFamily = plain,
+ fontWeight = FontWeight.Normal,
+ fontSize = 12.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.01.em,
+ ),
+ labelLarge = TextStyle(
+ fontFamily = plain,
+ fontWeight = FontWeight.Medium,
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.01.em,
+ ),
+ labelMedium = TextStyle(
+ fontFamily = plain,
+ fontWeight = FontWeight.Medium,
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ letterSpacing = 0.01.em,
+ ),
+ labelSmall = TextStyle(
+ fontFamily = plain,
+ fontWeight = FontWeight.Medium,
+ fontSize = 12.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.01.em,
+ ),
+ )
+}
+
+@Composable
+internal fun rememberSettingsTypography(): Typography {
+ return remember { SettingsTypography().typography }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
index 6bdc294d888a..9a34dbf36735 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
@@ -25,8 +25,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Divider
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Alignment
@@ -39,6 +37,7 @@ import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsOpacity
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.ui.SettingsTitle
@Composable
internal fun BaseLayout(
@@ -94,11 +93,7 @@ private fun BaseIcon(
@Composable
private fun Titles(title: String, subTitle: @Composable () -> Unit, modifier: Modifier) {
Column(modifier) {
- Text(
- text = title,
- color = MaterialTheme.colorScheme.onSurface,
- style = MaterialTheme.typography.titleMedium,
- )
+ SettingsTitle(title)
subTitle()
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
index 3b99d36e630b..4b2c8e41a388 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
@@ -19,8 +19,6 @@ package com.android.settingslib.spa.widget.preference
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.BatteryChargingFull
import androidx.compose.material3.Icon
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
@@ -29,6 +27,7 @@ import androidx.compose.ui.unit.Dp
import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.ui.SettingsBody
@Composable
internal fun BasePreference(
@@ -44,15 +43,7 @@ internal fun BasePreference(
) {
BaseLayout(
title = title,
- subTitle = {
- if (summary.value.isNotEmpty()) {
- Text(
- text = summary.value,
- color = MaterialTheme.colorScheme.onSurfaceVariant,
- style = MaterialTheme.typography.bodyMedium,
- )
- }
- },
+ subTitle = { SettingsBody(summary) },
modifier = modifier,
icon = icon,
enabled = enabled,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
new file mode 100644
index 000000000000..0dab0dfe1d6d
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget.preference
+
+import androidx.compose.foundation.LocalIndication
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.selection.toggleable
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.ui.SettingsSwitch
+
+/**
+ * The widget model for [SwitchPreference] widget.
+ */
+interface SwitchPreferenceModel {
+ /**
+ * The title of this [SwitchPreference].
+ */
+ val title: String
+
+ /**
+ * The summary of this [SwitchPreference].
+ */
+ val summary: State<String>
+ get() = stateOf("")
+
+ /**
+ * Indicates whether this [SwitchPreference] is checked.
+ *
+ * This can be `null` during the data loading before the data is available.
+ */
+ val checked: State<Boolean?>
+
+ /**
+ * Indicates whether this [SwitchPreference] is changeable.
+ *
+ * Not changeable [SwitchPreference] will be displayed in disabled style.
+ */
+ val changeable: State<Boolean>
+ get() = stateOf(true)
+
+ /**
+ * The switch change handler of this [SwitchPreference].
+ *
+ * If `null`, this [SwitchPreference] is not [toggleable].
+ */
+ val onCheckedChange: ((newChecked: Boolean) -> Unit)?
+}
+
+/**
+ * SwitchPreference widget.
+ *
+ * Data is provided through [SwitchPreferenceModel].
+ */
+@Composable
+fun SwitchPreference(model: SwitchPreferenceModel) {
+ InternalSwitchPreference(
+ title = model.title,
+ summary = model.summary,
+ checked = model.checked,
+ changeable = model.changeable,
+ onCheckedChange = model.onCheckedChange,
+ )
+}
+
+@Composable
+internal fun InternalSwitchPreference(
+ title: String,
+ summary: State<String> = "".toState(),
+ checked: State<Boolean?>,
+ changeable: State<Boolean> = true.toState(),
+ paddingStart: Dp = SettingsDimension.itemPaddingStart,
+ paddingEnd: Dp = SettingsDimension.itemPaddingEnd,
+ paddingVertical: Dp = SettingsDimension.itemPaddingVertical,
+ onCheckedChange: ((newChecked: Boolean) -> Unit)?,
+) {
+ val checkedValue = checked.value
+ val indication = LocalIndication.current
+ val modifier = remember(checkedValue) {
+ if (checkedValue != null && onCheckedChange != null) {
+ Modifier.toggleable(
+ value = checkedValue,
+ interactionSource = MutableInteractionSource(),
+ indication = indication,
+ enabled = changeable.value,
+ role = Role.Switch,
+ onValueChange = onCheckedChange,
+ )
+ } else Modifier
+ }
+ BasePreference(
+ title = title,
+ summary = summary,
+ modifier = modifier,
+ enabled = changeable,
+ paddingStart = paddingStart,
+ paddingEnd = paddingEnd,
+ paddingVertical = paddingVertical,
+ ) {
+ SettingsSwitch(checked = checked, changeable = changeable)
+ }
+}
+
+@Preview
+@Composable
+private fun SwitchPreferencePreview() {
+ SettingsTheme {
+ Column {
+ InternalSwitchPreference(
+ title = "Use Dark theme",
+ checked = true.toState(),
+ onCheckedChange = {},
+ )
+ InternalSwitchPreference(
+ title = "Use Dark theme",
+ summary = "Summary".toState(),
+ checked = false.toState(),
+ onCheckedChange = {},
+ )
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt
new file mode 100644
index 000000000000..41fd03b08563
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget.ui
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Info
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+@Composable
+fun Footer(footerText: String) {
+ if (footerText.isEmpty()) return
+ Column(Modifier.padding(SettingsDimension.itemPadding)) {
+ SettingsIcon(imageVector = Icons.Outlined.Info, contentDescription = null)
+ Spacer(modifier = Modifier.height(SettingsDimension.itemPaddingVertical))
+ SettingsBody(footerText)
+ }
+}
+
+@Preview
+@Composable
+private fun FooterPreview() {
+ SettingsTheme {
+ Footer("Footer text always at the end of page.")
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Icon.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Icon.kt
new file mode 100644
index 000000000000..cb08cdb20885
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Icon.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget.ui
+
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+
+@Composable
+fun SettingsIcon(
+ imageVector: ImageVector,
+ contentDescription: String?,
+) {
+ Icon(
+ imageVector = imageVector,
+ contentDescription = contentDescription,
+ modifier = Modifier.size(SettingsDimension.itemIconSize),
+ tint = MaterialTheme.colorScheme.onSurface,
+ )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt
new file mode 100644
index 000000000000..0454ac37cfb7
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget.ui
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AccessAlarm
+import androidx.compose.material.icons.outlined.MusicNote
+import androidx.compose.material.icons.outlined.MusicOff
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Slider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.BaseLayout
+import kotlin.math.roundToInt
+
+/**
+ * The widget model for [SettingsSlider] widget.
+ */
+interface SettingsSliderModel {
+ /**
+ * The title of this [SettingsSlider].
+ */
+ val title: String
+
+ /**
+ * The initial position of the [SettingsSlider].
+ */
+ val initValue: Int
+
+ /**
+ * The value range for this [SettingsSlider].
+ */
+ val valueRange: IntRange
+ get() = 0..100
+
+ /**
+ * The lambda to be invoked during the value change by dragging or a click. This callback is
+ * used to get the real time value of the [SettingsSlider].
+ */
+ val onValueChange: ((value: Int) -> Unit)?
+ get() = null
+
+ /**
+ * The lambda to be invoked when value change has ended. This callback is used to get when the
+ * user has completed selecting a new value by ending a drag or a click.
+ */
+ val onValueChangeFinished: (() -> Unit)?
+ get() = null
+
+ /**
+ * The icon image for [SettingsSlider]. If not specified, the slider hides the icon by default.
+ */
+ val icon: ImageVector?
+ get() = null
+
+ /**
+ * Indicates whether to show step marks. If show step marks, when user finish sliding,
+ * the slider will automatically jump to the nearest step mark. Otherwise, the slider hides
+ * the step marks by default.
+ *
+ * The step is fixed to 1.
+ */
+ val showSteps: Boolean
+ get() = false
+}
+
+/**
+ * Settings slider widget.
+ *
+ * Data is provided through [SettingsSliderModel].
+ */
+@Composable
+fun SettingsSlider(model: SettingsSliderModel) {
+ SettingsSlider(
+ title = model.title,
+ initValue = model.initValue,
+ valueRange = model.valueRange,
+ onValueChange = model.onValueChange,
+ onValueChangeFinished = model.onValueChangeFinished,
+ icon = model.icon,
+ showSteps = model.showSteps,
+ )
+}
+
+@Composable
+internal fun SettingsSlider(
+ title: String,
+ initValue: Int,
+ valueRange: IntRange = 0..100,
+ onValueChange: ((value: Int) -> Unit)? = null,
+ onValueChangeFinished: (() -> Unit)? = null,
+ icon: ImageVector? = null,
+ showSteps: Boolean = false,
+ modifier: Modifier = Modifier,
+) {
+ var sliderPosition by rememberSaveable { mutableStateOf(initValue.toFloat()) }
+ BaseLayout(
+ title = title,
+ subTitle = {
+ Slider(
+ value = sliderPosition,
+ onValueChange = {
+ sliderPosition = it
+ onValueChange?.invoke(sliderPosition.roundToInt())
+ },
+ modifier = modifier,
+ valueRange = valueRange.first.toFloat()..valueRange.last.toFloat(),
+ steps = if (showSteps) (valueRange.count() - 2) else 0,
+ onValueChangeFinished = onValueChangeFinished,
+ )
+ },
+ icon = if (icon != null) ({
+ Icon(imageVector = icon, contentDescription = null)
+ }) else null,
+ )
+}
+
+@Preview
+@Composable
+private fun SettingsSliderPreview() {
+ SettingsTheme {
+ val initValue = 30
+ var sliderPosition by rememberSaveable { mutableStateOf(initValue) }
+ SettingsSlider(
+ title = "Alarm Volume",
+ initValue = 30,
+ onValueChange = { sliderPosition = it },
+ onValueChangeFinished = {
+ println("onValueChangeFinished: the value is $sliderPosition")
+ },
+ icon = Icons.Outlined.AccessAlarm,
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun SettingsSliderIconChangePreview() {
+ SettingsTheme {
+ var icon by remember { mutableStateOf(Icons.Outlined.MusicNote) }
+ SettingsSlider(
+ title = "Media Volume",
+ initValue = 40,
+ onValueChange = { it: Int ->
+ icon = if (it > 0) Icons.Outlined.MusicNote else Icons.Outlined.MusicOff
+ },
+ icon = icon,
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun SettingsSliderStepsPreview() {
+ SettingsTheme {
+ SettingsSlider(
+ title = "Display Text",
+ initValue = 2,
+ valueRange = 1..5,
+ showSteps = true,
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt
new file mode 100644
index 000000000000..45d5f6baa9cb
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget.ui
+
+import androidx.compose.material3.Checkbox
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SettingsSwitch(
+ checked: State<Boolean?>,
+ changeable: State<Boolean>,
+ onCheckedChange: ((newChecked: Boolean) -> Unit)? = null,
+) {
+ // TODO: Replace Checkbox with Switch when the androidx.compose.material3_material3 library is
+ // updated to date.
+ val checkedValue = checked.value
+ if (checkedValue != null) {
+ Checkbox(
+ checked = checkedValue,
+ onCheckedChange = onCheckedChange,
+ enabled = changeable.value,
+ )
+ } else {
+ Checkbox(
+ checked = false,
+ onCheckedChange = null,
+ enabled = false,
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
new file mode 100644
index 000000000000..a414c89dc24c
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget.ui
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+
+@Composable
+fun SettingsTitle(title: State<String>) {
+ SettingsTitle(title.value)
+}
+
+@Composable
+fun SettingsTitle(title: String) {
+ Text(
+ text = title,
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.titleMedium,
+ )
+}
+
+@Composable
+fun SettingsBody(body: State<String>) {
+ SettingsBody(body.value)
+}
+
+@Composable
+fun SettingsBody(body: String) {
+ if (body.isNotEmpty()) {
+ Text(
+ text = body,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/build.gradle b/packages/SettingsLib/Spa/tests/build.gradle
index 707017e7e17f..be5a5ec40c4f 100644
--- a/packages/SettingsLib/Spa/tests/build.gradle
+++ b/packages/SettingsLib/Spa/tests/build.gradle
@@ -24,7 +24,7 @@ android {
compileSdk 33
defaultConfig {
- minSdk minSdk_version
+ minSdk spa_min_sdk
targetSdk 33
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -50,7 +50,7 @@ android {
compose true
}
composeOptions {
- kotlinCompilerExtensionVersion compose_version
+ kotlinCompilerExtensionVersion jetpack_compose_version
}
packagingOptions {
resources {
@@ -62,6 +62,6 @@ android {
dependencies {
androidTestImplementation(project(":spa"))
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.3'
- androidTestImplementation("androidx.compose.ui:ui-test-junit4:$compose_version")
- androidTestDebugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
+ androidTestImplementation("androidx.compose.ui:ui-test-junit4:$jetpack_compose_version")
+ androidTestDebugImplementation "androidx.compose.ui:ui-test-manifest:$jetpack_compose_version"
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SwitchPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SwitchPreferenceTest.kt
new file mode 100644
index 000000000000..d6c8fbc9dc9e
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SwitchPreferenceTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget.preference
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsOff
+import androidx.compose.ui.test.assertIsOn
+import androidx.compose.ui.test.isToggleable
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SwitchPreferenceTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun title_displayed() {
+ composeTestRule.setContent {
+ TestSwitchPreference(changeable = true)
+ }
+
+ composeTestRule.onNodeWithText("SwitchPreference").assertIsDisplayed()
+ }
+
+ @Test
+ fun toggleable_initialStateIsCorrect() {
+ composeTestRule.setContent {
+ TestSwitchPreference(changeable = true)
+ }
+
+ composeTestRule.onNode(isToggleable()).assertIsOff()
+ }
+
+ @Test
+ fun click_changeable_withEffect() {
+ composeTestRule.setContent {
+ TestSwitchPreference(changeable = true)
+ }
+
+ composeTestRule.onNodeWithText("SwitchPreference").performClick()
+ composeTestRule.onNode(isToggleable()).assertIsOn()
+ }
+
+ @Test
+ fun click_notChangeable_noEffect() {
+ composeTestRule.setContent {
+ TestSwitchPreference(changeable = false)
+ }
+
+ composeTestRule.onNodeWithText("SwitchPreference").performClick()
+ composeTestRule.onNode(isToggleable()).assertIsOff()
+ }
+}
+
+@Composable
+private fun TestSwitchPreference(changeable: Boolean) {
+ val checked = rememberSaveable { mutableStateOf(false) }
+ SwitchPreference(remember {
+ object : SwitchPreferenceModel {
+ override val title = "SwitchPreference"
+ override val checked = checked
+ override val changeable = stateOf(changeable)
+ override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+ }
+ })
+}
diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp
new file mode 100644
index 000000000000..48f7ff270ac7
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -0,0 +1,33 @@
+//
+// Copyright (C) 2022 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 {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+ name: "SpaPrivilegedLib",
+
+ srcs: ["src/**/*.kt"],
+
+ static_libs: [
+ "SpaLib",
+ "SettingsLib",
+ "androidx.compose.runtime_runtime",
+ ],
+ kotlincflags: ["-Xjvm-default=all"],
+ min_sdk_version: "31",
+}
diff --git a/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml b/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml
new file mode 100644
index 000000000000..2efa10744bb3
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+-->
+
+<manifest package="com.android.settingslib.spaprivileged" />
diff --git a/packages/SettingsLib/SpaPrivileged/OWNERS b/packages/SettingsLib/SpaPrivileged/OWNERS
new file mode 100644
index 000000000000..9256ca5cc2b0
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/packages/SettingsLib/Spa/OWNERS
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRepository.kt
new file mode 100644
index 000000000000..a6378ef53437
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRepository.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spaprivileged.framework.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.graphics.drawable.Drawable
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.produceState
+import com.android.settingslib.Utils
+import com.android.settingslib.spa.framework.compose.rememberContext
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+@Composable
+fun rememberAppRepository(): AppRepository = rememberContext(::AppRepositoryImpl)
+
+interface AppRepository {
+ @Composable
+ fun produceLabel(app: ApplicationInfo): State<String>
+
+ @Composable
+ fun produceIcon(app: ApplicationInfo): State<Drawable?>
+}
+
+private class AppRepositoryImpl(private val context: Context) : AppRepository {
+ private val packageManager = context.packageManager
+
+ @Composable
+ override fun produceLabel(app: ApplicationInfo) = produceState(initialValue = "", app) {
+ withContext(Dispatchers.Default) {
+ value = app.loadLabel(packageManager).toString()
+ }
+ }
+
+ @Composable
+ override fun produceIcon(app: ApplicationInfo) =
+ produceState<Drawable?>(initialValue = null, app) {
+ withContext(Dispatchers.Default) {
+ value = Utils.getBadgedIcon(context, app)
+ }
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt
new file mode 100644
index 000000000000..5a3e66619c39
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spaprivileged.framework.app
+
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+
+object PackageManagers {
+ fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo =
+ PackageManager.getPackageInfoAsUserCached(packageName, 0, userId)
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
new file mode 100644
index 000000000000..5ae514cfb524
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spaprivileged.template.app
+
+import android.content.pm.ApplicationInfo
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.settingslib.spa.framework.compose.rememberDrawablePainter
+import com.android.settingslib.spa.widget.ui.SettingsBody
+import com.android.settingslib.spa.widget.ui.SettingsTitle
+import com.android.settingslib.spaprivileged.framework.app.PackageManagers
+import com.android.settingslib.spaprivileged.framework.app.rememberAppRepository
+
+@Composable
+fun AppInfo(packageName: String, userId: Int) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally) {
+ val packageInfo = remember { PackageManagers.getPackageInfoAsUser(packageName, userId) }
+ Box(modifier = Modifier.padding(8.dp)) {
+ AppIcon(app = packageInfo.applicationInfo, size = 48)
+ }
+ AppLabel(packageInfo.applicationInfo)
+ Spacer(modifier = Modifier.height(4.dp))
+ SettingsBody(packageInfo.versionName)
+ }
+}
+
+@Composable
+fun AppIcon(app: ApplicationInfo, size: Int) {
+ val appRepository = rememberAppRepository()
+ Image(
+ painter = rememberDrawablePainter(appRepository.produceIcon(app).value),
+ contentDescription = null,
+ modifier = Modifier.size(size.dp)
+ )
+}
+
+@Composable
+fun AppLabel(app: ApplicationInfo) {
+ val appRepository = rememberAppRepository()
+ SettingsTitle(appRepository.produceLabel(app))
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index d9262cce3cb9..766c036d521c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -528,7 +528,7 @@ public class InfoMediaManager extends MediaManager {
class RouterManagerCallback implements MediaRouter2Manager.Callback {
@Override
- public void onRoutesAdded(List<MediaRoute2Info> routes) {
+ public void onRoutesUpdated() {
refreshDevices();
}
@@ -540,16 +540,6 @@ public class InfoMediaManager extends MediaManager {
}
@Override
- public void onRoutesChanged(List<MediaRoute2Info> routes) {
- refreshDevices();
- }
-
- @Override
- public void onRoutesRemoved(List<MediaRoute2Info> routes) {
- refreshDevices();
- }
-
- @Override
public void onTransferred(RoutingSessionInfo oldSession, RoutingSessionInfo newSession) {
if (DEBUG) {
Log.d(TAG, "onTransferred() oldSession : " + oldSession.getName()
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index ee7b7d6b180f..f4af6e852580 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -112,7 +112,7 @@ public class InfoMediaManagerTest {
final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
assertThat(mediaDevice).isNull();
- mInfoMediaManager.mMediaRouterCallback.onRoutesAdded(routes);
+ mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
@@ -135,7 +135,7 @@ public class InfoMediaManagerTest {
assertThat(mediaDevice).isNull();
mInfoMediaManager.mPackageName = "";
- mInfoMediaManager.mMediaRouterCallback.onRoutesAdded(routes);
+ mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
@@ -199,7 +199,7 @@ public class InfoMediaManagerTest {
final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
assertThat(mediaDevice).isNull();
- mInfoMediaManager.mMediaRouterCallback.onRoutesChanged(routes);
+ mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
@@ -222,7 +222,7 @@ public class InfoMediaManagerTest {
assertThat(mediaDevice).isNull();
mInfoMediaManager.mPackageName = "";
- mInfoMediaManager.mMediaRouterCallback.onRoutesChanged(routes);
+ mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
@@ -263,7 +263,7 @@ public class InfoMediaManagerTest {
final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
assertThat(mediaDevice).isNull();
- mInfoMediaManager.mMediaRouterCallback.onRoutesRemoved(routes);
+ mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
@@ -286,7 +286,7 @@ public class InfoMediaManagerTest {
assertThat(mediaDevice).isNull();
mInfoMediaManager.mPackageName = "";
- mInfoMediaManager.mMediaRouterCallback.onRoutesRemoved(routes);
+ mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 8b5e3c11b2f6..d27b9ced66a7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -54,6 +54,7 @@ import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.hardware.SensorPrivacyManager;
+import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
@@ -2019,12 +2020,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
// in case authenticators aren't registered yet at this point:
mAuthController.addCallback(new AuthController.Callback() {
@Override
- public void onAllAuthenticatorsRegistered() {
+ public void onAllAuthenticatorsRegistered(
+ @BiometricAuthenticator.Modality int modality) {
mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE));
}
@Override
- public void onEnrollmentsChanged() {
+ public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE));
}
});
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 06e1828ef9f4..d6974dfac570 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -16,6 +16,7 @@
package com.android.keyguard;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
import static com.android.keyguard.LockIconView.ICON_FINGERPRINT;
@@ -29,6 +30,7 @@ import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.AnimatedStateListDrawable;
+import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricSourceType;
import android.os.Process;
import android.os.VibrationAttributes;
@@ -701,13 +703,17 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
@Override
- public void onAllAuthenticatorsRegistered() {
- updateUdfpsConfig();
+ public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) {
+ if (modality == TYPE_FINGERPRINT) {
+ updateUdfpsConfig();
+ }
}
@Override
- public void onEnrollmentsChanged() {
- updateUdfpsConfig();
+ public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
+ if (modality == TYPE_FINGERPRINT) {
+ updateUdfpsConfig();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 47ff59cfc281..282f25104c44 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -46,6 +46,7 @@ import android.hardware.biometrics.PromptInfo;
import android.hardware.display.DisplayManager;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
@@ -156,25 +157,6 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
}
};
- private final IFingerprintAuthenticatorsRegisteredCallback
- mFingerprintAuthenticatorsRegisteredCallback =
- new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
- @Override
- public void onAllAuthenticatorsRegistered(
- List<FingerprintSensorPropertiesInternal> sensors) {
- mHandler.post(() -> handleAllFingerprintAuthenticatorsRegistered(sensors));
- }
- };
-
- private final BiometricStateListener mBiometricStateListener =
- new BiometricStateListener() {
- @Override
- public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
- mHandler.post(
- () -> handleEnrollmentsChanged(userId, sensorId, hasEnrollments));
- }
- };
-
@VisibleForTesting
final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
@@ -249,8 +231,8 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
List<FingerprintSensorPropertiesInternal> sensors) {
mExecution.assertIsMainThread();
if (DEBUG) {
- Log.d(TAG, "handleAllAuthenticatorsRegistered | sensors: " + Arrays.toString(
- sensors.toArray()));
+ Log.d(TAG, "handleAllFingerprintAuthenticatorsRegistered | sensors: "
+ + Arrays.toString(sensors.toArray()));
}
mAllFingerprintAuthenticatorsRegistered = true;
mFpProps = sensors;
@@ -292,15 +274,42 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
mSidefpsController = mSidefpsControllerFactory.get();
}
- mFingerprintManager.registerBiometricStateListener(mBiometricStateListener);
+ mFingerprintManager.registerBiometricStateListener(new BiometricStateListener() {
+ @Override
+ public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
+ mHandler.post(() -> handleEnrollmentsChanged(
+ TYPE_FINGERPRINT, userId, sensorId, hasEnrollments));
+ }
+ });
updateFingerprintLocation();
for (Callback cb : mCallbacks) {
- cb.onAllAuthenticatorsRegistered();
+ cb.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT);
}
}
- private void handleEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
+ private void handleAllFaceAuthenticatorsRegistered(List<FaceSensorPropertiesInternal> sensors) {
+ mExecution.assertIsMainThread();
+ if (DEBUG) {
+ Log.d(TAG, "handleAllFaceAuthenticatorsRegistered | sensors: " + Arrays.toString(
+ sensors.toArray()));
+ }
+
+ mFaceManager.registerBiometricStateListener(new BiometricStateListener() {
+ @Override
+ public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
+ mHandler.post(() -> handleEnrollmentsChanged(
+ TYPE_FACE, userId, sensorId, hasEnrollments));
+ }
+ });
+
+ for (Callback cb : mCallbacks) {
+ cb.onAllAuthenticatorsRegistered(TYPE_FACE);
+ }
+ }
+
+ private void handleEnrollmentsChanged(@Modality int modality, int userId, int sensorId,
+ boolean hasEnrollments) {
mExecution.assertIsMainThread();
Log.d(TAG, "handleEnrollmentsChanged, userId: " + userId + ", sensorId: " + sensorId
+ ", hasEnrollments: " + hasEnrollments);
@@ -314,7 +323,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
}
}
for (Callback cb : mCallbacks) {
- cb.onEnrollmentsChanged();
+ cb.onEnrollmentsChanged(modality);
}
}
@@ -700,7 +709,26 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
if (mFingerprintManager != null) {
mFingerprintManager.addAuthenticatorsRegisteredCallback(
- mFingerprintAuthenticatorsRegisteredCallback);
+ new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+ @Override
+ public void onAllAuthenticatorsRegistered(
+ List<FingerprintSensorPropertiesInternal> sensors) {
+ mHandler.post(() ->
+ handleAllFingerprintAuthenticatorsRegistered(sensors));
+ }
+ });
+ }
+ if (mFaceManager != null) {
+ mFaceManager.addAuthenticatorsRegisteredCallback(
+ new IFaceAuthenticatorsRegisteredCallback.Stub() {
+ @Override
+ public void onAllAuthenticatorsRegistered(
+ List<FaceSensorPropertiesInternal> sensors) {
+ mHandler.post(() ->
+ handleAllFaceAuthenticatorsRegistered(sensors));
+ }
+ }
+ );
}
mStableDisplaySize = mDisplayManager.getStableDisplaySize();
@@ -1116,13 +1144,13 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
* Called when authenticators are registered. If authenticators are already
* registered before this call, this callback will never be triggered.
*/
- default void onAllAuthenticatorsRegistered() {}
+ default void onAllAuthenticatorsRegistered(@Modality int modality) {}
/**
- * Called when UDFPS enrollments have changed. This is called after boot and on changes to
+ * Called when enrollments have changed. This is called after boot and on changes to
* enrollment.
*/
- default void onEnrollmentsChanged() {}
+ default void onEnrollmentsChanged(@Modality int modality) {}
/**
* Called when the biometric prompt starts showing.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 38fab8ffbfad..fd3f6007d8a9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -308,7 +308,7 @@ class AuthRippleController @Inject constructor(
private val authControllerCallback =
object : AuthController.Callback {
- override fun onAllAuthenticatorsRegistered() {
+ override fun onAllAuthenticatorsRegistered(modality: Int) {
updateUdfpsDependentParams()
updateSensorLocation()
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index a9e310d25f9c..7da2cf150147 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -16,12 +16,15 @@
package com.android.systemui.doze;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
import static com.android.systemui.doze.DozeMachine.State.DOZE;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE;
+import android.hardware.biometrics.BiometricAuthenticator;
import android.os.Handler;
import android.util.Log;
import android.view.Display;
@@ -232,13 +235,17 @@ public class DozeScreenState implements DozeMachine.Part {
private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
@Override
- public void onAllAuthenticatorsRegistered() {
- updateUdfpsController();
+ public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) {
+ if (modality == TYPE_FINGERPRINT) {
+ updateUdfpsController();
+ }
}
@Override
- public void onEnrollmentsChanged() {
- updateUdfpsController();
+ public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
+ if (modality == TYPE_FINGERPRINT) {
+ updateUdfpsController();
+ }
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index da6c163b1eea..997a6e554364 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -16,6 +16,8 @@
package com.android.systemui.doze;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
import static com.android.systemui.doze.DozeLog.REASON_SENSOR_QUICK_PICKUP;
import static com.android.systemui.doze.DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS;
import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_DISPLAY;
@@ -29,6 +31,7 @@ import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.hardware.TriggerEvent;
import android.hardware.TriggerEventListener;
+import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.display.AmbientDisplayConfiguration;
import android.net.Uri;
import android.os.Handler;
@@ -835,13 +838,17 @@ public class DozeSensors {
private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
@Override
- public void onAllAuthenticatorsRegistered() {
- updateUdfpsEnrolled();
+ public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) {
+ if (modality == TYPE_FINGERPRINT) {
+ updateUdfpsEnrolled();
+ }
}
@Override
- public void onEnrollmentsChanged() {
- updateUdfpsEnrolled();
+ public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
+ if (modality == TYPE_FINGERPRINT) {
+ updateUdfpsEnrolled();
+ }
}
private void updateUdfpsEnrolled() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index c67737136b3b..cde30af447f6 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -16,6 +16,7 @@
package com.android.keyguard;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
@@ -1005,7 +1006,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
// WHEN udfps is now enrolled
when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
- callback.onEnrollmentsChanged();
+ callback.onEnrollmentsChanged(TYPE_FINGERPRINT);
// THEN isUdfspEnrolled is TRUE
assertThat(mKeyguardUpdateMonitor.isUdfpsEnrolled()).isTrue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index d158892e4ec5..e0d1f7a19130 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -60,6 +60,9 @@ import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorProperties;
import android.hardware.display.DisplayManager;
import android.hardware.face.FaceManager;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -154,7 +157,9 @@ public class AuthControllerTest extends SysuiTestCase {
@Mock
private InteractionJankMonitor mInteractionJankMonitor;
@Captor
- ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mAuthenticatorsRegisteredCaptor;
+ ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mFpAuthenticatorsRegisteredCaptor;
+ @Captor
+ ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mFaceAuthenticatorsRegisteredCaptor;
@Captor
ArgumentCaptor<BiometricStateListener> mBiometricStateCaptor;
@Captor
@@ -193,25 +198,38 @@ public class AuthControllerTest extends SysuiTestCase {
when(mDisplayManager.getStableDisplaySize()).thenReturn(new Point());
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
-
- final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
- componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
- "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
- "00000001" /* serialNumber */, "" /* softwareVersion */));
- componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
- "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
- "vendor/version/revision" /* softwareVersion */));
-
- FingerprintSensorPropertiesInternal prop = new FingerprintSensorPropertiesInternal(
- 1 /* sensorId */,
- SensorProperties.STRENGTH_STRONG,
- 1 /* maxEnrollmentsPerUser */,
- componentInfo,
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
- true /* resetLockoutRequireHardwareAuthToken */);
- List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
- props.add(prop);
- when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
+ when(mFaceManager.isHardwareDetected()).thenReturn(true);
+
+ final List<ComponentInfoInternal> fpComponentInfo = List.of(
+ new ComponentInfoInternal("faceSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */, "" /* softwareVersion */));
+ final List<ComponentInfoInternal> faceComponentInfo = List.of(
+ new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
+ "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
+ "vendor/version/revision" /* softwareVersion */));
+
+ final List<FingerprintSensorPropertiesInternal> fpProps = List.of(
+ new FingerprintSensorPropertiesInternal(
+ 1 /* sensorId */,
+ SensorProperties.STRENGTH_STRONG,
+ 1 /* maxEnrollmentsPerUser */,
+ fpComponentInfo,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+ true /* resetLockoutRequireHardwareAuthToken */));
+ when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(fpProps);
+
+ final List<FaceSensorPropertiesInternal> faceProps = List.of(
+ new FaceSensorPropertiesInternal(
+ 2 /* sensorId */,
+ SensorProperties.STRENGTH_STRONG,
+ 1 /* maxEnrollmentsPerUser */,
+ fpComponentInfo,
+ FaceSensorProperties.TYPE_RGB,
+ true /* supportsFaceDetection */,
+ true /* supportsSelfIllumination */,
+ true /* resetLockoutRequireHardwareAuthToken */));
+ when(mFaceManager.getSensorPropertiesInternal()).thenReturn(faceProps);
mAuthController = new TestableAuthController(mContextSpy, mExecution, mCommandQueue,
mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager,
@@ -219,12 +237,15 @@ public class AuthControllerTest extends SysuiTestCase {
mAuthController.start();
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
- mAuthenticatorsRegisteredCaptor.capture());
+ mFpAuthenticatorsRegisteredCaptor.capture());
+ verify(mFaceManager).addAuthenticatorsRegisteredCallback(
+ mFaceAuthenticatorsRegisteredCaptor.capture());
when(mStatusBarStateController.isDozing()).thenReturn(false);
verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
- mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(props);
+ mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(fpProps);
+ mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(faceProps);
// Ensures that the operations posted on the handler get executed.
mTestableLooper.processAllMessages();
@@ -237,6 +258,7 @@ public class AuthControllerTest extends SysuiTestCase {
throws RemoteException {
// This test is sensitive to prior FingerprintManager interactions.
reset(mFingerprintManager);
+ reset(mFaceManager);
// This test requires an uninitialized AuthController.
AuthController authController = new TestableAuthController(mContextSpy, mExecution,
@@ -246,21 +268,27 @@ public class AuthControllerTest extends SysuiTestCase {
authController.start();
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
- mAuthenticatorsRegisteredCaptor.capture());
+ mFpAuthenticatorsRegisteredCaptor.capture());
+ verify(mFaceManager).addAuthenticatorsRegisteredCallback(
+ mFaceAuthenticatorsRegisteredCaptor.capture());
mTestableLooper.processAllMessages();
verify(mFingerprintManager, never()).registerBiometricStateListener(any());
+ verify(mFaceManager, never()).registerBiometricStateListener(any());
- mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(new ArrayList<>());
+ mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
+ mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
mTestableLooper.processAllMessages();
verify(mFingerprintManager).registerBiometricStateListener(any());
+ verify(mFaceManager).registerBiometricStateListener(any());
}
@Test
public void testDoesNotCrash_afterEnrollmentsChangedForUnknownSensor() throws RemoteException {
// This test is sensitive to prior FingerprintManager interactions.
reset(mFingerprintManager);
+ reset(mFaceManager);
// This test requires an uninitialized AuthController.
AuthController authController = new TestableAuthController(mContextSpy, mExecution,
@@ -270,18 +298,25 @@ public class AuthControllerTest extends SysuiTestCase {
authController.start();
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
- mAuthenticatorsRegisteredCaptor.capture());
+ mFpAuthenticatorsRegisteredCaptor.capture());
+ verify(mFaceManager).addAuthenticatorsRegisteredCallback(
+ mFaceAuthenticatorsRegisteredCaptor.capture());
// Emulates a device with no authenticators (empty list).
- mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(new ArrayList<>());
+ mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
+ mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
mTestableLooper.processAllMessages();
verify(mFingerprintManager).registerBiometricStateListener(
mBiometricStateCaptor.capture());
+ verify(mFaceManager).registerBiometricStateListener(
+ mBiometricStateCaptor.capture());
// Enrollments changed for an unknown sensor.
- mBiometricStateCaptor.getValue().onEnrollmentsChanged(0 /* userId */,
- 0xbeef /* sensorId */, true /* hasEnrollments */);
+ for (BiometricStateListener listener : mBiometricStateCaptor.getAllValues()) {
+ listener.onEnrollmentsChanged(0 /* userId */,
+ 0xbeef /* sensorId */, true /* hasEnrollments */);
+ }
mTestableLooper.processAllMessages();
// Nothing should crash.
@@ -827,4 +862,3 @@ public class AuthControllerTest extends SysuiTestCase {
}
}
}
-
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index 9ffc5a57cef6..b33f9a7f3933 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.doze;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
import static com.android.systemui.doze.DozeLog.REASON_SENSOR_TAP;
import static com.android.systemui.doze.DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS;
import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN;
@@ -412,7 +414,7 @@ public class DozeSensorsTest extends SysuiTestCase {
// WHEN enrollment changes to TRUE
when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
- mAuthControllerCallback.onEnrollmentsChanged();
+ mAuthControllerCallback.onEnrollmentsChanged(TYPE_FINGERPRINT);
// THEN mConfigured = TRUE
assertTrue(triggerSensor.mConfigured);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
index 24d051508fde..5ec6bdf3c00b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.keyguard;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.keyguard.LockIconView.ICON_LOCK;
import static com.android.keyguard.LockIconView.ICON_UNLOCK;
@@ -219,7 +221,7 @@ public class LockIconViewControllerTest extends SysuiTestCase {
Pair<Float, PointF> udfps = setupUdfps();
// WHEN all authenticators are registered
- mAuthControllerCallback.onAllAuthenticatorsRegistered();
+ mAuthControllerCallback.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT);
mDelayableExecutor.runAllReady();
// THEN lock icon view location is updated with the same coordinates as auth controller vals
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index f903c2005ae3..a94bfe281be1 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -302,6 +302,10 @@ message SystemMessage {
// Package: android
NOTE_WIFI_APM_NOTIFICATION = 73;
+ // Inform the user of bluetooth apm state changes.
+ // Package: android
+ NOTE_BT_APM_NOTIFICATION = 74;
+
// ADD_NEW_IDS_ABOVE_THIS_LINE
// Legacy IDs with arbitrary values appear below
// Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index 1d457aa933d7..02c6ca20d34e 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -479,6 +479,10 @@ public final class DropBoxManagerService extends SystemService {
if (length > max) {
// Log and fall through to create empty tombstone below
Slog.w(TAG, "Dropping: " + tag + " (" + length + " > " + max + " bytes)");
+ logDropboxDropped(
+ FrameworkStatsLog.DROPBOX_ENTRY_DROPPED__DROP_REASON__ENTRY_TOO_LARGE,
+ tag,
+ 0);
} else {
temp = new File(mDropBoxDir, "drop" + Thread.currentThread().getId() + ".tmp");
try (FileOutputStream out = new FileOutputStream(temp)) {
diff --git a/services/core/java/com/android/server/am/LmkdStatsReporter.java b/services/core/java/com/android/server/am/LmkdStatsReporter.java
index 91588913c33c..4380b42ee54c 100644
--- a/services/core/java/com/android/server/am/LmkdStatsReporter.java
+++ b/services/core/java/com/android/server/am/LmkdStatsReporter.java
@@ -50,7 +50,8 @@ public final class LmkdStatsReporter {
* Logs the event when LMKD kills a process to reduce memory pressure.
* Code: LMK_KILL_OCCURRED = 51
*/
- public static void logKillOccurred(DataInputStream inputData) {
+ public static void logKillOccurred(DataInputStream inputData, int totalForegroundServices,
+ int procsWithForegroundServices) {
try {
final long pgFault = inputData.readLong();
final long pgMajFault = inputData.readLong();
@@ -67,11 +68,10 @@ public final class LmkdStatsReporter {
final int thrashing = inputData.readInt();
final int maxThrashing = inputData.readInt();
final String procName = inputData.readUTF();
-
FrameworkStatsLog.write(FrameworkStatsLog.LMK_KILL_OCCURRED, uid, procName, oomScore,
pgFault, pgMajFault, rssInBytes, cacheInBytes, swapInBytes, processStartTimeNS,
minOomScore, freeMemKb, freeSwapKb, mapKillReason(killReason), thrashing,
- maxThrashing);
+ maxThrashing, totalForegroundServices, procsWithForegroundServices);
} catch (IOException e) {
Slog.e(TAG, "Invalid buffer data. Failed to log LMK_KILL_OCCURRED");
return;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 42792bf64f44..ccbca76d1868 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -814,7 +814,12 @@ public final class ProcessList {
< LmkdStatsReporter.KILL_OCCURRED_MSG_SIZE) {
return false;
}
- LmkdStatsReporter.logKillOccurred(inputData);
+ Pair<Integer, Integer> temp = getNumForegroundServices();
+ final int totalForegroundServices = temp.first;
+ final int procsWithForegroundServices = temp.second;
+ LmkdStatsReporter.logKillOccurred(inputData,
+ totalForegroundServices,
+ procsWithForegroundServices);
return true;
case LMK_STATE_CHANGED:
if (receivedLen
@@ -5123,6 +5128,26 @@ public final class ProcessList {
}
}
+ /**
+ * Get the number of foreground services in all processes and number of processes that have
+ * foreground service within.
+ */
+ Pair<Integer, Integer> getNumForegroundServices() {
+ int numForegroundServices = 0;
+ int procs = 0;
+ synchronized (mService) {
+ for (int i = 0, size = mLruProcesses.size(); i < size; i++) {
+ ProcessRecord pr = mLruProcesses.get(i);
+ int numFgs = pr.mServices.getNumForegroundServices();
+ if (numFgs > 0) {
+ numForegroundServices += numFgs;
+ procs++;
+ }
+ }
+ }
+ return new Pair<>(numForegroundServices, procs);
+ }
+
private final class ImperceptibleKillRunner extends IUidObserver.Stub {
private static final String EXTRA_PID = "pid";
private static final String EXTRA_UID = "uid";
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 9951e983a752..67eb675503ad 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -180,6 +180,16 @@ final class ProcessServiceRecord {
mRepFgServiceTypes = foregroundServiceTypes;
}
+ int getNumForegroundServices() {
+ int count = 0;
+ for (int i = 0, serviceCount = mServices.size(); i < serviceCount; i++) {
+ if (mServices.valueAt(i).isForeground) {
+ count++;
+ }
+ }
+ return count;
+ }
+
void updateHasTopStartedAlmostPerceptibleServices() {
mHasTopStartedAlmostPerceptibleServices = false;
mLastTopStartedAlmostPerceptibleBindRequestUptimeMs = 0;
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index d0817b078b61..470de8ce0955 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -99,6 +99,7 @@ public class SettingsToPropertiesMapper {
DeviceConfig.NAMESPACE_SWCODEC_NATIVE,
DeviceConfig.NAMESPACE_TETHERING,
DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE,
+ DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT,
DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE,
DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT,
DeviceConfig.NAMESPACE_MEMORY_SAFETY_NATIVE,
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java
new file mode 100644
index 000000000000..0f1fe68ad1d7
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 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.biometrics.sensors;
+
+import android.annotation.NonNull;
+import android.hardware.biometrics.SensorPropertiesInternal;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Common attributes for all biometric service providers.
+ *
+ * @param <T> Internal settings type.
+ */
+public interface BiometricServiceProvider<T extends SensorPropertiesInternal> {
+
+ /** Checks if the specified sensor is owned by this provider. */
+ boolean containsSensor(int sensorId);
+
+ /** All sensor properties. */
+ @NonNull
+ List<T> getSensorProperties();
+
+ /** Properties for the given sensor id. */
+ @NonNull
+ T getSensorProperties(int sensorId);
+
+ boolean isHardwareDetected(int sensorId);
+
+ /** If the user has any enrollments for the given sensor. */
+ boolean hasEnrollments(int sensorId, int userId);
+
+ long getAuthenticatorId(int sensorId, int userId);
+
+ @LockoutTracker.LockoutMode
+ int getLockoutModeForUser(int sensorId, int userId);
+
+ void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
+ boolean clearSchedulerBuffer);
+
+ void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd);
+
+ void dumpInternal(int sensorId, @NonNull PrintWriter pw);
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceRegistry.java
new file mode 100644
index 000000000000..7574523f0662
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceRegistry.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2022 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.biometrics.sensors;
+
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.biometrics.IBiometricAuthenticator;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.SensorPropertiesInternal;
+import android.os.Handler;
+import android.os.IInterface;
+import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.ServiceThread;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ * Container for all BiometricServiceProvider implementations.
+ *
+ * @param <T> The service provider type.
+ * @param <P> The internal properties type.
+ * @param <C> The registration callback for {@link #invokeRegisteredCallback(IInterface, List)}.
+ */
+public abstract class BiometricServiceRegistry<T extends BiometricServiceProvider<P>,
+ P extends SensorPropertiesInternal,
+ C extends IInterface> {
+
+ private static final String TAG = "BiometricServiceRegistry";
+
+ // Volatile so they can be read without a lock once all services are registered.
+ // But, ideally remove this and provide immutable copies via the callback instead.
+ @Nullable
+ private volatile List<T> mServiceProviders;
+ @Nullable
+ private volatile List<P> mAllProps;
+
+ @NonNull
+ private final Supplier<IBiometricService> mBiometricServiceSupplier;
+ @NonNull
+ private final RemoteCallbackList<C> mRegisteredCallbacks = new RemoteCallbackList<>();
+
+ public BiometricServiceRegistry(@NonNull Supplier<IBiometricService> biometricSupplier) {
+ mBiometricServiceSupplier = biometricSupplier;
+ }
+
+ /**
+ * Register an implementation by creating a new authenticator and initializing it via
+ * {@link IBiometricService#registerAuthenticator(int, int, int, IBiometricAuthenticator)}
+ * using the given properties.
+ *
+ * @param service service to register with
+ * @param props internal properties to initialize the authenticator
+ */
+ protected abstract void registerService(@NonNull IBiometricService service, @NonNull P props);
+
+ /**
+ * Invoke the callback to notify clients that all authenticators have been registered.
+ *
+ * @param callback callback to invoke
+ * @param allProps properties of all authenticators
+ */
+ protected abstract void invokeRegisteredCallback(@NonNull C callback,
+ @NonNull List<P> allProps) throws RemoteException;
+
+ /**
+ * Register all authenticators in a background thread.
+ *
+ * @param serviceProvider Supplier function that will be invoked on the background thread.
+ */
+ public void registerAll(Supplier<List<T>> serviceProvider) {
+ // Some HAL might not be started before the system service and will cause the code below
+ // to wait, and some of the operations below might take a significant amount of time to
+ // complete (calls to the HALs). To avoid blocking the rest of system server we put
+ // this on a background thread.
+ final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
+ true /* allowIo */);
+ thread.start();
+ final Handler handler = new Handler(thread.getLooper());
+ handler.post(() -> registerAllInBackground(serviceProvider));
+ thread.quitSafely();
+ }
+
+ /** Register authenticators now, only called by {@link #registerAll(Supplier).} */
+ @VisibleForTesting
+ public void registerAllInBackground(Supplier<List<T>> serviceProvider) {
+ List<T> providers = serviceProvider.get();
+ if (providers == null) {
+ providers = new ArrayList<>();
+ }
+
+ final IBiometricService biometricService = mBiometricServiceSupplier.get();
+ if (biometricService == null) {
+ throw new IllegalStateException("biometric service cannot be null");
+ }
+
+ // Register each sensor individually with BiometricService
+ final List<P> allProps = new ArrayList<>();
+ for (T provider : providers) {
+ final List<P> props = provider.getSensorProperties();
+ for (P prop : props) {
+ registerService(biometricService, prop);
+ }
+ allProps.addAll(props);
+ }
+
+ finishRegistration(providers, allProps);
+ }
+
+ private synchronized void finishRegistration(
+ @NonNull List<T> providers, @NonNull List<P> allProps) {
+ mServiceProviders = Collections.unmodifiableList(providers);
+ mAllProps = Collections.unmodifiableList(allProps);
+ broadcastAllAuthenticatorsRegistered();
+ }
+
+ /**
+ * Add a callback that will be invoked once the work from {@link #registerAll(Supplier)}
+ * has finished registering all providers (executes immediately if already done).
+ *
+ * @param callback registration callback
+ */
+ public synchronized void addAllRegisteredCallback(@Nullable C callback) {
+ if (callback == null) {
+ Slog.e(TAG, "addAllRegisteredCallback, callback is null");
+ return;
+ }
+
+ final boolean registered = mRegisteredCallbacks.register(callback);
+ final boolean allRegistered = mServiceProviders != null;
+ if (registered && allRegistered) {
+ broadcastAllAuthenticatorsRegistered();
+ } else if (!registered) {
+ Slog.e(TAG, "addAllRegisteredCallback failed to register callback");
+ }
+ }
+
+ private synchronized void broadcastAllAuthenticatorsRegistered() {
+ final int n = mRegisteredCallbacks.beginBroadcast();
+ for (int i = 0; i < n; ++i) {
+ final C cb = mRegisteredCallbacks.getBroadcastItem(i);
+ try {
+ invokeRegisteredCallback(cb, mAllProps);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception in broadcastAllAuthenticatorsRegistered", e);
+ } finally {
+ mRegisteredCallbacks.unregister(cb);
+ }
+ }
+ mRegisteredCallbacks.finishBroadcast();
+ }
+
+ /**
+ * Get a list of registered providers.
+ *
+ * Undefined until {@link #registerAll(Supplier)} has fired the completion callback.
+ */
+ @NonNull
+ public List<T> getProviders() {
+ return mServiceProviders != null ? mServiceProviders : Collections.emptyList();
+ }
+
+ /**
+ * Gets the provider for given sensor id or null if not registered.
+ *
+ * Undefined until {@link #registerAll(Supplier)} has fired the completion callback.
+ */
+ @Nullable
+ public T getProviderForSensor(int sensorId) {
+ if (mServiceProviders != null) {
+ for (T provider : mServiceProviders) {
+ if (provider.containsSensor(sensorId)) {
+ return provider;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * For devices with only a single provider, returns that provider.
+ * If no providers, or multiple providers exist, returns null.
+ *
+ * Undefined until {@link #registerAll(Supplier)} has fired the completion callback.
+ */
+ @Nullable
+ public Pair<Integer, T> getSingleProvider() {
+ if (mAllProps == null || mAllProps.size() != 1) {
+ Slog.e(TAG, "Multiple sensors found: " + mAllProps.size());
+ return null;
+ }
+
+ final int sensorId = mAllProps.get(0).sensorId;
+ final T provider = getProviderForSensor(sensorId);
+ if (provider != null) {
+ return new Pair<>(sensorId, provider);
+ }
+
+ Slog.e(TAG, "Single sensor, but provider not found");
+ return null;
+ }
+
+ /**
+ * Get the properties for all providers.
+ *
+ * Undefined until {@link #registerAll(Supplier)} has fired the completion callback.
+ */
+ @NonNull
+ public List<P> getAllProperties() {
+ return mAllProps != null ? mAllProps : Collections.emptyList();
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
index 0d789f7a1840..f8543162f95e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
@@ -23,32 +23,64 @@ import static android.hardware.biometrics.BiometricStateListener.STATE_IDLE;
import static android.hardware.biometrics.BiometricStateListener.STATE_KEYGUARD_AUTH;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.IBiometricStateListener;
+import android.hardware.biometrics.SensorPropertiesInternal;
import android.os.RemoteException;
+import android.os.UserManager;
import android.util.Slog;
import com.android.server.biometrics.Utils;
+import java.util.Collections;
+import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* A callback for receiving notifications about biometric sensor state changes.
+ *
+ * @param <T> service provider type
+ * @param <P> internal property type
*/
-public class BiometricStateCallback implements ClientMonitorCallback {
+public class BiometricStateCallback<T extends BiometricServiceProvider<P>,
+ P extends SensorPropertiesInternal> implements ClientMonitorCallback {
private static final String TAG = "BiometricStateCallback";
@NonNull
- private final CopyOnWriteArrayList<IBiometricStateListener>
- mBiometricStateListeners = new CopyOnWriteArrayList<>();
-
- private @BiometricStateListener.State int mBiometricState;
+ private final CopyOnWriteArrayList<IBiometricStateListener> mBiometricStateListeners =
+ new CopyOnWriteArrayList<>();
+ @NonNull
+ private final UserManager mUserManager;
+ @BiometricStateListener.State
+ private int mBiometricState;
+ @NonNull
+ private List<T> mProviders = List.of();
- public BiometricStateCallback() {
+ /**
+ * Create a new callback that must be {@link #start(List)}ed.
+ *
+ * @param userManager user manager
+ */
+ public BiometricStateCallback(@NonNull UserManager userManager) {
mBiometricState = STATE_IDLE;
+ mUserManager = userManager;
+ }
+
+ /**
+ * This should be called when the service has been initialized and all providers are ready.
+ *
+ * @param allProviders all registered biometric service providers
+ */
+ public synchronized void start(@NonNull List<T> allProviders) {
+ mProviders = Collections.unmodifiableList(allProviders);
+ broadcastCurrentEnrollmentState(null /* listener */);
}
+ /** Get the current state. */
+ @BiometricStateListener.State
public int getBiometricState() {
return mBiometricState;
}
@@ -120,23 +152,43 @@ public class BiometricStateCallback implements ClientMonitorCallback {
}
/**
- * This should be invoked when:
- * 1) Enrolled --> None-enrolled
- * 2) None-enrolled --> enrolled
- * 3) HAL becomes ready
- * 4) Listener is registered
+ * Enables clients to register a BiometricStateListener. For example, this is used to forward
+ * fingerprint sensor state changes to SideFpsEventHandler.
+ *
+ * @param listener listener to register
*/
- public void notifyAllEnrollmentStateChanged(int userId, int sensorId,
+ public synchronized void registerBiometricStateListener(
+ @NonNull IBiometricStateListener listener) {
+ mBiometricStateListeners.add(listener);
+ broadcastCurrentEnrollmentState(listener);
+ }
+
+ private synchronized void broadcastCurrentEnrollmentState(
+ @Nullable IBiometricStateListener listener) {
+ for (T provider : mProviders) {
+ for (SensorPropertiesInternal prop : provider.getSensorProperties()) {
+ for (UserInfo userInfo : mUserManager.getAliveUsers()) {
+ final boolean enrolled = provider.hasEnrollments(prop.sensorId, userInfo.id);
+ if (listener != null) {
+ notifyEnrollmentStateChanged(
+ listener, userInfo.id, prop.sensorId, enrolled);
+ } else {
+ notifyAllEnrollmentStateChanged(
+ userInfo.id, prop.sensorId, enrolled);
+ }
+ }
+ }
+ }
+ }
+
+ private void notifyAllEnrollmentStateChanged(int userId, int sensorId,
boolean hasEnrollments) {
for (IBiometricStateListener listener : mBiometricStateListeners) {
notifyEnrollmentStateChanged(listener, userId, sensorId, hasEnrollments);
}
}
- /**
- * Notifies the listener of enrollment state changes.
- */
- public void notifyEnrollmentStateChanged(@NonNull IBiometricStateListener listener,
+ private void notifyEnrollmentStateChanged(@NonNull IBiometricStateListener listener,
int userId, int sensorId, boolean hasEnrollments) {
try {
listener.onEnrollmentsChanged(userId, sensorId, hasEnrollments);
@@ -144,14 +196,4 @@ public class BiometricStateCallback implements ClientMonitorCallback {
Slog.e(TAG, "Remote exception", e);
}
}
-
- /**
- * Enables clients to register a BiometricStateListener. For example, this is used to forward
- * fingerprint sensor state changes to SideFpsEventHandler.
- *
- * @param listener
- */
- public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
- mBiometricStateListeners.add(listener);
- }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 79e65cc6d2e5..271bce9890c6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -17,20 +17,18 @@
package com.android.server.biometrics.sensors.face;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
-import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.MANAGE_FACE;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
-import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.biometrics.IBiometricStateListener;
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
@@ -39,18 +37,18 @@ import android.hardware.biometrics.face.SensorProps;
import android.hardware.face.Face;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.FaceServiceReceiver;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.hardware.face.IFaceService;
import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder;
-import android.os.Handler;
import android.os.IBinder;
import android.os.NativeHandle;
-import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Pair;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -58,10 +56,10 @@ import android.view.Surface;
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
-import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
@@ -88,51 +86,10 @@ public class FaceService extends SystemService {
private final LockoutResetDispatcher mLockoutResetDispatcher;
private final LockPatternUtils mLockPatternUtils;
@NonNull
- private final List<ServiceProvider> mServiceProviders;
-
- @Nullable
- private ServiceProvider getProviderForSensor(int sensorId) {
- for (ServiceProvider provider : mServiceProviders) {
- if (provider.containsSensor(sensorId)) {
- return provider;
- }
- }
- return null;
- }
-
- /**
- * For devices with only a single provider, returns that provider. If no providers, or multiple
- * providers exist, returns null.
- */
- @Nullable
- private Pair<Integer, ServiceProvider> getSingleProvider() {
- final List<FaceSensorPropertiesInternal> properties = getSensorProperties();
- if (properties.size() != 1) {
- Slog.e(TAG, "Multiple sensors found: " + properties.size());
- return null;
- }
-
- // Theoretically we can just return the first provider, but maybe this is easier to
- // understand.
- final int sensorId = properties.get(0).sensorId;
- for (ServiceProvider provider : mServiceProviders) {
- if (provider.containsSensor(sensorId)) {
- return new Pair<>(sensorId, provider);
- }
- }
-
- Slog.e(TAG, "Single sensor, but provider not found");
- return null;
- }
-
+ private final FaceServiceRegistry mRegistry;
@NonNull
- private List<FaceSensorPropertiesInternal> getSensorProperties() {
- final List<FaceSensorPropertiesInternal> properties = new ArrayList<>();
- for (ServiceProvider provider : mServiceProviders) {
- properties.addAll(provider.getSensorProperties());
- }
- return properties;
- }
+ private final BiometricStateCallback<ServiceProvider, FaceSensorPropertiesInternal>
+ mBiometricStateCallback;
/**
* Receives the incoming binder calls from FaceManager.
@@ -142,8 +99,7 @@ public class FaceService extends SystemService {
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@NonNull String opPackageName) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for createTestSession, sensorId: " + sensorId);
@@ -156,9 +112,8 @@ public class FaceService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) {
-
final ProtoOutputStream proto = new ProtoOutputStream();
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider != null) {
provider.dumpProtoState(sensorId, proto, clearSchedulerBuffer);
}
@@ -170,16 +125,14 @@ public class FaceService extends SystemService {
@Override // Binder call
public List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(
String opPackageName) {
-
- return FaceService.this.getSensorProperties();
+ return mRegistry.getAllProperties();
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public FaceSensorPropertiesInternal getSensorProperties(int sensorId,
@NonNull String opPackageName) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for getSensorProperties, sensorId: " + sensorId
+ ", caller: " + opPackageName);
@@ -193,8 +146,7 @@ public class FaceService extends SystemService {
@Override // Binder call
public void generateChallenge(IBinder token, int sensorId, int userId,
IFaceServiceReceiver receiver, String opPackageName) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
return;
@@ -207,8 +159,7 @@ public class FaceService extends SystemService {
@Override // Binder call
public void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName,
long challenge) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId);
return;
@@ -222,8 +173,7 @@ public class FaceService extends SystemService {
public long enroll(int userId, final IBinder token, final byte[] hardwareAuthToken,
final IFaceServiceReceiver receiver, final String opPackageName,
final int[] disabledFeatures, Surface previewSurface, boolean debugConsent) {
-
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for enroll");
return -1;
@@ -245,8 +195,7 @@ public class FaceService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC)
@Override // Binder call
public void cancelEnrollment(final IBinder token, long requestId) {
-
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for cancelEnrollment");
return;
@@ -260,7 +209,6 @@ public class FaceService extends SystemService {
public long authenticate(final IBinder token, final long operationId, int userId,
final IFaceServiceReceiver receiver, final String opPackageName,
boolean isKeyguardBypassEnabled) {
-
// TODO(b/152413782): If the sensor supports face detect and the device is encrypted or
// lockdown, something wrong happened. See similar path in FingerprintService.
@@ -273,7 +221,7 @@ public class FaceService extends SystemService {
// permission.
final boolean isKeyguard = Utils.isKeyguard(getContext(), opPackageName);
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for authenticate");
return -1;
@@ -301,7 +249,7 @@ public class FaceService extends SystemService {
return -1;
}
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for detectFace");
return -1;
@@ -318,8 +266,7 @@ public class FaceService extends SystemService {
IBinder token, long operationId, int userId,
IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId,
int cookie, boolean allowBackgroundAuthentication) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for prepareForAuthentication");
return;
@@ -336,8 +283,7 @@ public class FaceService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public void startPreparedClient(int sensorId, int cookie) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for startPreparedClient");
return;
@@ -350,8 +296,7 @@ public class FaceService extends SystemService {
@Override // Binder call
public void cancelAuthentication(final IBinder token, final String opPackageName,
final long requestId) {
-
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for cancelAuthentication");
return;
@@ -370,7 +315,7 @@ public class FaceService extends SystemService {
return;
}
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for cancelFaceDetect");
return;
@@ -383,8 +328,7 @@ public class FaceService extends SystemService {
@Override // Binder call
public void cancelAuthenticationFromService(int sensorId, final IBinder token,
final String opPackageName, final long requestId) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for cancelAuthenticationFromService");
return;
@@ -397,8 +341,7 @@ public class FaceService extends SystemService {
@Override // Binder call
public void remove(final IBinder token, final int faceId, final int userId,
final IFaceServiceReceiver receiver, final String opPackageName) {
-
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for remove");
return;
@@ -412,7 +355,6 @@ public class FaceService extends SystemService {
@Override // Binder call
public void removeAll(final IBinder token, final int userId,
final IFaceServiceReceiver receiver, final String opPackageName) {
-
final FaceServiceReceiver internalReceiver = new FaceServiceReceiver() {
int sensorsFinishedRemoving = 0;
final int numSensors = getSensorPropertiesInternal(
@@ -432,7 +374,7 @@ public class FaceService extends SystemService {
// This effectively iterates through all sensors, but has to do so by finding all
// sensors under each provider.
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
List<FaceSensorPropertiesInternal> props = provider.getSensorProperties();
for (FaceSensorPropertiesInternal prop : props) {
provider.scheduleRemoveAll(prop.sensorId, token, userId, internalReceiver,
@@ -467,27 +409,27 @@ public class FaceService extends SystemService {
try {
if (args.length > 1 && "--proto".equals(args[0]) && "--state".equals(args[1])) {
final ProtoOutputStream proto = new ProtoOutputStream(fd);
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
provider.dumpProtoState(props.sensorId, proto, false);
}
}
proto.flush();
} else if (args.length > 0 && "--proto".equals(args[0])) {
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
provider.dumpProtoMetrics(props.sensorId, fd);
}
}
} else if (args.length > 1 && "--hal".equals(args[0])) {
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
provider.dumpHal(props.sensorId, fd,
Arrays.copyOfRange(args, 1, args.length, args.getClass()));
}
}
} else {
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
pw.println("Dumping for sensorId: " + props.sensorId
+ ", provider: " + provider.getClass().getSimpleName());
@@ -504,10 +446,9 @@ public class FaceService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public boolean isHardwareDetected(int sensorId, String opPackageName) {
-
final long token = Binder.clearCallingIdentity();
try {
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for isHardwareDetected, caller: " + opPackageName);
return false;
@@ -521,12 +462,11 @@ public class FaceService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public List<Face> getEnrolledFaces(int sensorId, int userId, String opPackageName) {
-
if (userId != UserHandle.getCallingUserId()) {
Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS);
}
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for getEnrolledFaces, caller: " + opPackageName);
return Collections.emptyList();
@@ -538,12 +478,11 @@ public class FaceService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName) {
-
if (userId != UserHandle.getCallingUserId()) {
Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS);
}
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for hasEnrolledFaces, caller: " + opPackageName);
return false;
@@ -555,8 +494,7 @@ public class FaceService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public @LockoutTracker.LockoutMode int getLockoutModeForUser(int sensorId, int userId) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for getLockoutModeForUser");
return LockoutTracker.LOCKOUT_NONE;
@@ -569,8 +507,7 @@ public class FaceService extends SystemService {
@Override
public void invalidateAuthenticatorId(int sensorId, int userId,
IInvalidationCallback callback) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for invalidateAuthenticatorId");
return;
@@ -582,7 +519,7 @@ public class FaceService extends SystemService {
@Override // Binder call
public long getAuthenticatorId(int sensorId, int userId) {
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for getAuthenticatorId");
return 0;
@@ -595,8 +532,7 @@ public class FaceService extends SystemService {
@Override // Binder call
public void resetLockout(IBinder token, int sensorId, int userId, byte[] hardwareAuthToken,
String opPackageName) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for resetLockout, caller: " + opPackageName);
return;
@@ -610,8 +546,7 @@ public class FaceService extends SystemService {
public void setFeature(final IBinder token, int userId, int feature, boolean enabled,
final byte[] hardwareAuthToken, IFaceServiceReceiver receiver,
final String opPackageName) {
-
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for setFeature");
return;
@@ -625,8 +560,7 @@ public class FaceService extends SystemService {
@Override
public void getFeature(final IBinder token, int userId, int feature,
IFaceServiceReceiver receiver, final String opPackageName) {
-
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for getFeature");
return;
@@ -636,18 +570,14 @@ public class FaceService extends SystemService {
new ClientMonitorCallbackConverter(receiver), opPackageName);
}
- private void addHidlProviders(@NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
- for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
- mServiceProviders.add(
- Face10.newInstance(getContext(), hidlSensor, mLockoutResetDispatcher));
- }
- }
+ private List<ServiceProvider> getAidlProviders() {
+ final List<ServiceProvider> providers = new ArrayList<>();
- private void addAidlProviders() {
final String[] instances = ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
if (instances == null || instances.length == 0) {
- return;
+ return providers;
}
+
for (String instance : instances) {
final String fqName = IFace.DESCRIPTOR + "/" + instance;
final IFace face = IFace.Stub.asInterface(
@@ -660,53 +590,41 @@ public class FaceService extends SystemService {
final SensorProps[] props = face.getSensorProps();
final FaceProvider provider = new FaceProvider(getContext(), props, instance,
mLockoutResetDispatcher, BiometricContext.getInstance(getContext()));
- mServiceProviders.add(provider);
+ providers.add(provider);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
}
}
+
+ return providers;
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public void registerAuthenticators(
@NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
-
- // Some HAL might not be started before the system service and will cause the code below
- // to wait, and some of the operations below might take a significant amount of time to
- // complete (calls to the HALs). To avoid blocking the rest of system server we put
- // this on a background thread.
- final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
- true /* allowIo */);
- thread.start();
- final Handler handler = new Handler(thread.getLooper());
-
- handler.post(() -> {
- addHidlProviders(hidlSensors);
- addAidlProviders();
-
- final IBiometricService biometricService = IBiometricService.Stub.asInterface(
- ServiceManager.getService(Context.BIOMETRIC_SERVICE));
-
- // Register each sensor individually with BiometricService
- for (ServiceProvider provider : mServiceProviders) {
- final List<FaceSensorPropertiesInternal> props = provider.getSensorProperties();
- for (FaceSensorPropertiesInternal prop : props) {
- final int sensorId = prop.sensorId;
- final @BiometricManager.Authenticators.Types int strength =
- Utils.propertyStrengthToAuthenticatorStrength(prop.sensorStrength);
- final FaceAuthenticator authenticator = new FaceAuthenticator(
- mServiceWrapper, sensorId);
- try {
- biometricService.registerAuthenticator(sensorId, TYPE_FACE, strength,
- authenticator);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when registering sensorId: " + sensorId);
- }
- }
+ mRegistry.registerAll(() -> {
+ final List<ServiceProvider> providers = new ArrayList<>();
+ for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
+ providers.add(
+ Face10.newInstance(getContext(), hidlSensor, mLockoutResetDispatcher));
}
+ providers.addAll(getAidlProviders());
+ return providers;
});
}
+
+ @Override
+ public void addAuthenticatorsRegisteredCallback(
+ IFaceAuthenticatorsRegisteredCallback callback) {
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ mRegistry.addAllRegisteredCallback(callback);
+ }
+
+ @Override
+ public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
+ mBiometricStateCallback.registerBiometricStateListener(listener);
+ }
}
public FaceService(Context context) {
@@ -714,7 +632,16 @@ public class FaceService extends SystemService {
mServiceWrapper = new FaceServiceWrapper();
mLockoutResetDispatcher = new LockoutResetDispatcher(context);
mLockPatternUtils = new LockPatternUtils(context);
- mServiceProviders = new ArrayList<>();
+ mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
+ mRegistry = new FaceServiceRegistry(mServiceWrapper,
+ () -> IBiometricService.Stub.asInterface(
+ ServiceManager.getService(Context.BIOMETRIC_SERVICE)));
+ mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
+ @Override
+ public void onAllAuthenticatorsRegistered(List<FaceSensorPropertiesInternal> sensors) {
+ mBiometricStateCallback.start(mRegistry.getProviders());
+ }
+ });
}
@Override
@@ -752,7 +679,7 @@ public class FaceService extends SystemService {
if (Utils.isVirtualEnabled(getContext())) {
Slog.i(TAG, "Sync virtual enrollments");
final int userId = ActivityManager.getCurrentUser();
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
provider.scheduleInternalCleanup(props.sensorId, userId, null /* callback */,
true /* favorHalEnrollments */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java
new file mode 100644
index 000000000000..0f0a81d24473
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 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.biometrics.sensors.face;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
+import android.hardware.face.IFaceService;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.BiometricServiceRegistry;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+/** Registry for {@link IFaceService} providers. */
+public class FaceServiceRegistry extends BiometricServiceRegistry<ServiceProvider,
+ FaceSensorPropertiesInternal, IFaceAuthenticatorsRegisteredCallback> {
+
+ private static final String TAG = "FaceServiceRegistry";
+
+ @NonNull
+ private final IFaceService mService;
+
+ /** Creates a new registry tied to the given service. */
+ public FaceServiceRegistry(@NonNull IFaceService service,
+ @Nullable Supplier<IBiometricService> biometricSupplier) {
+ super(biometricSupplier);
+ mService = service;
+ }
+
+ @Override
+ protected void registerService(@NonNull IBiometricService service,
+ @NonNull FaceSensorPropertiesInternal props) {
+ @BiometricManager.Authenticators.Types final int strength =
+ Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength);
+ try {
+ service.registerAuthenticator(props.sensorId, TYPE_FACE, strength,
+ new FaceAuthenticator(mService, props.sensorId));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when registering sensorId: " + props.sensorId);
+ }
+ }
+
+ @Override
+ protected void invokeRegisteredCallback(@NonNull IFaceAuthenticatorsRegisteredCallback callback,
+ @NonNull List<FaceSensorPropertiesInternal> allProps) throws RemoteException {
+ callback.onAllAuthenticatorsRegistered(allProps);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index 6f98365332e2..4efaedbd5530 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -26,15 +26,13 @@ import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceServiceReceiver;
import android.os.IBinder;
-import android.util.proto.ProtoOutputStream;
import android.view.Surface;
+import com.android.server.biometrics.sensors.BiometricServiceProvider;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.LockoutTracker;
import java.io.FileDescriptor;
-import java.io.PrintWriter;
import java.util.List;
/**
@@ -56,24 +54,11 @@ import java.util.List;
* to check (e.g. via {@link FaceManager#getSensorPropertiesInternal()}) that the code path isn't
* taken. ServiceProviders will provide a no-op for unsupported operations to fail safely.
*/
-public interface ServiceProvider {
- /**
- * Checks if the specified sensor is owned by this provider.
- */
- boolean containsSensor(int sensorId);
-
- @NonNull
- List<FaceSensorPropertiesInternal> getSensorProperties();
-
- @NonNull
- FaceSensorPropertiesInternal getSensorProperties(int sensorId);
+public interface ServiceProvider extends BiometricServiceProvider<FaceSensorPropertiesInternal> {
@NonNull
List<Face> getEnrolledFaces(int sensorId, int userId);
- @LockoutTracker.LockoutMode
- int getLockoutModeForUser(int sensorId, int userId);
-
/**
* Requests for the authenticatorId (whose source of truth is in the TEE or equivalent) to be
* invalidated. See {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}
@@ -84,10 +69,6 @@ public interface ServiceProvider {
+ " this method");
}
- long getAuthenticatorId(int sensorId, int userId);
-
- boolean isHardwareDetected(int sensorId);
-
void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull IFaceServiceReceiver receiver, String opPackageName);
@@ -142,13 +123,6 @@ public interface ServiceProvider {
void scheduleInternalCleanup(int sensorId, int userId,
@Nullable ClientMonitorCallback callback, boolean favorHalEnrollments);
- void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
- boolean clearSchedulerBuffer);
-
- void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd);
-
- void dumpInternal(int sensorId, @NonNull PrintWriter pw);
-
@NonNull
ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@NonNull String opPackageName);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 19d54c84c706..6bff179e8eb7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -285,6 +285,11 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
}
@Override
+ public boolean hasEnrollments(int sensorId, int userId) {
+ return !getEnrolledFaces(sensorId, userId).isEmpty();
+ }
+
+ @Override
public void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
@NonNull IInvalidationCallback callback) {
mHandler.post(() -> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 65289122747c..c0a119ff5f1e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -484,6 +484,11 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
}
@Override
+ public boolean hasEnrollments(int sensorId, int userId) {
+ return !getEnrolledFaces(sensorId, userId).isEmpty();
+ }
+
+ @Override
@LockoutTracker.LockoutMode
public int getLockoutModeForUser(int sensorId, int userId) {
return mLockoutTracker.getLockoutModeForUser(userId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 2ba449ae089e..7e2742edd47a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -17,14 +17,11 @@
package com.android.server.biometrics.sensors.fingerprint;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
-import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.MANAGE_FINGERPRINT;
-import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.Manifest.permission.USE_FINGERPRINT;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR;
@@ -36,8 +33,6 @@ import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IBiometricSensorReceiver;
@@ -65,7 +60,6 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
-import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
@@ -79,11 +73,9 @@ import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.R;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
-import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
@@ -115,74 +107,32 @@ public class FingerprintService extends SystemService {
protected static final String TAG = "FingerprintService";
- private final Object mLock = new Object();
private final AppOpsManager mAppOps;
private final LockoutResetDispatcher mLockoutResetDispatcher;
private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
private final LockPatternUtils mLockPatternUtils;
- @NonNull private final List<ServiceProvider> mServiceProviders;
- @NonNull private final BiometricStateCallback mBiometricStateCallback;
- @NonNull private final Handler mHandler;
- @NonNull private final BiometricContext mBiometricContext;
- @NonNull private final Supplier<IBiometricService> mBiometricServiceSupplier;
- @NonNull private final Function<String, IFingerprint> mIFingerprintProvider;
-
- @GuardedBy("mLock")
- @NonNull private final RemoteCallbackList<IFingerprintAuthenticatorsRegisteredCallback>
- mAuthenticatorsRegisteredCallbacks;
-
- @GuardedBy("mLock")
- @NonNull private final List<FingerprintSensorPropertiesInternal> mSensorProps;
-
- /**
- * Registers BiometricStateListener in list stored by FingerprintService
- * @param listener new BiometricStateListener being added
- */
- public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
- mBiometricStateCallback.registerBiometricStateListener(listener);
- broadcastCurrentEnrollmentState(listener);
- }
-
- /**
- * @param listener if non-null, notifies only this listener. if null, notifies all listeners
- * in {@link BiometricStateCallback}. This is slightly ugly, but reduces
- * redundant code.
- */
- private void broadcastCurrentEnrollmentState(@Nullable IBiometricStateListener listener) {
- final UserManager um = UserManager.get(getContext());
- synchronized (mLock) {
- // Update the new listener with current state of all sensors
- for (FingerprintSensorPropertiesInternal prop : mSensorProps) {
- final ServiceProvider provider = getProviderForSensor(prop.sensorId);
- for (UserInfo userInfo : um.getAliveUsers()) {
- final boolean enrolled = !provider
- .getEnrolledFingerprints(prop.sensorId, userInfo.id).isEmpty();
-
- // Defer this work and allow the loop to release the lock sooner
- mHandler.post(() -> {
- if (listener != null) {
- mBiometricStateCallback.notifyEnrollmentStateChanged(
- listener, userInfo.id, prop.sensorId, enrolled);
- } else {
- mBiometricStateCallback.notifyAllEnrollmentStateChanged(
- userInfo.id, prop.sensorId, enrolled);
- }
- });
- }
- }
- }
- }
+ @NonNull
+ private final BiometricContext mBiometricContext;
+ @NonNull
+ private final Supplier<String[]> mAidlInstanceNameSupplier;
+ @NonNull
+ private final Function<String, IFingerprint> mIFingerprintProvider;
+ @NonNull
+ private final BiometricStateCallback<ServiceProvider, FingerprintSensorPropertiesInternal>
+ mBiometricStateCallback;
+ @NonNull
+ private final Handler mHandler;
+ @NonNull
+ private final FingerprintServiceRegistry mRegistry;
- /**
- * Receives the incoming binder calls from FingerprintManager.
- */
- private final IFingerprintService.Stub mServiceWrapper = new IFingerprintService.Stub() {
+ /** Receives the incoming binder calls from FingerprintManager. */
+ @VisibleForTesting
+ final IFingerprintService.Stub mServiceWrapper = new IFingerprintService.Stub() {
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@NonNull String opPackageName) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for createTestSession, sensorId: " + sensorId);
@@ -195,9 +145,8 @@ public class FingerprintService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) {
-
final ProtoOutputStream proto = new ProtoOutputStream();
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider != null) {
provider.dumpProtoState(sensorId, proto, clearSchedulerBuffer);
}
@@ -212,16 +161,14 @@ public class FingerprintService extends SystemService {
!= PackageManager.PERMISSION_GRANTED) {
Utils.checkPermission(getContext(), TEST_BIOMETRIC);
}
-
- return FingerprintService.this.getSensorProperties();
+ return mRegistry.getAllProperties();
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public FingerprintSensorPropertiesInternal getSensorProperties(int sensorId,
@NonNull String opPackageName) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for getSensorProperties, sensorId: " + sensorId
+ ", caller: " + opPackageName);
@@ -234,8 +181,7 @@ public class FingerprintService extends SystemService {
@Override // Binder call
public void generateChallenge(IBinder token, int sensorId, int userId,
IFingerprintServiceReceiver receiver, String opPackageName) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
return;
@@ -248,8 +194,7 @@ public class FingerprintService extends SystemService {
@Override // Binder call
public void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName,
long challenge) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId);
return;
@@ -264,8 +209,7 @@ public class FingerprintService extends SystemService {
public long enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken,
final int userId, final IFingerprintServiceReceiver receiver,
final String opPackageName, @FingerprintManager.EnrollReason int enrollReason) {
-
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for enroll");
return -1;
@@ -278,8 +222,7 @@ public class FingerprintService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT)
@Override // Binder call
public void cancelEnrollment(final IBinder token, long requestId) {
-
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for cancelEnrollment");
return;
@@ -339,10 +282,10 @@ public class FingerprintService extends SystemService {
final Pair<Integer, ServiceProvider> provider;
if (sensorId == FingerprintManager.SENSOR_ID_ANY) {
- provider = getSingleProvider();
+ provider = mRegistry.getSingleProvider();
} else {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
- provider = new Pair<>(sensorId, getProviderForSensor(sensorId));
+ provider = new Pair<>(sensorId, mRegistry.getProviderForSensor(sensorId));
}
if (provider == null) {
Slog.w(TAG, "Null provider for authenticate");
@@ -374,7 +317,6 @@ public class FingerprintService extends SystemService {
final IFingerprintServiceReceiver receiver,
final String opPackageName,
boolean ignoreEnrollmentState) throws PackageManager.NameNotFoundException {
-
final Context context = getUiContext();
final Context promptContext = context.createPackageContextAsUser(
opPackageName, 0 /* flags */, UserHandle.getUserHandleForUid(uId));
@@ -468,7 +410,7 @@ public class FingerprintService extends SystemService {
return -1;
}
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for detectFingerprint");
return -1;
@@ -484,8 +426,7 @@ public class FingerprintService extends SystemService {
public void prepareForAuthentication(int sensorId, IBinder token, long operationId,
int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
long requestId, int cookie, boolean allowBackgroundAuthentication) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for prepareForAuthentication");
return;
@@ -501,8 +442,7 @@ public class FingerprintService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC)
@Override // Binder call
public void startPreparedClient(int sensorId, int cookie) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for startPreparedClient");
return;
@@ -532,7 +472,7 @@ public class FingerprintService extends SystemService {
return;
}
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for cancelAuthentication");
return;
@@ -553,7 +493,7 @@ public class FingerprintService extends SystemService {
// For IBiometricsFingerprint2.1, cancelling fingerprint detect is the same as
// cancelling authentication.
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for cancelFingerprintDetect");
return;
@@ -566,11 +506,9 @@ public class FingerprintService extends SystemService {
@Override // Binder call
public void cancelAuthenticationFromService(final int sensorId, final IBinder token,
final String opPackageName, final long requestId) {
-
-
Slog.d(TAG, "cancelAuthenticationFromService, sensorId: " + sensorId);
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for cancelAuthenticationFromService");
return;
@@ -583,8 +521,7 @@ public class FingerprintService extends SystemService {
@Override // Binder call
public void remove(final IBinder token, final int fingerId, final int userId,
final IFingerprintServiceReceiver receiver, final String opPackageName) {
-
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for remove");
return;
@@ -617,7 +554,7 @@ public class FingerprintService extends SystemService {
// This effectively iterates through all sensors, but has to do so by finding all
// sensors under each provider.
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
List<FingerprintSensorPropertiesInternal> props = provider.getSensorProperties();
for (FingerprintSensorPropertiesInternal prop : props) {
provider.scheduleRemoveAll(prop.sensorId, token, internalReceiver, userId,
@@ -652,7 +589,7 @@ public class FingerprintService extends SystemService {
try {
if (args.length > 1 && "--proto".equals(args[0]) && "--state".equals(args[1])) {
final ProtoOutputStream proto = new ProtoOutputStream(fd);
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
for (FingerprintSensorPropertiesInternal props
: provider.getSensorProperties()) {
provider.dumpProtoState(props.sensorId, proto, false);
@@ -660,14 +597,14 @@ public class FingerprintService extends SystemService {
}
proto.flush();
} else if (args.length > 0 && "--proto".equals(args[0])) {
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
for (FingerprintSensorPropertiesInternal props
: provider.getSensorProperties()) {
provider.dumpProtoMetrics(props.sensorId, fd);
}
}
} else {
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
for (FingerprintSensorPropertiesInternal props
: provider.getSensorProperties()) {
pw.println("Dumping for sensorId: " + props.sensorId
@@ -698,7 +635,7 @@ public class FingerprintService extends SystemService {
final long token = Binder.clearCallingIdentity();
try {
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for isHardwareDetectedDeprecated, caller: "
+ opPackageName);
@@ -713,8 +650,7 @@ public class FingerprintService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public boolean isHardwareDetected(int sensorId, String opPackageName) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for isHardwareDetected, caller: " + opPackageName);
return false;
@@ -730,7 +666,7 @@ public class FingerprintService extends SystemService {
return;
}
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for rename");
return;
@@ -781,8 +717,7 @@ public class FingerprintService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
public boolean hasEnrolledFingerprints(int sensorId, int userId, String opPackageName) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for hasEnrolledFingerprints, caller: " + opPackageName);
return false;
@@ -794,8 +729,7 @@ public class FingerprintService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public @LockoutTracker.LockoutMode int getLockoutModeForUser(int sensorId, int userId) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for getLockoutModeForUser");
return LockoutTracker.LOCKOUT_NONE;
@@ -807,8 +741,7 @@ public class FingerprintService extends SystemService {
@Override
public void invalidateAuthenticatorId(int sensorId, int userId,
IInvalidationCallback callback) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for invalidateAuthenticatorId");
return;
@@ -819,8 +752,7 @@ public class FingerprintService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public long getAuthenticatorId(int sensorId, int userId) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for getAuthenticatorId");
return 0;
@@ -832,8 +764,7 @@ public class FingerprintService extends SystemService {
@Override // Binder call
public void resetLockout(IBinder token, int sensorId, int userId,
@Nullable byte[] hardwareAuthToken, String opPackageName) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for resetLockout, caller: " + opPackageName);
return;
@@ -864,55 +795,38 @@ public class FingerprintService extends SystemService {
@Override // Binder call
public void registerAuthenticators(
@NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
-
- // Some HAL might not be started before the system service and will cause the code below
- // to wait, and some of the operations below might take a significant amount of time to
- // complete (calls to the HALs). To avoid blocking the rest of system server we put
- // this on a background thread.
- final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
- true /* allowIo */);
- thread.start();
- final Handler handler = new Handler(thread.getLooper());
- handler.post(() -> {
+ mRegistry.registerAll(() -> {
+ final List<ServiceProvider> providers = new ArrayList<>();
+ providers.addAll(getHidlProviders(hidlSensors));
List<String> aidlSensors = new ArrayList<>();
- final String[] instances =
- ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR);
+ final String[] instances = mAidlInstanceNameSupplier.get();
if (instances != null) {
aidlSensors.addAll(Lists.newArrayList(instances));
}
- registerAuthenticatorsForService(aidlSensors, hidlSensors);
+ providers.addAll(getAidlProviders(
+ Utils.filterAvailableHalInstances(getContext(), aidlSensors)));
+ return providers;
});
- thread.quitSafely();
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void addAuthenticatorsRegisteredCallback(
IFingerprintAuthenticatorsRegisteredCallback callback) {
- if (callback == null) {
- Slog.e(TAG, "addAuthenticatorsRegisteredCallback, callback is null");
- return;
- }
+ mRegistry.addAllRegisteredCallback(callback);
+ }
- final boolean registered;
- final boolean hasSensorProps;
- synchronized (mLock) {
- registered = mAuthenticatorsRegisteredCallbacks.register(callback);
- hasSensorProps = !mSensorProps.isEmpty();
- }
- if (registered && hasSensorProps) {
- broadcastAllAuthenticatorsRegistered();
- } else if (!registered) {
- Slog.e(TAG, "addAuthenticatorsRegisteredCallback failed to register callback");
- }
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ @Override
+ public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
+ mBiometricStateCallback.registerBiometricStateListener(listener);
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void onPointerDown(long requestId, int sensorId, int x, int y,
float minor, float major) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching provider for onFingerDown, sensorId: " + sensorId);
return;
@@ -923,8 +837,7 @@ public class FingerprintService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void onPointerUp(long requestId, int sensorId) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching provider for onFingerUp, sensorId: " + sensorId);
return;
@@ -935,8 +848,7 @@ public class FingerprintService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void onUiReady(long requestId, int sensorId) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching provider for onUiReady, sensorId: " + sensorId);
return;
@@ -947,8 +859,7 @@ public class FingerprintService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) {
-
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
provider.setUdfpsOverlayController(controller);
}
}
@@ -956,22 +867,15 @@ public class FingerprintService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void setSidefpsController(@NonNull ISidefpsController controller) {
-
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
provider.setSidefpsController(controller);
}
}
- @Override
- public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
- Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
- FingerprintService.this.registerBiometricStateListener(listener);
- }
-
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void onPowerPressed() {
- Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
provider.onPowerPressed();
}
}
@@ -981,6 +885,7 @@ public class FingerprintService extends SystemService {
this(context, BiometricContext.getInstance(context),
() -> IBiometricService.Stub.asInterface(
ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
+ () -> ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR),
(fqName) -> IFingerprint.Stub.asInterface(
Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName))));
}
@@ -988,61 +893,34 @@ public class FingerprintService extends SystemService {
@VisibleForTesting
FingerprintService(Context context,
BiometricContext biometricContext,
- Supplier<IBiometricService> biometricServiceProvider,
+ Supplier<IBiometricService> biometricServiceSupplier,
+ Supplier<String[]> aidlInstanceNameSupplier,
Function<String, IFingerprint> fingerprintProvider) {
super(context);
mBiometricContext = biometricContext;
- mBiometricServiceSupplier = biometricServiceProvider;
+ mAidlInstanceNameSupplier = aidlInstanceNameSupplier;
mIFingerprintProvider = fingerprintProvider;
mAppOps = context.getSystemService(AppOpsManager.class);
mGestureAvailabilityDispatcher = new GestureAvailabilityDispatcher();
mLockoutResetDispatcher = new LockoutResetDispatcher(context);
mLockPatternUtils = new LockPatternUtils(context);
- mServiceProviders = new ArrayList<>();
- mBiometricStateCallback = new BiometricStateCallback();
- mAuthenticatorsRegisteredCallbacks = new RemoteCallbackList<>();
- mSensorProps = new ArrayList<>();
+ mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
mHandler = new Handler(Looper.getMainLooper());
+ mRegistry = new FingerprintServiceRegistry(mServiceWrapper, biometricServiceSupplier);
+ mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+ @Override
+ public void onAllAuthenticatorsRegistered(
+ List<FingerprintSensorPropertiesInternal> sensors) {
+ mBiometricStateCallback.start(mRegistry.getProviders());
+ }
+ });
}
- @VisibleForTesting
- void registerAuthenticatorsForService(@NonNull List<String> aidlInstanceNames,
+ @NonNull
+ private List<ServiceProvider> getHidlProviders(
@NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
- addHidlProviders(hidlSensors);
- addAidlProviders(Utils.filterAvailableHalInstances(getContext(), aidlInstanceNames));
-
- final IBiometricService biometricService = mBiometricServiceSupplier.get();
-
- // Register each sensor individually with BiometricService
- for (ServiceProvider provider : mServiceProviders) {
- final List<FingerprintSensorPropertiesInternal> props =
- provider.getSensorProperties();
- for (FingerprintSensorPropertiesInternal prop : props) {
- final int sensorId = prop.sensorId;
- @BiometricManager.Authenticators.Types final int strength =
- Utils.propertyStrengthToAuthenticatorStrength(prop.sensorStrength);
- final FingerprintAuthenticator authenticator = new FingerprintAuthenticator(
- mServiceWrapper, sensorId);
- try {
- biometricService.registerAuthenticator(sensorId, TYPE_FINGERPRINT,
- strength, authenticator);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when registering sensorId: " + sensorId);
- }
- }
- }
-
- synchronized (mLock) {
- for (ServiceProvider provider : mServiceProviders) {
- mSensorProps.addAll(provider.getSensorProperties());
- }
- }
+ final List<ServiceProvider> providers = new ArrayList<>();
- broadcastCurrentEnrollmentState(null); // broadcasts to all listeners
- broadcastAllAuthenticatorsRegistered();
- }
-
- private void addHidlProviders(List<FingerprintSensorPropertiesInternal> hidlSensors) {
for (FingerprintSensorPropertiesInternal hidlSensor : hidlSensors) {
final Fingerprint21 fingerprint21;
if ((Build.IS_USERDEBUG || Build.IS_ENG)
@@ -1059,11 +937,16 @@ public class FingerprintService extends SystemService {
mBiometricStateCallback, hidlSensor, mHandler,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
}
- mServiceProviders.add(fingerprint21);
+ providers.add(fingerprint21);
}
+
+ return providers;
}
- private void addAidlProviders(List<String> instances) {
+ @NonNull
+ private List<ServiceProvider> getAidlProviders(@NonNull List<String> instances) {
+ final List<ServiceProvider> providers = new ArrayList<>();
+
for (String instance : instances) {
final String fqName = IFingerprint.DESCRIPTOR + "/" + instance;
final IFingerprint fp = mIFingerprintProvider.apply(fqName);
@@ -1075,7 +958,7 @@ public class FingerprintService extends SystemService {
mLockoutResetDispatcher, mGestureAvailabilityDispatcher,
mBiometricContext);
Slog.i(TAG, "Adding AIDL provider: " + fqName);
- mServiceProviders.add(provider);
+ providers.add(provider);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
}
@@ -1083,38 +966,8 @@ public class FingerprintService extends SystemService {
Slog.e(TAG, "Unable to get declared service: " + fqName);
}
}
- }
- // Notifies the callbacks that all of the authenticators have been registered and removes the
- // invoked callbacks from the callback list.
- private void broadcastAllAuthenticatorsRegistered() {
- // Make a local copy of the data so it can be used outside of the synchronized block when
- // making Binder calls.
- final List<IFingerprintAuthenticatorsRegisteredCallback> callbacks = new ArrayList<>();
- final List<FingerprintSensorPropertiesInternal> props;
- synchronized (mLock) {
- if (!mSensorProps.isEmpty()) {
- props = new ArrayList<>(mSensorProps);
- } else {
- Slog.e(TAG, "mSensorProps is empty");
- return;
- }
- final int n = mAuthenticatorsRegisteredCallbacks.beginBroadcast();
- for (int i = 0; i < n; ++i) {
- final IFingerprintAuthenticatorsRegisteredCallback cb =
- mAuthenticatorsRegisteredCallbacks.getBroadcastItem(i);
- callbacks.add(cb);
- mAuthenticatorsRegisteredCallbacks.unregister(cb);
- }
- mAuthenticatorsRegisteredCallbacks.finishBroadcast();
- }
- for (IFingerprintAuthenticatorsRegisteredCallback cb : callbacks) {
- try {
- cb.onAllAuthenticatorsRegistered(props);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception in onAllAuthenticatorsRegistered", e);
- }
- }
+ return providers;
}
@Override
@@ -1122,51 +975,9 @@ public class FingerprintService extends SystemService {
publishBinderService(Context.FINGERPRINT_SERVICE, mServiceWrapper);
}
- @Nullable
- private ServiceProvider getProviderForSensor(int sensorId) {
- for (ServiceProvider provider : mServiceProviders) {
- if (provider.containsSensor(sensorId)) {
- return provider;
- }
- }
- return null;
- }
-
- /**
- * For devices with only a single provider, returns that provider. If multiple providers,
- * returns the first one. If no providers, returns null.
- */
- @Nullable
- private Pair<Integer, ServiceProvider> getSingleProvider() {
- final List<FingerprintSensorPropertiesInternal> properties = getSensorProperties();
- if (properties.isEmpty()) {
- Slog.e(TAG, "No providers found");
- return null;
- }
-
- // Theoretically we can just return the first provider, but maybe this is easier to
- // understand.
- final int sensorId = properties.get(0).sensorId;
- for (ServiceProvider provider : mServiceProviders) {
- if (provider.containsSensor(sensorId)) {
- return new Pair<>(sensorId, provider);
- }
- }
-
- Slog.e(TAG, "Provider not found");
- return null;
- }
-
- @NonNull
- private List<FingerprintSensorPropertiesInternal> getSensorProperties() {
- synchronized (mLock) {
- return mSensorProps;
- }
- }
-
@NonNull
private List<Fingerprint> getEnrolledFingerprintsDeprecated(int userId, String opPackageName) {
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for getEnrolledFingerprintsDeprecated, caller: "
+ opPackageName);
@@ -1229,7 +1040,7 @@ public class FingerprintService extends SystemService {
if (Utils.isVirtualEnabled(getContext())) {
Slog.i(TAG, "Sync virtual enrollments");
final int userId = ActivityManager.getCurrentUser();
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
for (FingerprintSensorPropertiesInternal props : provider.getSensorProperties()) {
provider.scheduleInternalCleanup(props.sensorId, userId, null /* callback */,
true /* favorHalEnrollments */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java
new file mode 100644
index 000000000000..33810b764f23
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 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.biometrics.sensors.fingerprint;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
+import android.hardware.fingerprint.IFingerprintService;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.BiometricServiceRegistry;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+/** Registry for {@link IFingerprintService} providers. */
+public class FingerprintServiceRegistry extends BiometricServiceRegistry<ServiceProvider,
+ FingerprintSensorPropertiesInternal, IFingerprintAuthenticatorsRegisteredCallback> {
+
+ private static final String TAG = "FingerprintServiceRegistry";
+
+ @NonNull
+ private final IFingerprintService mService;
+
+ /** Creates a new registry tied to the given service. */
+ public FingerprintServiceRegistry(@NonNull IFingerprintService service,
+ @Nullable Supplier<IBiometricService> biometricSupplier) {
+ super(biometricSupplier);
+ mService = service;
+ }
+
+ @Override
+ protected void registerService(@NonNull IBiometricService service,
+ @NonNull FingerprintSensorPropertiesInternal props) {
+ @BiometricManager.Authenticators.Types final int strength =
+ Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength);
+ try {
+ service.registerAuthenticator(props.sensorId, TYPE_FINGERPRINT, strength,
+ new FingerprintAuthenticator(mService, props.sensorId));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when registering sensorId: " + props.sensorId);
+ }
+ }
+
+ @Override
+ protected void invokeRegisteredCallback(
+ @NonNull IFingerprintAuthenticatorsRegisteredCallback callback,
+ @NonNull List<FingerprintSensorPropertiesInternal> allProps) throws RemoteException {
+ callback.onAllAuthenticatorsRegistered(allProps);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 275d7e445a75..9075e7ec2080 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -28,14 +28,11 @@ import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
-import android.util.proto.ProtoOutputStream;
+import com.android.server.biometrics.sensors.BiometricServiceProvider;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.LockoutTracker;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
import java.util.List;
/**
@@ -59,23 +56,8 @@ import java.util.List;
* fail safely.
*/
@SuppressWarnings("deprecation")
-public interface ServiceProvider {
- /**
- * Checks if the specified sensor is owned by this provider.
- */
- boolean containsSensor(int sensorId);
-
- @NonNull
- List<FingerprintSensorPropertiesInternal> getSensorProperties();
-
- /**
- * Returns the internal properties of the specified sensor, if owned by this provider.
- *
- * @param sensorId The ID of a fingerprint sensor, or -1 for any sensor.
- * @return An object representing the internal properties of the specified sensor.
- */
- @Nullable
- FingerprintSensorPropertiesInternal getSensorProperties(int sensorId);
+public interface ServiceProvider extends
+ BiometricServiceProvider<FingerprintSensorPropertiesInternal> {
void scheduleResetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken);
@@ -126,16 +108,11 @@ public interface ServiceProvider {
void scheduleInternalCleanup(int sensorId, int userId,
@Nullable ClientMonitorCallback callback, boolean favorHalEnrollments);
- boolean isHardwareDetected(int sensorId);
-
void rename(int sensorId, int fingerId, int userId, @NonNull String name);
@NonNull
List<Fingerprint> getEnrolledFingerprints(int sensorId, int userId);
- @LockoutTracker.LockoutMode
- int getLockoutModeForUser(int sensorId, int userId);
-
/**
* Requests for the authenticatorId (whose source of truth is in the TEE or equivalent) to
* be invalidated. See {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}
@@ -143,7 +120,6 @@ public interface ServiceProvider {
void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
@NonNull IInvalidationCallback callback);
- long getAuthenticatorId(int sensorId, int userId);
void onPointerDown(long requestId, int sensorId, int x, int y, float minor, float major);
@@ -161,13 +137,6 @@ public interface ServiceProvider {
*/
void setSidefpsController(@NonNull ISidefpsController controller);
- void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
- boolean clearSchedulerBuffer);
-
- void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd);
-
- void dumpInternal(int sensorId, @NonNull PrintWriter pw);
-
@NonNull
ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@NonNull String opPackageName);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 2dc005206b42..3fe6332fcaa0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -565,6 +565,11 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
}
@Override
+ public boolean hasEnrollments(int sensorId, int userId) {
+ return !getEnrolledFingerprints(sensorId, userId).isEmpty();
+ }
+
+ @Override
public void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
@NonNull IInvalidationCallback callback) {
mHandler.post(() -> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index ed482f013869..0e6df8e0df77 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -789,6 +789,11 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
}
@Override
+ public boolean hasEnrollments(int sensorId, int userId) {
+ return !getEnrolledFingerprints(sensorId, userId).isEmpty();
+ }
+
+ @Override
@LockoutTracker.LockoutMode public int getLockoutModeForUser(int sensorId, int userId) {
return mLockoutTracker.getLockoutModeForUser(userId);
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index a817cea1a674..05d32d1d9023 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -148,6 +148,12 @@ class AutomaticBrightnessController {
// The currently accepted nominal ambient light level.
private float mAmbientLux;
+ // The last calculated ambient light level (long time window).
+ private float mSlowAmbientLux;
+
+ // The last calculated ambient light level (short time window).
+ private float mFastAmbientLux;
+
// The last ambient lux value prior to passing the darkening or brightening threshold.
private float mPreThresholdLux;
@@ -440,6 +446,14 @@ class AutomaticBrightnessController {
return mAmbientLux;
}
+ float getSlowAmbientLux() {
+ return mSlowAmbientLux;
+ }
+
+ float getFastAmbientLux() {
+ return mFastAmbientLux;
+ }
+
private boolean setDisplayPolicy(int policy) {
if (mDisplayPolicy == policy) {
return false;
@@ -812,20 +826,20 @@ class AutomaticBrightnessController {
// proposed ambient light value since the slow value might be sufficiently far enough away
// from the fast value to cause a recalculation while its actually just converging on
// the fast value still.
- float slowAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonLong);
- float fastAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonShort);
+ mSlowAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonLong);
+ mFastAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonShort);
- if ((slowAmbientLux >= mAmbientBrighteningThreshold
- && fastAmbientLux >= mAmbientBrighteningThreshold
+ if ((mSlowAmbientLux >= mAmbientBrighteningThreshold
+ && mFastAmbientLux >= mAmbientBrighteningThreshold
&& nextBrightenTransition <= time)
- || (slowAmbientLux <= mAmbientDarkeningThreshold
- && fastAmbientLux <= mAmbientDarkeningThreshold
+ || (mSlowAmbientLux <= mAmbientDarkeningThreshold
+ && mFastAmbientLux <= mAmbientDarkeningThreshold
&& nextDarkenTransition <= time)) {
mPreThresholdLux = mAmbientLux;
- setAmbientLux(fastAmbientLux);
+ setAmbientLux(mFastAmbientLux);
if (mLoggingEnabled) {
Slog.d(TAG, "updateAmbientLux: "
- + ((fastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
+ + ((mFastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
+ "mBrighteningLuxThreshold=" + mAmbientBrighteningThreshold + ", "
+ "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", "
+ "mAmbientLux=" + mAmbientLux);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 6f3a0c516a5b..8807e1970061 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1640,11 +1640,16 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// brightness cap, RBC state, etc.
mTempBrightnessEvent.setTime(System.currentTimeMillis());
mTempBrightnessEvent.setBrightness(brightnessState);
+ mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId);
mTempBrightnessEvent.setReason(mBrightnessReason);
mTempBrightnessEvent.setHbmMax(mHbmController.getCurrentBrightnessMax());
mTempBrightnessEvent.setHbmMode(mHbmController.getHighBrightnessMode());
mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
| (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0));
+ mTempBrightnessEvent.setRbcStrength((mCdsi != null && mCdsi.isReduceBrightColorsActivated())
+ ? mCdsi.getReduceBrightColorsStrength() : -1);
+ mTempBrightnessEvent.setPowerFactor(
+ mPowerRequest.lowPowerMode ? mPowerRequest.screenLowPowerBrightnessFactor : 1.0f);
// Temporary is what we use during slider interactions. We avoid logging those so that
// we don't spam logcat when the slider is being used.
boolean tempToTempTransition =
@@ -1653,9 +1658,17 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
== BrightnessReason.REASON_TEMPORARY;
if ((!mTempBrightnessEvent.equalsMainData(mLastBrightnessEvent) && !tempToTempTransition)
|| brightnessAdjustmentFlags != 0) {
+ float lastBrightness = mLastBrightnessEvent.getBrightness();
+ mTempBrightnessEvent.setInitialBrightness(lastBrightness);
+ mTempBrightnessEvent.setFastAmbientLux(
+ mAutomaticBrightnessController == null
+ ? -1 : mAutomaticBrightnessController.getFastAmbientLux());
+ mTempBrightnessEvent.setSlowAmbientLux(
+ mAutomaticBrightnessController == null
+ ? -1 : mAutomaticBrightnessController.getSlowAmbientLux());
+ mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
-
// Adjustment flags (and user-set flag) only get added after the equality checks since
// they are transient.
newEvent.setAdjustmentFlags(brightnessAdjustmentFlags);
@@ -1663,6 +1676,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
? BrightnessEvent.FLAG_USER_SET : 0));
Slog.i(mTag, newEvent.toString(/* includeTime= */ false));
+ if (userSetBrightnessChanged) {
+ logManualBrightnessEvent(newEvent);
+ }
if (mBrightnessEventRingBuffer != null) {
mBrightnessEventRingBuffer.append(newEvent);
}
@@ -2752,6 +2768,30 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
}
+ private void logManualBrightnessEvent(BrightnessEvent event) {
+ float hbmMaxNits =
+ event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
+ ? -1f : convertToNits(event.getHbmMax());
+
+ // thermalCapNits set to -1 if not currently capping max brightness
+ float thermalCapNits =
+ event.getThermalMax() == PowerManager.BRIGHTNESS_MAX
+ ? -1f : convertToNits(event.getThermalMax());
+
+ FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
+ convertToNits(event.getInitialBrightness()),
+ convertToNits(event.getBrightness()),
+ event.getSlowAmbientLux(),
+ event.getPhysicalDisplayId(),
+ event.isShortTermModelActive(),
+ event.getPowerFactor(),
+ event.getRbcStrength(),
+ hbmMaxNits,
+ thermalCapNits,
+ event.isAutomaticBrightnessEnabled(),
+ FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
+ }
+
private final class DisplayControllerHandler extends Handler {
DisplayControllerHandler(Looper looper) {
super(looper, null, true /*async*/);
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index d831dbd4bb23..70415a3c81c9 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -33,17 +33,24 @@ public final class BrightnessEvent {
private BrightnessReason mReason = new BrightnessReason();
private int mDisplayId;
+ private String mPhysicalDisplayId;
+ private long mTime;
private float mLux;
+ private float mFastAmbientLux;
+ private float mSlowAmbientLux;
private float mPreThresholdLux;
- private long mTime;
+ private float mInitialBrightness;
private float mBrightness;
private float mRecommendedBrightness;
private float mPreThresholdBrightness;
+ private int mHbmMode;
private float mHbmMax;
+ private int mRbcStrength;
private float mThermalMax;
- private int mHbmMode;
+ private float mPowerFactor;
private int mFlags;
private int mAdjustmentFlags;
+ private boolean mAutomaticBrightnessEnabled;
public BrightnessEvent(BrightnessEvent that) {
copyFrom(that);
@@ -60,37 +67,59 @@ public final class BrightnessEvent {
* @param that BrightnessEvent which is to be copied
*/
public void copyFrom(BrightnessEvent that) {
+ mReason.set(that.getReason());
mDisplayId = that.getDisplayId();
+ mPhysicalDisplayId = that.getPhysicalDisplayId();
mTime = that.getTime();
+ // Lux values
mLux = that.getLux();
+ mFastAmbientLux = that.getFastAmbientLux();
+ mSlowAmbientLux = that.getSlowAmbientLux();
mPreThresholdLux = that.getPreThresholdLux();
+ // Brightness values
+ mInitialBrightness = that.getInitialBrightness();
mBrightness = that.getBrightness();
mRecommendedBrightness = that.getRecommendedBrightness();
mPreThresholdBrightness = that.getPreThresholdBrightness();
+ // Different brightness modulations
+ mHbmMode = that.getHbmMode();
mHbmMax = that.getHbmMax();
+ mRbcStrength = that.getRbcStrength();
mThermalMax = that.getThermalMax();
+ mPowerFactor = that.getPowerFactor();
mFlags = that.getFlags();
- mHbmMode = that.getHbmMode();
- mReason.set(that.getReason());
mAdjustmentFlags = that.getAdjustmentFlags();
+ // Auto-brightness setting
+ mAutomaticBrightnessEnabled = that.isAutomaticBrightnessEnabled();
}
/**
* A utility to reset the BrightnessEvent to default values
*/
public void reset() {
+ mReason.set(null);
mTime = SystemClock.uptimeMillis();
- mBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- mRecommendedBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mPhysicalDisplayId = "";
+ // Lux values
mLux = 0;
+ mFastAmbientLux = 0;
+ mSlowAmbientLux = 0;
mPreThresholdLux = 0;
+ // Brightness values
+ mInitialBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mRecommendedBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
mPreThresholdBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ // Different brightness modulations
+ mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
mHbmMax = PowerManager.BRIGHTNESS_MAX;
+ mRbcStrength = 0;
mThermalMax = PowerManager.BRIGHTNESS_MAX;
+ mPowerFactor = 1f;
mFlags = 0;
- mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
- mReason.set(null);
mAdjustmentFlags = 0;
+ // Auto-brightness setting
+ mAutomaticBrightnessEnabled = true;
}
/**
@@ -104,23 +133,34 @@ public final class BrightnessEvent {
public boolean equalsMainData(BrightnessEvent that) {
// This equals comparison purposefully ignores time since it is regularly changing and
// we don't want to log a brightness event just because the time changed.
- return mDisplayId == that.mDisplayId
+ return mReason.equals(that.mReason)
+ && mDisplayId == that.mDisplayId
+ && mPhysicalDisplayId.equals(that.mPhysicalDisplayId)
+ && Float.floatToRawIntBits(mLux) == Float.floatToRawIntBits(that.mLux)
+ && Float.floatToRawIntBits(mFastAmbientLux)
+ == Float.floatToRawIntBits(that.mFastAmbientLux)
+ && Float.floatToRawIntBits(mSlowAmbientLux)
+ == Float.floatToRawIntBits(that.mSlowAmbientLux)
+ && Float.floatToRawIntBits(mPreThresholdLux)
+ == Float.floatToRawIntBits(that.mPreThresholdLux)
+ && Float.floatToRawIntBits(mInitialBrightness)
+ == Float.floatToRawIntBits(that.mInitialBrightness)
&& Float.floatToRawIntBits(mBrightness)
== Float.floatToRawIntBits(that.mBrightness)
&& Float.floatToRawIntBits(mRecommendedBrightness)
== Float.floatToRawIntBits(that.mRecommendedBrightness)
&& Float.floatToRawIntBits(mPreThresholdBrightness)
== Float.floatToRawIntBits(that.mPreThresholdBrightness)
- && Float.floatToRawIntBits(mLux) == Float.floatToRawIntBits(that.mLux)
- && Float.floatToRawIntBits(mPreThresholdLux)
- == Float.floatToRawIntBits(that.mPreThresholdLux)
- && Float.floatToRawIntBits(mHbmMax) == Float.floatToRawIntBits(that.mHbmMax)
&& mHbmMode == that.mHbmMode
+ && Float.floatToRawIntBits(mHbmMax) == Float.floatToRawIntBits(that.mHbmMax)
+ && mRbcStrength == that.mRbcStrength
&& Float.floatToRawIntBits(mThermalMax)
== Float.floatToRawIntBits(that.mThermalMax)
+ && Float.floatToRawIntBits(mPowerFactor)
+ == Float.floatToRawIntBits(that.mPowerFactor)
&& mFlags == that.mFlags
&& mAdjustmentFlags == that.mAdjustmentFlags
- && mReason.equals(that.mReason);
+ && mAutomaticBrightnessEnabled == that.mAutomaticBrightnessEnabled;
}
/**
@@ -133,16 +173,23 @@ public final class BrightnessEvent {
return (includeTime ? TimeUtils.formatForLogging(mTime) + " - " : "")
+ "BrightnessEvent: "
+ "disp=" + mDisplayId
+ + ", physDisp=" + mPhysicalDisplayId
+ ", brt=" + mBrightness + ((mFlags & FLAG_USER_SET) != 0 ? "(user_set)" : "")
+ + ", initBrt=" + mInitialBrightness
+ ", rcmdBrt=" + mRecommendedBrightness
+ ", preBrt=" + mPreThresholdBrightness
+ ", lux=" + mLux
+ + ", fastLux=" + mFastAmbientLux
+ + ", slowLux=" + mSlowAmbientLux
+ ", preLux=" + mPreThresholdLux
+ ", hbmMax=" + mHbmMax
+ ", hbmMode=" + BrightnessInfo.hbmToString(mHbmMode)
+ + ", rbcStrength=" + mRbcStrength
+ ", thrmMax=" + mThermalMax
+ + ", powerFactor=" + mPowerFactor
+ ", flags=" + flagsToString()
- + ", reason=" + mReason.toString(mAdjustmentFlags);
+ + ", reason=" + mReason.toString(mAdjustmentFlags)
+ + ", autoBrightness=" + mAutomaticBrightnessEnabled;
}
@Override
@@ -150,12 +197,20 @@ public final class BrightnessEvent {
return toString(/* includeTime */ true);
}
+ public BrightnessReason getReason() {
+ return mReason;
+ }
+
public void setReason(BrightnessReason reason) {
this.mReason = reason;
}
- public BrightnessReason getReason() {
- return mReason;
+ public long getTime() {
+ return mTime;
+ }
+
+ public void setTime(long time) {
+ this.mTime = time;
}
public int getDisplayId() {
@@ -166,6 +221,14 @@ public final class BrightnessEvent {
this.mDisplayId = displayId;
}
+ public String getPhysicalDisplayId() {
+ return mPhysicalDisplayId;
+ }
+
+ public void setPhysicalDisplayId(String mPhysicalDisplayId) {
+ this.mPhysicalDisplayId = mPhysicalDisplayId;
+ }
+
public float getLux() {
return mLux;
}
@@ -174,6 +237,22 @@ public final class BrightnessEvent {
this.mLux = lux;
}
+ public float getFastAmbientLux() {
+ return mFastAmbientLux;
+ }
+
+ public void setFastAmbientLux(float mFastAmbientLux) {
+ this.mFastAmbientLux = mFastAmbientLux;
+ }
+
+ public float getSlowAmbientLux() {
+ return mSlowAmbientLux;
+ }
+
+ public void setSlowAmbientLux(float mSlowAmbientLux) {
+ this.mSlowAmbientLux = mSlowAmbientLux;
+ }
+
public float getPreThresholdLux() {
return mPreThresholdLux;
}
@@ -182,12 +261,12 @@ public final class BrightnessEvent {
this.mPreThresholdLux = preThresholdLux;
}
- public long getTime() {
- return mTime;
+ public float getInitialBrightness() {
+ return mInitialBrightness;
}
- public void setTime(long time) {
- this.mTime = time;
+ public void setInitialBrightness(float mInitialBrightness) {
+ this.mInitialBrightness = mInitialBrightness;
}
public float getBrightness() {
@@ -214,6 +293,14 @@ public final class BrightnessEvent {
this.mPreThresholdBrightness = preThresholdBrightness;
}
+ public int getHbmMode() {
+ return mHbmMode;
+ }
+
+ public void setHbmMode(int hbmMode) {
+ this.mHbmMode = hbmMode;
+ }
+
public float getHbmMax() {
return mHbmMax;
}
@@ -222,6 +309,14 @@ public final class BrightnessEvent {
this.mHbmMax = hbmMax;
}
+ public int getRbcStrength() {
+ return mRbcStrength;
+ }
+
+ public void setRbcStrength(int mRbcStrength) {
+ this.mRbcStrength = mRbcStrength;
+ }
+
public float getThermalMax() {
return mThermalMax;
}
@@ -230,12 +325,12 @@ public final class BrightnessEvent {
this.mThermalMax = thermalMax;
}
- public int getHbmMode() {
- return mHbmMode;
+ public float getPowerFactor() {
+ return mPowerFactor;
}
- public void setHbmMode(int hbmMode) {
- this.mHbmMode = hbmMode;
+ public void setPowerFactor(float mPowerFactor) {
+ this.mPowerFactor = mPowerFactor;
}
public int getFlags() {
@@ -246,6 +341,10 @@ public final class BrightnessEvent {
this.mFlags = flags;
}
+ public boolean isShortTermModelActive() {
+ return (mFlags & FLAG_USER_SET) != 0;
+ }
+
public int getAdjustmentFlags() {
return mAdjustmentFlags;
}
@@ -254,6 +353,14 @@ public final class BrightnessEvent {
this.mAdjustmentFlags = adjustmentFlags;
}
+ public boolean isAutomaticBrightnessEnabled() {
+ return mAutomaticBrightnessEnabled;
+ }
+
+ public void setAutomaticBrightnessEnabled(boolean mAutomaticBrightnessEnabled) {
+ this.mAutomaticBrightnessEnabled = mAutomaticBrightnessEnabled;
+ }
+
private String flagsToString() {
return ((mFlags & FLAG_USER_SET) != 0 ? "user_set " : "")
+ ((mFlags & FLAG_RBC) != 0 ? "rbc " : "")
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 6e304163995b..366dfd1efd29 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -1515,6 +1515,10 @@ public final class ColorDisplayService extends SystemService {
return mReduceBrightColorsTintController.isActivated();
}
+ public int getReduceBrightColorsStrength() {
+ return mReduceBrightColorsTintController.getStrength();
+ }
+
/**
* Gets the computed brightness, in nits, when the reduce bright colors feature is applied
* at the current strength.
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index d48acb18722a..23f437392d2b 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -910,11 +910,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
final String mImeControlTargetName;
@Nullable
final String mImeTargetNameFromWm;
+ @Nullable
+ final String mImeSurfaceParentName;
Entry(ClientState client, EditorInfo editorInfo, String focusedWindowName,
@SoftInputModeFlags int softInputMode, @SoftInputShowHideReason int reason,
boolean inFullscreenMode, String requestWindowName,
- @Nullable String imeControlTargetName, @Nullable String imeTargetName) {
+ @Nullable String imeControlTargetName, @Nullable String imeTargetName,
+ @Nullable String imeSurfaceParentName) {
mClientState = client;
mEditorInfo = editorInfo;
mFocusedWindowName = focusedWindowName;
@@ -926,6 +929,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
mRequestWindowName = requestWindowName;
mImeControlTargetName = imeControlTargetName;
mImeTargetNameFromWm = imeTargetName;
+ mImeSurfaceParentName = imeSurfaceParentName;
}
}
@@ -972,6 +976,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
pw.println(" imeTargetNameFromWm=" + entry.mImeTargetNameFromWm);
pw.print(prefix);
+ pw.println(" imeSurfaceParentName=" + entry.mImeSurfaceParentName);
+
+ pw.print(prefix);
pw.print(" editorInfo: ");
pw.print(" inputType=" + entry.mEditorInfo.inputType);
pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
@@ -4676,7 +4683,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
mCurFocusedWindowClient, mCurEditorInfo, info.focusedWindowName,
mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode,
- info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName));
+ info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName,
+ info.imeSurfaceParentName));
}
@BinderThread
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index e27cbeaab139..bfa8af957208 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -927,8 +927,9 @@ class MediaRouter2ServiceImpl {
routerRecord.mUserRecord.mHandler, routerRecord, manager));
}
- userRecord.mHandler.sendMessage(obtainMessage(UserHandler::notifyRoutesToManager,
- userRecord.mHandler, manager));
+ userRecord.mHandler.sendMessage(
+ obtainMessage(
+ UserHandler::notifyInitialRoutesToManager, userRecord.mHandler, manager));
}
private void unregisterManagerLocked(@NonNull IMediaRouter2Manager manager, boolean died) {
@@ -1311,6 +1312,36 @@ class MediaRouter2ServiceImpl {
new CopyOnWriteArrayList<>();
private final Map<String, RouterRecord> mSessionToRouterMap = new ArrayMap<>();
+ /**
+ * Latest list of routes sent to privileged {@link android.media.MediaRouter2 routers} and
+ * {@link android.media.MediaRouter2Manager managers}.
+ *
+ * <p>Privileged routers are instances of {@link android.media.MediaRouter2 MediaRouter2}
+ * that have {@code MODIFY_AUDIO_ROUTING} permission.
+ *
+ * <p>This list contains all routes exposed by route providers. This includes routes from
+ * both system route providers and user route providers.
+ *
+ * <p>See {@link #getRouters(boolean hasModifyAudioRoutingPermission)}.
+ */
+ private final Map<String, MediaRoute2Info> mLastNotifiedRoutesToPrivilegedRouters =
+ new ArrayMap<>();
+
+ /**
+ * Latest list of routes sent to non-privileged {@link android.media.MediaRouter2 routers}.
+ *
+ * <p>Non-privileged routers are instances of {@link android.media.MediaRouter2
+ * MediaRouter2} that do <i><b>not</b></i> have {@code MODIFY_AUDIO_ROUTING} permission.
+ *
+ * <p>This list contains all routes exposed by user route providers. It might also include
+ * the current default route from {@link #mSystemProvider} to expose local route updates
+ * (e.g. volume changes) to non-privileged routers.
+ *
+ * <p>See {@link SystemMediaRoute2Provider#mDefaultRoute}.
+ */
+ private final Map<String, MediaRoute2Info> mLastNotifiedRoutesToNonPrivilegedRouters =
+ new ArrayMap<>();
+
private boolean mRunning;
// TODO: (In Android S+) Pull out SystemMediaRoute2Provider out of UserHandler.
@@ -1425,91 +1456,182 @@ class MediaRouter2ServiceImpl {
}
private void onProviderStateChangedOnHandler(@NonNull MediaRoute2Provider provider) {
- int providerInfoIndex = getLastProviderInfoIndex(provider.getUniqueId());
MediaRoute2ProviderInfo currentInfo = provider.getProviderInfo();
+
+ int providerInfoIndex =
+ indexOfRouteProviderInfoByUniqueId(provider.getUniqueId(), mLastProviderInfos);
+
MediaRoute2ProviderInfo prevInfo =
- (providerInfoIndex < 0) ? null : mLastProviderInfos.get(providerInfoIndex);
- if (Objects.equals(prevInfo, currentInfo)) return;
+ providerInfoIndex == -1 ? null : mLastProviderInfos.get(providerInfoIndex);
+
+ // Ignore if no changes
+ if (Objects.equals(prevInfo, currentInfo)) {
+ return;
+ }
+
+ boolean hasAddedOrModifiedRoutes = false;
+ boolean hasRemovedRoutes = false;
+
+ boolean isSystemProvider = provider.mIsSystemRouteProvider;
- List<MediaRoute2Info> addedRoutes = new ArrayList<>();
- List<MediaRoute2Info> removedRoutes = new ArrayList<>();
- List<MediaRoute2Info> changedRoutes = new ArrayList<>();
if (prevInfo == null) {
+ // Provider is being added.
mLastProviderInfos.add(currentInfo);
- addedRoutes.addAll(currentInfo.getRoutes());
+ addToRoutesMap(currentInfo.getRoutes(), isSystemProvider);
+ // Check if new provider exposes routes.
+ hasAddedOrModifiedRoutes = !currentInfo.getRoutes().isEmpty();
} else if (currentInfo == null) {
+ // Provider is being removed.
+ hasRemovedRoutes = true;
mLastProviderInfos.remove(prevInfo);
- removedRoutes.addAll(prevInfo.getRoutes());
+ removeFromRoutesMap(prevInfo.getRoutes(), isSystemProvider);
} else {
+ // Provider is being updated.
mLastProviderInfos.set(providerInfoIndex, currentInfo);
- final Collection<MediaRoute2Info> prevRoutes = prevInfo.getRoutes();
final Collection<MediaRoute2Info> currentRoutes = currentInfo.getRoutes();
+ // Checking for individual routes.
for (MediaRoute2Info route : currentRoutes) {
if (!route.isValid()) {
- Slog.w(TAG, "onProviderStateChangedOnHandler: Ignoring invalid route : "
- + route);
+ Slog.w(
+ TAG,
+ "onProviderStateChangedOnHandler: Ignoring invalid route : "
+ + route);
continue;
}
+
MediaRoute2Info prevRoute = prevInfo.getRoute(route.getOriginalId());
- if (prevRoute == null) {
- addedRoutes.add(route);
- } else if (!Objects.equals(prevRoute, route)) {
- changedRoutes.add(route);
+ if (prevRoute == null || !Objects.equals(prevRoute, route)) {
+ hasAddedOrModifiedRoutes = true;
+ mLastNotifiedRoutesToPrivilegedRouters.put(route.getId(), route);
+ if (!isSystemProvider) {
+ mLastNotifiedRoutesToNonPrivilegedRouters.put(route.getId(), route);
+ }
}
}
+ // Checking for individual removals
for (MediaRoute2Info prevRoute : prevInfo.getRoutes()) {
if (currentInfo.getRoute(prevRoute.getOriginalId()) == null) {
- removedRoutes.add(prevRoute);
+ hasRemovedRoutes = true;
+ mLastNotifiedRoutesToPrivilegedRouters.remove(prevRoute.getId());
+ if (!isSystemProvider) {
+ mLastNotifiedRoutesToNonPrivilegedRouters.remove(prevRoute.getId());
+ }
}
}
}
+ dispatchUpdates(
+ hasAddedOrModifiedRoutes,
+ hasRemovedRoutes,
+ isSystemProvider,
+ mSystemProvider.getDefaultRoute());
+ }
+
+ /**
+ * Adds provided routes to {@link #mLastNotifiedRoutesToPrivilegedRouters}. Also adds them
+ * to {@link #mLastNotifiedRoutesToNonPrivilegedRouters} if they were provided by a
+ * non-system route provider. Overwrites any route with matching id that already exists.
+ *
+ * @param routes list of routes to be added.
+ * @param isSystemRoutes indicates whether routes come from a system route provider.
+ */
+ private void addToRoutesMap(
+ @NonNull Collection<MediaRoute2Info> routes, boolean isSystemRoutes) {
+ for (MediaRoute2Info route : routes) {
+ if (!isSystemRoutes) {
+ mLastNotifiedRoutesToNonPrivilegedRouters.put(route.getId(), route);
+ }
+ mLastNotifiedRoutesToPrivilegedRouters.put(route.getId(), route);
+ }
+ }
+
+ /**
+ * Removes provided routes from {@link #mLastNotifiedRoutesToPrivilegedRouters}. Also
+ * removes them from {@link #mLastNotifiedRoutesToNonPrivilegedRouters} if they were
+ * provided by a non-system route provider.
+ *
+ * @param routes list of routes to be removed.
+ * @param isSystemRoutes whether routes come from a system route provider.
+ */
+ private void removeFromRoutesMap(
+ @NonNull Collection<MediaRoute2Info> routes, boolean isSystemRoutes) {
+ for (MediaRoute2Info route : routes) {
+ if (!isSystemRoutes) {
+ mLastNotifiedRoutesToNonPrivilegedRouters.remove(route.getId());
+ }
+ mLastNotifiedRoutesToPrivilegedRouters.remove(route.getId());
+ }
+ }
+
+ /**
+ * Dispatches the latest route updates in {@link #mLastNotifiedRoutesToPrivilegedRouters}
+ * and {@link #mLastNotifiedRoutesToNonPrivilegedRouters} to registered {@link
+ * android.media.MediaRouter2 routers} and {@link MediaRouter2Manager managers} after a call
+ * to {@link #onProviderStateChangedOnHandler(MediaRoute2Provider)}. Ignores if no changes
+ * were made.
+ *
+ * @param hasAddedOrModifiedRoutes whether routes were added or modified.
+ * @param hasRemovedRoutes whether routes were removed.
+ * @param isSystemProvider whether the latest update was caused by a system provider.
+ * @param defaultRoute the current default route in {@link #mSystemProvider}.
+ */
+ private void dispatchUpdates(
+ boolean hasAddedOrModifiedRoutes,
+ boolean hasRemovedRoutes,
+ boolean isSystemProvider,
+ MediaRoute2Info defaultRoute) {
+
+ // Ignore if no changes.
+ if (!hasAddedOrModifiedRoutes && !hasRemovedRoutes) {
+ return;
+ }
+
List<IMediaRouter2> routersWithModifyAudioRoutingPermission = getRouters(true);
List<IMediaRouter2> routersWithoutModifyAudioRoutingPermission = getRouters(false);
List<IMediaRouter2Manager> managers = getManagers();
- List<MediaRoute2Info> defaultRoute = new ArrayList<>();
- defaultRoute.add(mSystemProvider.getDefaultRoute());
-
- if (addedRoutes.size() > 0) {
- notifyRoutesAddedToRouters(routersWithModifyAudioRoutingPermission, addedRoutes);
- if (!provider.mIsSystemRouteProvider) {
- notifyRoutesAddedToRouters(routersWithoutModifyAudioRoutingPermission,
- addedRoutes);
- } else if (prevInfo == null) {
- notifyRoutesAddedToRouters(routersWithoutModifyAudioRoutingPermission,
- defaultRoute);
- } // 'else' is handled as changed routes
- notifyRoutesAddedToManagers(managers, addedRoutes);
- }
- if (removedRoutes.size() > 0) {
- notifyRoutesRemovedToRouters(routersWithModifyAudioRoutingPermission,
- removedRoutes);
- if (!provider.mIsSystemRouteProvider) {
- notifyRoutesRemovedToRouters(routersWithoutModifyAudioRoutingPermission,
- removedRoutes);
- }
- notifyRoutesRemovedToManagers(managers, removedRoutes);
- }
- if (changedRoutes.size() > 0) {
- notifyRoutesChangedToRouters(routersWithModifyAudioRoutingPermission,
- changedRoutes);
- if (!provider.mIsSystemRouteProvider) {
- notifyRoutesChangedToRouters(routersWithoutModifyAudioRoutingPermission,
- changedRoutes);
- } else if (prevInfo != null) {
- notifyRoutesChangedToRouters(routersWithoutModifyAudioRoutingPermission,
- defaultRoute);
- } // 'else' is handled as added routes
- notifyRoutesChangedToManagers(managers, changedRoutes);
- }
- }
-
- private int getLastProviderInfoIndex(@NonNull String providerId) {
- for (int i = 0; i < mLastProviderInfos.size(); i++) {
- MediaRoute2ProviderInfo providerInfo = mLastProviderInfos.get(i);
- if (TextUtils.equals(providerInfo.getUniqueId(), providerId)) {
+
+ // Managers receive all provider updates with all routes.
+ notifyRoutesUpdatedToManagers(
+ managers, new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
+
+ // Routers with modify audio permission (usually system routers) receive all provider
+ // updates with all routes.
+ notifyRoutesUpdatedToRouters(
+ routersWithModifyAudioRoutingPermission,
+ new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
+
+ if (!isSystemProvider) {
+ // Regular routers receive updates from all non-system providers with all non-system
+ // routes.
+ notifyRoutesUpdatedToRouters(
+ routersWithoutModifyAudioRoutingPermission,
+ new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values()));
+ } else if (hasAddedOrModifiedRoutes) {
+ // On system provider updates, regular routers receive the updated default route.
+ // This is the only system route they should receive.
+ mLastNotifiedRoutesToNonPrivilegedRouters.put(defaultRoute.getId(), defaultRoute);
+ notifyRoutesUpdatedToRouters(
+ routersWithoutModifyAudioRoutingPermission,
+ new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values()));
+ }
+ }
+
+ /**
+ * Returns the index of the first element in {@code lastProviderInfos} that matches the
+ * specified unique id.
+ *
+ * @param uniqueId unique id of {@link MediaRoute2ProviderInfo} to be found.
+ * @param lastProviderInfos list of {@link MediaRoute2ProviderInfo}.
+ * @return index of found element, or -1 if not found.
+ */
+ private static int indexOfRouteProviderInfoByUniqueId(
+ @NonNull String uniqueId,
+ @NonNull List<MediaRoute2ProviderInfo> lastProviderInfos) {
+ for (int i = 0; i < lastProviderInfos.size(); i++) {
+ MediaRoute2ProviderInfo providerInfo = lastProviderInfos.get(i);
+ if (TextUtils.equals(providerInfo.getUniqueId(), uniqueId)) {
return i;
}
}
@@ -1989,41 +2111,19 @@ class MediaRouter2ServiceImpl {
}
}
- private void notifyRoutesAddedToRouters(@NonNull List<IMediaRouter2> routers,
- @NonNull List<MediaRoute2Info> routes) {
- for (IMediaRouter2 router : routers) {
- try {
- router.notifyRoutesAdded(routes);
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify routes added. Router probably died.", ex);
- }
- }
- }
-
- private void notifyRoutesRemovedToRouters(@NonNull List<IMediaRouter2> routers,
- @NonNull List<MediaRoute2Info> routes) {
- for (IMediaRouter2 router : routers) {
- try {
- router.notifyRoutesRemoved(routes);
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify routes removed. Router probably died.", ex);
- }
- }
- }
-
- private void notifyRoutesChangedToRouters(@NonNull List<IMediaRouter2> routers,
- @NonNull List<MediaRoute2Info> routes) {
+ private void notifyRoutesUpdatedToRouters(
+ @NonNull List<IMediaRouter2> routers, @NonNull List<MediaRoute2Info> routes) {
for (IMediaRouter2 router : routers) {
try {
- router.notifyRoutesChanged(routes);
+ router.notifyRoutesUpdated(routes);
} catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify routes changed. Router probably died.", ex);
+ Slog.w(TAG, "Failed to notify routes updated. Router probably died.", ex);
}
}
}
- private void notifySessionInfoChangedToRouters(@NonNull List<IMediaRouter2> routers,
- @NonNull RoutingSessionInfo sessionInfo) {
+ private void notifySessionInfoChangedToRouters(
+ @NonNull List<IMediaRouter2> routers, @NonNull RoutingSessionInfo sessionInfo) {
for (IMediaRouter2 router : routers) {
try {
router.notifySessionInfoChanged(sessionInfo);
@@ -2033,48 +2133,31 @@ class MediaRouter2ServiceImpl {
}
}
- private void notifyRoutesToManager(@NonNull IMediaRouter2Manager manager) {
- List<MediaRoute2Info> routes = new ArrayList<>();
- for (MediaRoute2ProviderInfo providerInfo : mLastProviderInfos) {
- routes.addAll(providerInfo.getRoutes());
- }
- if (routes.size() == 0) {
+ /**
+ * Notifies {@code manager} with all known routes. This only happens once after {@code
+ * manager} is registered through {@link #registerManager(IMediaRouter2Manager, String)
+ * registerManager()}.
+ *
+ * @param manager {@link IMediaRouter2Manager} to be notified.
+ */
+ private void notifyInitialRoutesToManager(@NonNull IMediaRouter2Manager manager) {
+ if (mLastNotifiedRoutesToPrivilegedRouters.isEmpty()) {
return;
}
try {
- manager.notifyRoutesAdded(routes);
+ manager.notifyRoutesUpdated(
+ new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify all routes. Manager probably died.", ex);
}
}
- private void notifyRoutesAddedToManagers(@NonNull List<IMediaRouter2Manager> managers,
- @NonNull List<MediaRoute2Info> routes) {
- for (IMediaRouter2Manager manager : managers) {
- try {
- manager.notifyRoutesAdded(routes);
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify routes added. Manager probably died.", ex);
- }
- }
- }
-
- private void notifyRoutesRemovedToManagers(@NonNull List<IMediaRouter2Manager> managers,
- @NonNull List<MediaRoute2Info> routes) {
- for (IMediaRouter2Manager manager : managers) {
- try {
- manager.notifyRoutesRemoved(routes);
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify routes removed. Manager probably died.", ex);
- }
- }
- }
-
- private void notifyRoutesChangedToManagers(@NonNull List<IMediaRouter2Manager> managers,
+ private void notifyRoutesUpdatedToManagers(
+ @NonNull List<IMediaRouter2Manager> managers,
@NonNull List<MediaRoute2Info> routes) {
for (IMediaRouter2Manager manager : managers) {
try {
- manager.notifyRoutesChanged(routes);
+ manager.notifyRoutesUpdated(routes);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify routes changed. Manager probably died.", ex);
}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index a21919cdb960..d48af213dfc7 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -1,5 +1,6 @@
package com.android.server.wm;
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.ActivityManager.processStateAmToProto;
@@ -274,6 +275,8 @@ class ActivityMetricsLogger {
final boolean mProcessRunning;
/** whether the process of the launching activity didn't have any active activity. */
final boolean mProcessSwitch;
+ /** The process state of the launching activity prior to the launch */
+ final int mProcessState;
/** Whether the last launched activity has reported drawn. */
boolean mIsDrawn;
/** The latest activity to have been launched. */
@@ -309,8 +312,8 @@ class ActivityMetricsLogger {
@Nullable
static TransitionInfo create(@NonNull ActivityRecord r,
@NonNull LaunchingState launchingState, @Nullable ActivityOptions options,
- boolean processRunning, boolean processSwitch, boolean newActivityCreated,
- int startResult) {
+ boolean processRunning, boolean processSwitch, int processState,
+ boolean newActivityCreated, int startResult) {
if (startResult != START_SUCCESS && startResult != START_TASK_TO_FRONT) {
return null;
}
@@ -325,18 +328,19 @@ class ActivityMetricsLogger {
transitionType = TYPE_TRANSITION_COLD_LAUNCH;
}
return new TransitionInfo(r, launchingState, options, transitionType, processRunning,
- processSwitch);
+ processSwitch, processState);
}
/** Use {@link TransitionInfo#create} instead to ensure the transition type is valid. */
private TransitionInfo(ActivityRecord r, LaunchingState launchingState,
ActivityOptions options, int transitionType, boolean processRunning,
- boolean processSwitch) {
+ boolean processSwitch, int processState) {
mLaunchingState = launchingState;
mTransitionStartTimeNs = launchingState.mCurrentTransitionStartTimeNs;
mTransitionType = transitionType;
mProcessRunning = processRunning;
mProcessSwitch = processSwitch;
+ mProcessState = processState;
mTransitionDeviceUptimeMs = launchingState.mCurrentUpTimeMs;
setLatestLaunchedActivity(r);
// The launching state can be reused by consecutive launch. Its original association
@@ -640,12 +644,16 @@ class ActivityMetricsLogger {
// interesting.
final boolean processSwitch = !processRunning
|| !processRecord.hasStartedActivity(launchedActivity);
+ final int processState = processRunning
+ ? processRecord.getCurrentProcState()
+ : PROCESS_STATE_NONEXISTENT;
final TransitionInfo info = launchingState.mAssociatedTransitionInfo;
if (DEBUG_METRICS) {
Slog.i(TAG, "notifyActivityLaunched" + " resultCode=" + resultCode
+ " launchedActivity=" + launchedActivity + " processRunning=" + processRunning
+ " processSwitch=" + processSwitch
+ + " processState=" + processState
+ " newActivityCreated=" + newActivityCreated + " info=" + info);
}
@@ -681,7 +689,8 @@ class ActivityMetricsLogger {
}
final TransitionInfo newInfo = TransitionInfo.create(launchedActivity, launchingState,
- options, processRunning, processSwitch, newActivityCreated, resultCode);
+ options, processRunning, processSwitch, processState, newActivityCreated,
+ resultCode);
if (newInfo == null) {
abort(launchingState, "unrecognized launch");
return;
@@ -996,8 +1005,9 @@ class ActivityMetricsLogger {
final long timestamp = info.mTransitionStartTimeNs;
final long uptime = info.mTransitionDeviceUptimeMs;
final int transitionDelay = info.mCurrentTransitionDelayMs;
+ final int processState = info.mProcessState;
mLoggerHandler.post(() -> logAppTransition(
- timestamp, uptime, transitionDelay, infoSnapshot, isHibernating));
+ timestamp, uptime, transitionDelay, infoSnapshot, isHibernating, processState));
}
mLoggerHandler.post(() -> logAppDisplayed(infoSnapshot));
if (info.mPendingFullyDrawn != null) {
@@ -1009,7 +1019,8 @@ class ActivityMetricsLogger {
// This gets called on another thread without holding the activity manager lock.
private void logAppTransition(long transitionStartTimeNs, long transitionDeviceUptimeMs,
- int currentTransitionDelayMs, TransitionInfoSnapshot info, boolean isHibernating) {
+ int currentTransitionDelayMs, TransitionInfoSnapshot info, boolean isHibernating,
+ int processState) {
final LogMaker builder = new LogMaker(APP_TRANSITION);
builder.setPackageName(info.packageName);
builder.setType(info.type);
@@ -1075,7 +1086,8 @@ class ActivityMetricsLogger {
isIncremental,
isLoading,
info.launchedActivityName.hashCode(),
- TimeUnit.NANOSECONDS.toMillis(transitionStartTimeNs));
+ TimeUnit.NANOSECONDS.toMillis(transitionStartTimeNs),
+ processState);
if (DEBUG_METRICS) {
Slog.i(TAG, String.format("APP_START_OCCURRED(%s, %s, %s, %s, %s)",
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fccf54d83198..fa3fc9ffb69c 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -53,7 +53,6 @@ import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.view.View.GONE;
-import static android.view.ViewRootImpl.LOCAL_LAYOUT;
import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.systemBars;
@@ -950,7 +949,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final int preferredModeId = getDisplayPolicy().getRefreshRatePolicy()
.getPreferredModeId(w);
- if (mTmpApplySurfaceChangesTransactionState.preferredModeId == 0
+ if (w.isFocused() && mTmpApplySurfaceChangesTransactionState.preferredModeId == 0
&& preferredModeId != 0) {
mTmpApplySurfaceChangesTransactionState.preferredModeId = preferredModeId;
}
@@ -2710,25 +2709,22 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mCurrentPrivacyIndicatorBounds =
mCurrentPrivacyIndicatorBounds.updateStaticBounds(staticBounds);
if (!Objects.equals(oldBounds, mCurrentPrivacyIndicatorBounds)) {
- updateDisplayFrames(false /* insetsSourceMayChange */, true /* notifyInsetsChange */);
+ updateDisplayFrames(true /* notifyInsetsChange */);
}
}
void onDisplayInfoChanged() {
- updateDisplayFrames(LOCAL_LAYOUT, LOCAL_LAYOUT);
+ updateDisplayFrames(false /* notifyInsetsChange */);
mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
mInputMonitor.layoutInputConsumers(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
mDisplayPolicy.onDisplayInfoChanged(mDisplayInfo);
}
- private void updateDisplayFrames(boolean insetsSourceMayChange, boolean notifyInsetsChange) {
+ private void updateDisplayFrames(boolean notifyInsetsChange) {
if (mDisplayFrames.update(mDisplayInfo,
calculateDisplayCutoutForRotation(mDisplayInfo.rotation),
calculateRoundedCornersForRotation(mDisplayInfo.rotation),
calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation))) {
- if (insetsSourceMayChange) {
- mDisplayPolicy.updateInsetsSourceFramesExceptIme(mDisplayFrames);
- }
mInsetsStateController.onDisplayFramesUpdated(notifyInsetsChange);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 07694065cbd7..5221072f696d 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1576,19 +1576,6 @@ public class DisplayPolicy {
}
}
- void updateInsetsSourceFramesExceptIme(DisplayFrames displayFrames) {
- sTmpClientFrames.attachedFrame = null;
- for (int i = mInsetsSourceWindowsExceptIme.size() - 1; i >= 0; i--) {
- final WindowState win = mInsetsSourceWindowsExceptIme.valueAt(i);
- mWindowLayout.computeFrames(win.mAttrs.forRotation(displayFrames.mRotation),
- displayFrames.mInsetsState, displayFrames.mDisplayCutoutSafe,
- displayFrames.mUnrestricted, win.getWindowingMode(), UNSPECIFIED_LENGTH,
- UNSPECIFIED_LENGTH, win.getRequestedVisibilities(), win.mGlobalScale,
- sTmpClientFrames);
- win.updateSourceFrame(sTmpClientFrames.frame);
- }
- }
-
void onDisplayInfoChanged(DisplayInfo info) {
mSystemGestures.onDisplayInfoChanged(info);
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 0128c187bbdd..fb68fe666c0b 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -242,16 +242,17 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
@Override
public int relayout(IWindow window, WindowManager.LayoutParams attrs,
- int requestedWidth, int requestedHeight, int viewFlags, int flags,
- ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
- SurfaceControl outSurfaceControl, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls, Bundle outSyncSeqIdBundle) {
+ int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
+ int lastSyncSeqId, ClientWindowFrames outFrames,
+ MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl,
+ InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
+ Bundle outSyncSeqIdBundle) {
if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from "
+ Binder.getCallingPid());
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag);
int res = mService.relayoutWindow(this, window, attrs,
- requestedWidth, requestedHeight, viewFlags, flags,
- outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
+ requestedWidth, requestedHeight, viewFlags, flags, seq,
+ lastSyncSeqId, outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
outActiveControls, outSyncSeqIdBundle);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to "
@@ -260,6 +261,16 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
}
@Override
+ public void relayoutAsync(IWindow window, WindowManager.LayoutParams attrs,
+ int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
+ int lastSyncSeqId) {
+ relayout(window, attrs, requestedWidth, requestedHeight, viewFlags, flags, seq,
+ lastSyncSeqId, null /* outFrames */, null /* mergedConfiguration */,
+ null /* outSurfaceControl */, null /* outInsetsState */,
+ null /* outActiveControls */, null /* outSyncIdBundle */);
+ }
+
+ @Override
public boolean outOfMemory(IWindow window) {
return mService.outOfMemoryWindow(this, window);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index da5f5d0e29fd..a71c3866ba38 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -810,12 +810,17 @@ public abstract class WindowManagerInternal {
*/
public final String imeLayerTargetName;
+ /** The surface parent of the IME container. */
+ public final String imeSurfaceParentName;
+
public ImeTargetInfo(String focusedWindowName, String requestWindowName,
- String imeControlTargetName, String imeLayerTargetName) {
+ String imeControlTargetName, String imeLayerTargetName,
+ String imeSurfaceParentName) {
this.focusedWindowName = focusedWindowName;
this.requestWindowName = requestWindowName;
this.imeControlTargetName = imeControlTargetName;
this.imeLayerTargetName = imeLayerTargetName;
+ this.imeSurfaceParentName = imeSurfaceParentName;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index fd54f78b22fe..6544f821f13c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2248,11 +2248,14 @@ public class WindowManagerService extends IWindowManager.Stub
}
public int relayoutWindow(Session session, IWindow client, LayoutParams attrs,
- int requestedWidth, int requestedHeight, int viewVisibility, int flags,
- ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
- SurfaceControl outSurfaceControl, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls, Bundle outSyncIdBundle) {
- Arrays.fill(outActiveControls, null);
+ int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq,
+ int lastSyncSeqId, ClientWindowFrames outFrames,
+ MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl,
+ InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
+ Bundle outSyncIdBundle) {
+ if (outActiveControls != null) {
+ Arrays.fill(outActiveControls, null);
+ }
int result = 0;
boolean configChanged;
final int pid = Binder.getCallingPid();
@@ -2263,8 +2266,15 @@ public class WindowManagerService extends IWindowManager.Stub
if (win == null) {
return 0;
}
+ if (win.mRelayoutSeq < seq) {
+ win.mRelayoutSeq = seq;
+ } else if (win.mRelayoutSeq > seq) {
+ return 0;
+ }
- if (win.cancelAndRedraw() && win.mPrepareSyncSeqId <= win.mLastSeqIdSentToRelayout) {
+ if (win.cancelAndRedraw() && win.mPrepareSyncSeqId <= lastSyncSeqId) {
+ // The client has reported the sync draw, but we haven't finished it yet.
+ // Don't let the client perform a non-sync draw at this time.
result |= RELAYOUT_RES_CANCEL_AND_REDRAW;
}
@@ -2427,7 +2437,7 @@ public class WindowManagerService extends IWindowManager.Stub
// Create surfaceControl before surface placement otherwise layout will be skipped
// (because WS.isGoneForLayout() is true when there is no surface.
- if (shouldRelayout) {
+ if (shouldRelayout && outSurfaceControl != null) {
try {
result = createSurfaceControl(outSurfaceControl, result, win, winAnimator);
} catch (Exception e) {
@@ -2466,22 +2476,25 @@ public class WindowManagerService extends IWindowManager.Stub
winAnimator.mEnterAnimationPending = false;
winAnimator.mEnteringAnimation = false;
- if (viewVisibility == View.VISIBLE && winAnimator.hasSurface()) {
- // We already told the client to go invisible, but the message may not be
- // handled yet, or it might want to draw a last frame. If we already have a
- // surface, let the client use that, but don't create new surface at this point.
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: getSurface");
- winAnimator.mSurfaceController.getSurfaceControl(outSurfaceControl);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- } else {
- if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win);
-
- try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmReleaseOutSurface_"
- + win.mAttrs.getTitle());
- outSurfaceControl.release();
- } finally {
+ if (outSurfaceControl != null) {
+ if (viewVisibility == View.VISIBLE && winAnimator.hasSurface()) {
+ // We already told the client to go invisible, but the message may not be
+ // handled yet, or it might want to draw a last frame. If we already have a
+ // surface, let the client use that, but don't create new surface at this
+ // point.
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: getSurface");
+ winAnimator.mSurfaceController.getSurfaceControl(outSurfaceControl);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ } else {
+ if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win);
+
+ try {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmReleaseOutSurface_"
+ + win.mAttrs.getTitle());
+ outSurfaceControl.release();
+ } finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
}
}
@@ -2534,20 +2547,16 @@ public class WindowManagerService extends IWindowManager.Stub
win.mResizedWhileGone = false;
}
- win.fillClientWindowFramesAndConfiguration(outFrames, mergedConfiguration,
- false /* useLatestConfig */, shouldRelayout);
+ if (outFrames != null && outMergedConfiguration != null) {
+ win.fillClientWindowFramesAndConfiguration(outFrames, outMergedConfiguration,
+ false /* useLatestConfig */, shouldRelayout);
- // Set resize-handled here because the values are sent back to the client.
- win.onResizeHandled();
+ // Set resize-handled here because the values are sent back to the client.
+ win.onResizeHandled();
+ }
- outInsetsState.set(win.getCompatInsetsState(), win.isClientLocal());
- if (DEBUG) {
- Slog.v(TAG_WM, "Relayout given client " + client.asBinder()
- + ", requestedWidth=" + requestedWidth
- + ", requestedHeight=" + requestedHeight
- + ", viewVisibility=" + viewVisibility
- + "\nRelayout returning frame=" + outFrames.frame
- + ", surface=" + outSurfaceControl);
+ if (outInsetsState != null) {
+ outInsetsState.set(win.getCompatInsetsState(), win.isClientLocal());
}
ProtoLog.v(WM_DEBUG_FOCUS, "Relayout of %s: focusMayChange=%b",
@@ -2558,14 +2567,16 @@ public class WindowManagerService extends IWindowManager.Stub
}
win.mInRelayout = false;
- if (USE_BLAST_SYNC && win.useBLASTSync() && viewVisibility != View.GONE
- && (win.mSyncSeqId > win.mLastSeqIdSentToRelayout)) {
- win.markRedrawForSyncReported();
-
- win.mLastSeqIdSentToRelayout = win.mSyncSeqId;
- outSyncIdBundle.putInt("seqid", win.mSyncSeqId);
- } else {
- outSyncIdBundle.putInt("seqid", -1);
+ if (outSyncIdBundle != null) {
+ final int maybeSyncSeqId;
+ if (USE_BLAST_SYNC && win.useBLASTSync() && viewVisibility != View.GONE
+ && win.mSyncSeqId > lastSyncSeqId) {
+ maybeSyncSeqId = win.mSyncSeqId;
+ win.markRedrawForSyncReported();
+ } else {
+ maybeSyncSeqId = -1;
+ }
+ outSyncIdBundle.putInt("seqid", maybeSyncSeqId);
}
if (configChanged) {
@@ -2574,7 +2585,9 @@ public class WindowManagerService extends IWindowManager.Stub
displayContent.sendNewConfiguration();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
- getInsetsSourceControls(win, outActiveControls);
+ if (outActiveControls != null) {
+ getInsetsSourceControls(win, outActiveControls);
+ }
}
Binder.restoreCallingIdentity(origId);
@@ -8202,6 +8215,7 @@ public class WindowManagerService extends IWindowManager.Stub
final String requestWindowName;
final String imeControlTargetName;
final String imeLayerTargetName;
+ final String imeSurfaceParentName;
synchronized (mGlobalLock) {
final WindowState focusedWin = mWindowMap.get(focusedToken);
focusedWindowName = focusedWin != null ? focusedWin.getName() : "null";
@@ -8218,15 +8232,17 @@ public class WindowManagerService extends IWindowManager.Stub
}
final InsetsControlTarget target = dc.getImeTarget(IME_TARGET_LAYERING);
imeLayerTargetName = target != null ? target.getWindow().getName() : "null";
+ final SurfaceControl imeParent = dc.mInputMethodSurfaceParent;
+ imeSurfaceParentName = imeParent != null ? imeParent.toString() : "null";
if (show) {
dc.onShowImeRequested();
}
} else {
- imeControlTargetName = imeLayerTargetName = "no-display";
+ imeControlTargetName = imeLayerTargetName = imeSurfaceParentName = "no-display";
}
}
return new ImeTargetInfo(focusedWindowName, requestWindowName, imeControlTargetName,
- imeLayerTargetName);
+ imeLayerTargetName, imeSurfaceParentName);
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 283010ea5767..d222a566bd9d 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -389,7 +389,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
* examine the git commit message introducing this comment and variable.2
*/
int mSyncSeqId = 0;
- int mLastSeqIdSentToRelayout = 0;
/** The last syncId associated with a prepareSync or 0 when no sync is active. */
int mPrepareSyncSeqId = 0;
@@ -425,6 +424,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
boolean mHaveFrame;
boolean mObscured;
+ int mRelayoutSeq = -1;
int mLayoutSeq = -1;
/**
@@ -1349,29 +1349,15 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
final WindowFrames windowFrames = mWindowFrames;
mTmpRect.set(windowFrames.mParentFrame);
- if (LOCAL_LAYOUT) {
- windowFrames.mCompatFrame.set(clientWindowFrames.frame);
-
- windowFrames.mFrame.set(clientWindowFrames.frame);
- windowFrames.mDisplayFrame.set(clientWindowFrames.displayFrame);
- windowFrames.mParentFrame.set(clientWindowFrames.parentFrame);
- if (mGlobalScale != 1f) {
- // The frames sent from the client need to be adjusted to the real coordinate space.
- windowFrames.mFrame.scale(mGlobalScale);
- windowFrames.mDisplayFrame.scale(mGlobalScale);
- windowFrames.mParentFrame.scale(mGlobalScale);
- }
- } else {
- windowFrames.mDisplayFrame.set(clientWindowFrames.displayFrame);
- windowFrames.mParentFrame.set(clientWindowFrames.parentFrame);
- windowFrames.mFrame.set(clientWindowFrames.frame);
+ windowFrames.mDisplayFrame.set(clientWindowFrames.displayFrame);
+ windowFrames.mParentFrame.set(clientWindowFrames.parentFrame);
+ windowFrames.mFrame.set(clientWindowFrames.frame);
- windowFrames.mCompatFrame.set(windowFrames.mFrame);
- if (mInvGlobalScale != 1f) {
- // Also, the scaled frame that we report to the app needs to be adjusted to be in
- // its coordinate space.
- windowFrames.mCompatFrame.scale(mInvGlobalScale);
- }
+ windowFrames.mCompatFrame.set(windowFrames.mFrame);
+ if (mInvGlobalScale != 1f) {
+ // Also, the scaled frame that we report to the app needs to be adjusted to be in
+ // its coordinate space.
+ windowFrames.mCompatFrame.scale(mInvGlobalScale);
}
windowFrames.setParentFrameWasClippedByDisplayCutout(
clientWindowFrames.isParentFrameClippedByDisplayCutout);
@@ -1415,13 +1401,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
updateSourceFrame(windowFrames.mFrame);
- if (LOCAL_LAYOUT) {
- if (!mHaveFrame) {
- // The first frame should not be considered as moved.
- updateLastFrames();
- }
- }
-
if (mActivityRecord != null && !mIsChildWindow) {
mActivityRecord.layoutLetterbox(this);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index f15e60f32fb7..df523fedc917 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -33,6 +33,7 @@ import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVI
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONTENT_TRIGGER;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_DEADLINE;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_DEVICE_NOT_DOZING;
+import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_STORAGE_NOT_LOW;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_TIMING_DELAY;
@@ -790,6 +791,83 @@ public class JobStatusTest {
assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
}
+ @Test
+ public void testWouldBeReadyWithConstraint_FlexibilityDoesNotAffectReadiness() {
+ final JobStatus job = createJobStatus(
+ new JobInfo.Builder(101, new ComponentName("foo", "bar")).build());
+
+ markImplicitConstraintsSatisfied(job, false);
+ job.setFlexibilityConstraintSatisfied(0, false);
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_IDLE));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_BATTERY_NOT_LOW));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_STORAGE_NOT_LOW));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_TIMING_DELAY));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_FLEXIBLE));
+
+ markImplicitConstraintsSatisfied(job, true);
+ job.setFlexibilityConstraintSatisfied(0, false);
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_IDLE));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BATTERY_NOT_LOW));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_STORAGE_NOT_LOW));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_TIMING_DELAY));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_FLEXIBLE));
+
+ markImplicitConstraintsSatisfied(job, false);
+ job.setFlexibilityConstraintSatisfied(0, true);
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_IDLE));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_BATTERY_NOT_LOW));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_STORAGE_NOT_LOW));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_TIMING_DELAY));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_FLEXIBLE));
+
+ markImplicitConstraintsSatisfied(job, true);
+ job.setFlexibilityConstraintSatisfied(0, true);
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_IDLE));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BATTERY_NOT_LOW));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_STORAGE_NOT_LOW));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_TIMING_DELAY));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_FLEXIBLE));
+ }
+
+ @Test
+ public void testReadinessStatusWithConstraint_FlexibilityConstraint() {
+ final JobStatus job = createJobStatus(
+ new JobInfo.Builder(101, new ComponentName("foo", "bar")).build());
+ job.setConstraintSatisfied(CONSTRAINT_FLEXIBLE, sElapsedRealtimeClock.millis(), false);
+ markImplicitConstraintsSatisfied(job, true);
+ assertTrue(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, true));
+ assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, false));
+
+ markImplicitConstraintsSatisfied(job, false);
+ assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, true));
+ assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, false));
+
+ job.setConstraintSatisfied(CONSTRAINT_FLEXIBLE, sElapsedRealtimeClock.millis(), true);
+ markImplicitConstraintsSatisfied(job, true);
+ assertTrue(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, true));
+ assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, false));
+
+ markImplicitConstraintsSatisfied(job, false);
+ assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, true));
+ assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, false));
+ }
+
private void markImplicitConstraintsSatisfied(JobStatus job, boolean isSatisfied) {
job.setQuotaConstraintSatisfied(sElapsedRealtimeClock.millis(), isSatisfied);
job.setTareWealthConstraintSatisfied(sElapsedRealtimeClock.millis(), isSatisfied);
@@ -797,7 +875,6 @@ public class JobStatusTest {
sElapsedRealtimeClock.millis(), isSatisfied, false);
job.setBackgroundNotRestrictedConstraintSatisfied(
sElapsedRealtimeClock.millis(), isSatisfied, false);
- job.setFlexibilityConstraintSatisfied(sElapsedRealtimeClock.millis(), isSatisfied);
}
private static JobStatus createJobStatus(long earliestRunTimeElapsedMillis,
diff --git a/services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
index 9e986be99e95..da7664b82294 100644
--- a/services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -17,7 +17,10 @@
package com.android.server.tare;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import android.util.ArraySet;
import android.util.SparseLongArray;
@@ -99,7 +102,9 @@ public class AgentTrendCalculatorTest {
@Before
public void setUp() {
- mEconomicPolicy = new MockEconomicPolicy(mock(InternalResourceService.class));
+ final InternalResourceService irs = mock(InternalResourceService.class);
+ when(irs.isVip(anyInt(), anyString())).thenReturn(false);
+ mEconomicPolicy = new MockEconomicPolicy(irs);
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
index 13510adbb960..d90d8b8bfac0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
@@ -68,7 +68,7 @@ public class ScribeTest {
private MockitoSession mMockingSession;
private Scribe mScribeUnderTest;
private File mTestFileDir;
- private final List<PackageInfo> mInstalledPackages = new ArrayList<>();
+ private final List<InstalledPackageInfo> mInstalledPackages = new ArrayList<>();
private final List<Analyst.Report> mReports = new ArrayList<>();
@Mock
@@ -455,6 +455,6 @@ public class ScribeTest {
ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.uid = UserHandle.getUid(userId, Math.abs(pkgName.hashCode()));
pkgInfo.applicationInfo = applicationInfo;
- mInstalledPackages.add(pkgInfo);
+ mInstalledPackages.add(new InstalledPackageInfo(pkgInfo));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java
new file mode 100644
index 000000000000..8cd58abcdf0f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2022 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.biometrics.sensors.face;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
+import android.hardware.face.IFaceService;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class FaceServiceRegistryTest {
+
+ private static final int SENSOR_ID_1 = 1;
+ private static final int SENSOR_ID_2 = 2;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private IBiometricService mBiometricService;
+ @Mock
+ private IFaceService mFaceService;
+ @Mock
+ private ServiceProvider mProvider1;
+ @Mock
+ private ServiceProvider mProvider2;
+ @Captor
+ private ArgumentCaptor<Integer> mIdCaptor;
+ @Captor
+ private ArgumentCaptor<Integer> mStrengthCaptor;
+
+ private FaceSensorPropertiesInternal mProvider1Props;
+ private FaceSensorPropertiesInternal mProvider2Props;
+ private FaceServiceRegistry mRegistry;
+
+ @Before
+ public void setup() {
+ mProvider1Props = new FaceSensorPropertiesInternal(SENSOR_ID_1,
+ STRENGTH_WEAK, 5 /* maxEnrollmentsPerUser */,
+ List.of(), FaceSensorProperties.TYPE_RGB,
+ true /* supportsFace Detection */,
+ true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresHardwareAuthToken */);
+ mProvider2Props = new FaceSensorPropertiesInternal(SENSOR_ID_2,
+ STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
+ List.of(), FaceSensorProperties.TYPE_IR,
+ true /* supportsFace Detection */,
+ true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresHardwareAuthToken */);
+
+ when(mProvider1.getSensorProperties()).thenReturn(List.of(mProvider1Props));
+ when(mProvider1.containsSensor(eq(SENSOR_ID_1))).thenReturn(true);
+ when(mProvider2.getSensorProperties()).thenReturn(List.of(mProvider2Props));
+ when(mProvider2.containsSensor(eq(SENSOR_ID_2))).thenReturn(true);
+ mRegistry = new FaceServiceRegistry(mFaceService, () -> mBiometricService);
+ }
+
+ @Test
+ public void registersAllProviders() throws Exception {
+ mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+ assertThat(mRegistry.getProviders()).containsExactly(mProvider1, mProvider2);
+ assertThat(mRegistry.getAllProperties()).containsExactly(mProvider1Props, mProvider2Props);
+ verify(mBiometricService, times(2)).registerAuthenticator(
+ mIdCaptor.capture(), eq(TYPE_FACE), mStrengthCaptor.capture(), any());
+ assertThat(mIdCaptor.getAllValues()).containsExactly(SENSOR_ID_1, SENSOR_ID_2);
+ assertThat(mStrengthCaptor.getAllValues())
+ .containsExactly(BIOMETRIC_WEAK, BIOMETRIC_STRONG);
+ }
+
+ @Test
+ public void getsProviderById() {
+ mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+ assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1);
+ assertThat(mRegistry.getProviderForSensor(SENSOR_ID_2)).isSameInstanceAs(mProvider2);
+ assertThat(mRegistry.getProviderForSensor(500)).isNull();
+ }
+
+ @Test
+ public void getsSingleProvider() {
+ mRegistry.registerAllInBackground(() -> List.of(mProvider1));
+
+ assertThat(mRegistry.getSingleProvider().second).isSameInstanceAs(mProvider1);
+ assertThat(mRegistry.getProviders()).containsExactly(mProvider1);
+ assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1);
+ }
+
+ @Test
+ public void registersListenerBeforeAllRegistered() {
+ final List<FaceSensorPropertiesInternal> all = new ArrayList<>();
+ mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
+ @Override
+ public void onAllAuthenticatorsRegistered(
+ List<FaceSensorPropertiesInternal> sensors) {
+ all.addAll(sensors);
+ }
+ });
+
+ mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+ assertThat(all).containsExactly(mProvider1Props, mProvider2Props);
+ }
+
+ @Test
+ public void registersListenerAfterAllRegistered() {
+ mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+ final List<FaceSensorPropertiesInternal> all = new ArrayList<>();
+ mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
+ @Override
+ public void onAllAuthenticatorsRegistered(
+ List<FaceSensorPropertiesInternal> sensors) {
+ all.addAll(sensors);
+ }
+ });
+
+ assertThat(all).containsExactly(mProvider1Props, mProvider2Props);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
index 5f88c99b1d1e..0e30782eaece 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.fingerprint;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
+
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -24,38 +26,73 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricStateListener;
+import android.hardware.biometrics.SensorPropertiesInternal;
+import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.BiometricServiceProvider;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.EnrollClient;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
@Presubmit
@SmallTest
public class BiometricStateCallbackTest {
- private BiometricStateCallback mCallback;
+ private static final int USER_ID = 10;
+ private static final int SENSOR_ID = 2;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ private BiometricStateCallback<FakeProvider, SensorPropertiesInternal> mCallback;
@Mock
- BiometricStateListener mBiometricStateListener;
+ private UserManager mUserManager;
+ @Mock
+ private BiometricStateListener mBiometricStateListener;
+ @Mock
+ private FakeProvider mFakeProvider;
+
+ private SensorPropertiesInternal mFakeProviderProps;
@Before
public void setup() {
- MockitoAnnotations.initMocks(this);
-
- mCallback = new BiometricStateCallback();
+ mFakeProviderProps = new SensorPropertiesInternal(SENSOR_ID, STRENGTH_STRONG,
+ 5 /* maxEnrollmentsPerUser */, List.of(),
+ false /* resetLockoutRequiresHardwareAuthToken */,
+ false /* resetLockoutRequiresChallenge */);
+ when(mFakeProvider.getSensorProperties()).thenReturn(List.of(mFakeProviderProps));
+ when(mFakeProvider.getSensorProperties(eq(SENSOR_ID))).thenReturn(mFakeProviderProps);
+ when(mFakeProvider.hasEnrollments(eq(SENSOR_ID), eq(USER_ID))).thenReturn(true);
+ when(mUserManager.getAliveUsers()).thenReturn(
+ List.of(new UserInfo(USER_ID, "name", 0)));
+
+ mCallback = new BiometricStateCallback<>(mUserManager);
mCallback.registerBiometricStateListener(mBiometricStateListener);
}
@Test
+ public void startNotifiesEnrollments() {
+ mCallback.start(List.of(mFakeProvider));
+
+ verify(mBiometricStateListener).onEnrollmentsChanged(eq(USER_ID), eq(SENSOR_ID), eq(true));
+ }
+
+ @Test
public void testNoEnrollmentsToEnrollments_callbackNotified() {
testEnrollmentCallback(true /* changed */, true /* isNowEnrolled */,
true /* expectCallback */, true /* expectedCallbackValue */);
@@ -102,4 +139,6 @@ public class BiometricStateCallbackTest {
verify(mBiometricStateListener, never()).onEnrollmentsChanged(anyInt(), anyInt(),
anyBoolean());
}
+
+ private interface FakeProvider extends BiometricServiceProvider<SensorPropertiesInternal> {}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java
new file mode 100644
index 000000000000..67d94a8f02d8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2022 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.biometrics.sensors.fingerprint;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
+import android.hardware.fingerprint.IFingerprintService;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class FingerprintServiceRegistryTest {
+
+ private static final int SENSOR_ID_1 = 1;
+ private static final int SENSOR_ID_2 = 2;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private IBiometricService mBiometricService;
+ @Mock
+ private IFingerprintService mFingerprintService;
+ @Mock
+ private ServiceProvider mProvider1;
+ @Mock
+ private ServiceProvider mProvider2;
+ @Captor
+ private ArgumentCaptor<Integer> mIdCaptor;
+ @Captor
+ private ArgumentCaptor<Integer> mStrengthCaptor;
+
+ private FingerprintSensorPropertiesInternal mProvider1Props;
+ private FingerprintSensorPropertiesInternal mProvider2Props;
+ private FingerprintServiceRegistry mRegistry;
+
+ @Before
+ public void setup() {
+ mProvider1Props = new FingerprintSensorPropertiesInternal(SENSOR_ID_1,
+ STRENGTH_WEAK, 5 /* maxEnrollmentsPerUser */,
+ List.of(), FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+ false /* resetLockoutRequiresHardwareAuthToken */);
+ mProvider2Props = new FingerprintSensorPropertiesInternal(SENSOR_ID_2,
+ STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
+ List.of(), FingerprintSensorProperties.TYPE_UNKNOWN,
+ false /* resetLockoutRequiresHardwareAuthToken */);
+
+ when(mProvider1.getSensorProperties()).thenReturn(List.of(mProvider1Props));
+ when(mProvider1.containsSensor(eq(SENSOR_ID_1))).thenReturn(true);
+ when(mProvider2.getSensorProperties()).thenReturn(List.of(mProvider2Props));
+ when(mProvider2.containsSensor(eq(SENSOR_ID_2))).thenReturn(true);
+ mRegistry = new FingerprintServiceRegistry(mFingerprintService, () -> mBiometricService);
+ }
+
+ @Test
+ public void registersAllProviders() throws Exception {
+ mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+ assertThat(mRegistry.getProviders()).containsExactly(mProvider1, mProvider2);
+ assertThat(mRegistry.getAllProperties()).containsExactly(mProvider1Props, mProvider2Props);
+ verify(mBiometricService, times(2)).registerAuthenticator(
+ mIdCaptor.capture(), eq(TYPE_FINGERPRINT), mStrengthCaptor.capture(), any());
+ assertThat(mIdCaptor.getAllValues()).containsExactly(SENSOR_ID_1, SENSOR_ID_2);
+ assertThat(mStrengthCaptor.getAllValues())
+ .containsExactly(BIOMETRIC_WEAK, BIOMETRIC_STRONG);
+ }
+
+ @Test
+ public void getsProviderById() {
+ mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+ assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1);
+ assertThat(mRegistry.getProviderForSensor(SENSOR_ID_2)).isSameInstanceAs(mProvider2);
+ assertThat(mRegistry.getProviderForSensor(500)).isNull();
+ }
+
+ @Test
+ public void getsSingleProvider() {
+ mRegistry.registerAllInBackground(() -> List.of(mProvider1));
+
+ assertThat(mRegistry.getSingleProvider().second).isSameInstanceAs(mProvider1);
+ assertThat(mRegistry.getProviders()).containsExactly(mProvider1);
+ assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1);
+ }
+
+ @Test
+ public void registersListenerBeforeAllRegistered() {
+ final List<FingerprintSensorPropertiesInternal> all = new ArrayList<>();
+ mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+ @Override
+ public void onAllAuthenticatorsRegistered(
+ List<FingerprintSensorPropertiesInternal> sensors) {
+ all.addAll(sensors);
+ }
+ });
+
+ mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+ assertThat(all).containsExactly(mProvider1Props, mProvider2Props);
+ }
+
+ @Test
+ public void registersListenerAfterAllRegistered() {
+ mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+ final List<FingerprintSensorPropertiesInternal> all = new ArrayList<>();
+ mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+ @Override
+ public void onAllAuthenticatorsRegistered(
+ List<FingerprintSensorPropertiesInternal> sensors) {
+ all.addAll(sensors);
+ }
+ });
+
+ assertThat(all).containsExactly(mProvider1Props, mProvider2Props);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index ca3677e3b5ff..a4048a27dfb5 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -32,7 +32,8 @@ import android.hardware.biometrics.fingerprint.FingerprintSensorType;
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.SensorLocation;
import android.hardware.biometrics.fingerprint.SensorProps;
-import android.os.RemoteException;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.testing.TestableContext;
@@ -52,6 +53,8 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
@Presubmit
@SmallTest
@@ -94,9 +97,12 @@ public class FingerprintServiceTest {
mContext.getTestablePermissions().setPermission(
USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_GRANTED);
+ }
+ private void initServiceWith(String... aidlInstances) {
mService = new FingerprintService(mContext, mBiometricContext,
() -> mIBiometricService,
+ () -> aidlInstances,
(fqName) -> {
if (fqName.endsWith(NAME_DEFAULT)) return mIFingerprintDefault;
if (fqName.endsWith(NAME_VIRTUAL)) return mIFingerprintVirtual;
@@ -105,29 +111,50 @@ public class FingerprintServiceTest {
}
@Test
- public void registerAuthenticators_defaultOnly() throws RemoteException {
- mService.registerAuthenticatorsForService(List.of(NAME_DEFAULT, NAME_VIRTUAL), List.of());
+ public void registerAuthenticators_defaultOnly() throws Exception {
+ initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
+
+ mService.mServiceWrapper.registerAuthenticators(List.of());
+ waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any());
}
@Test
- public void registerAuthenticators_virtualOnly() throws RemoteException {
+ public void registerAuthenticators_virtualOnly() throws Exception {
+ initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
- mService.registerAuthenticatorsForService(List.of(NAME_DEFAULT, NAME_VIRTUAL), List.of());
+ mService.mServiceWrapper.registerAuthenticators(List.of());
+ waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
}
@Test
- public void registerAuthenticators_virtualAlwaysWhenNoOther() throws RemoteException {
- mService.registerAuthenticatorsForService(List.of(NAME_VIRTUAL), List.of());
+ public void registerAuthenticators_virtualAlwaysWhenNoOther() throws Exception {
+ initServiceWith(NAME_VIRTUAL);
+
+ mService.mServiceWrapper.registerAuthenticators(List.of());
+ waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
}
+ private void waitForRegistration() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mService.mServiceWrapper.addAuthenticatorsRegisteredCallback(
+ new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+ @Override
+ public void onAllAuthenticatorsRegistered(
+ List<FingerprintSensorPropertiesInternal> sensors) {
+ latch.countDown();
+ }
+ });
+ latch.await(5, TimeUnit.SECONDS);
+ }
+
private static SensorProps createProps(int id, byte strength, byte type) {
final SensorProps props = new SensorProps();
props.commonProps = new CommonProps();
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
index e305957c6290..c4435b77628a 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
@@ -39,16 +39,23 @@ public final class BrightnessEventTest {
mBrightnessEvent = new BrightnessEvent(1);
mBrightnessEvent.setReason(
getReason(BrightnessReason.REASON_DOZE, BrightnessReason.MODIFIER_LOW_POWER));
+ mBrightnessEvent.setPhysicalDisplayId("test");
mBrightnessEvent.setLux(100.0f);
+ mBrightnessEvent.setFastAmbientLux(90.0f);
+ mBrightnessEvent.setSlowAmbientLux(85.0f);
mBrightnessEvent.setPreThresholdLux(150.0f);
mBrightnessEvent.setTime(System.currentTimeMillis());
+ mBrightnessEvent.setInitialBrightness(25.0f);
mBrightnessEvent.setBrightness(0.6f);
mBrightnessEvent.setRecommendedBrightness(0.6f);
mBrightnessEvent.setHbmMax(0.62f);
+ mBrightnessEvent.setRbcStrength(-1);
mBrightnessEvent.setThermalMax(0.65f);
+ mBrightnessEvent.setPowerFactor(0.2f);
mBrightnessEvent.setHbmMode(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
mBrightnessEvent.setFlags(0);
mBrightnessEvent.setAdjustmentFlags(0);
+ mBrightnessEvent.setAutomaticBrightnessEnabled(true);
}
@Test
@@ -63,12 +70,15 @@ public final class BrightnessEventTest {
public void testToStringWorksAsExpected() {
String actualString = mBrightnessEvent.toString(false);
String expectedString =
- "BrightnessEvent: disp=1, brt=0.6, rcmdBrt=0.6, preBrt=NaN, lux=100.0, preLux=150"
- + ".0, hbmMax=0.62, hbmMode=off, thrmMax=0.65, flags=, reason=doze [ "
- + "low_pwr ]";
+ "BrightnessEvent: disp=1, physDisp=test, brt=0.6, initBrt=25.0, rcmdBrt=0.6,"
+ + " preBrt=NaN, lux=100.0, fastLux=90.0, slowLux=85.0, preLux=150.0, hbmMax=0.62,"
+ + " hbmMode=off, rbcStrength=-1, thrmMax=0.65, powerFactor=0.2, flags=, reason=doze"
+ + " [ low_pwr ], autoBrightness=true";
assertEquals(actualString, expectedString);
}
+
+
private BrightnessReason getReason(int reason, int modifier) {
BrightnessReason brightnessReason = new BrightnessReason();
brightnessReason.setReason(reason);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 4d18dfe6a62c..5ad4edf71075 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -39,6 +39,7 @@ import android.service.carrier.CarrierService;
import android.telecom.TelecomManager;
import android.telephony.AccessNetworkConstants.AccessNetworkType;
import android.telephony.data.ApnSetting;
+import android.telephony.data.DataCallResponse;
import android.telephony.gba.TlsParams;
import android.telephony.gba.UaSecurityProtocolIdentifier;
import android.telephony.ims.ImsReasonInfo;
@@ -1125,6 +1126,27 @@ public class CarrierConfigManager {
public static final String KEY_DEFAULT_MTU_INT = "default_mtu_int";
/**
+ * The data call retry configuration for different types of APN.
+ * @hide
+ */
+ public static final String KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS =
+ "carrier_data_call_retry_config_strings";
+
+ /**
+ * Delay in milliseconds between trying APN from the pool
+ * @hide
+ */
+ public static final String KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG =
+ "carrier_data_call_apn_delay_default_long";
+
+ /**
+ * Faster delay in milliseconds between trying APN from the pool
+ * @hide
+ */
+ public static final String KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG =
+ "carrier_data_call_apn_delay_faster_long";
+
+ /**
* Delay in milliseconds for retrying APN after disconnect
* @hide
*/
@@ -1132,6 +1154,21 @@ public class CarrierConfigManager {
"carrier_data_call_apn_retry_after_disconnect_long";
/**
+ * The maximum times for telephony to retry data setup on the same APN requested by
+ * network through the data setup response retry timer
+ * {@link DataCallResponse#getRetryDurationMillis()}. This is to prevent that network keeps
+ * asking device to retry data setup forever and causes power consumption issue. For infinite
+ * retring same APN, configure this as 2147483647 (i.e. {@link Integer#MAX_VALUE}).
+ *
+ * Note if network does not suggest any retry timer, frameworks uses the retry configuration
+ * from {@link #KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS}, and the maximum retry times could
+ * be configured there.
+ * @hide
+ */
+ public static final String KEY_CARRIER_DATA_CALL_RETRY_NETWORK_REQUESTED_MAX_COUNT_INT =
+ "carrier_data_call_retry_network_requested_max_count_int";
+
+ /**
* Data call setup permanent failure causes by the carrier
*/
public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS =
@@ -1151,6 +1188,19 @@ public class CarrierConfigManager {
"carrier_metered_roaming_apn_types_strings";
/**
+ * APN types that are not allowed on cellular
+ * @hide
+ */
+ public static final String KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY =
+ "carrier_wwan_disallowed_apn_types_string_array";
+
+ /**
+ * APN types that are not allowed on IWLAN
+ * @hide
+ */
+ public static final String KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY =
+ "carrier_wlan_disallowed_apn_types_string_array";
+ /**
* CDMA carrier ERI (Enhanced Roaming Indicator) file name
* @hide
*/
@@ -8369,6 +8419,7 @@ public class CarrierConfigManager {
* "1800000, maximum_retries=20" means for those capabilities, retry happens in 2.5s, 3s, 5s,
* 10s, 15s, 20s, 40s, 1m, 2m, 4m, 10m, 20m, 30m, 30m, 30m, until reaching 20 retries.
*
+ * // TODO: remove KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS
* @hide
*/
public static final String KEY_TELEPHONY_DATA_SETUP_RETRY_RULES_STRING_ARRAY =
@@ -8770,13 +8821,27 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL, false);
sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false);
sDefaults.putInt(KEY_DEFAULT_MTU_INT, 1500);
+ sDefaults.putStringArray(KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS, new String[]{
+ "default:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
+ + "320000:5000,640000:5000,1280000:5000,1800000:5000",
+ "mms:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
+ + "320000:5000,640000:5000,1280000:5000,1800000:5000",
+ "ims:max_retries=10, 5000, 5000, 5000",
+ "others:max_retries=3, 5000, 5000, 5000"});
+ sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG, 20000);
+ sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG, 3000);
sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_RETRY_AFTER_DISCONNECT_LONG, 3000);
+ sDefaults.putInt(KEY_CARRIER_DATA_CALL_RETRY_NETWORK_REQUESTED_MAX_COUNT_INT, 3);
sDefaults.putString(KEY_CARRIER_ERI_FILE_NAME_STRING, "eri.xml");
sDefaults.putInt(KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT, 7200);
sDefaults.putStringArray(KEY_CARRIER_METERED_APN_TYPES_STRINGS,
new String[]{"default", "mms", "dun", "supl"});
sDefaults.putStringArray(KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
new String[]{"default", "mms", "dun", "supl"});
+ sDefaults.putStringArray(KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
+ new String[]{""});
+ sDefaults.putStringArray(KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
+ new String[]{""});
sDefaults.putIntArray(KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY,
new int[] {TelephonyManager.NETWORK_TYPE_CDMA, TelephonyManager.NETWORK_TYPE_1xRTT,
TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyManager.NETWORK_TYPE_EVDO_A,
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index da1ffcdea812..0ce6b14ce3b0 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2510,6 +2510,9 @@ interface ITelephony {
CellIdentity getLastKnownCellIdentity(int subId, String callingPackage,
String callingFeatureId);
+ /** Check if telephony new data stack is enabled. */
+ boolean isUsingNewDataStack();
+
/**
* @return true if the modem service is set successfully, false otherwise.
*/
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index e138d332bb17..be7fb7315955 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -38,7 +38,10 @@ abstract class BaseTest @JvmOverloads constructor(
) {
init {
testSpec.setIsTablet(
- WindowManagerStateHelper(instrumentation).currentState.wmState.isTablet
+ WindowManagerStateHelper(
+ instrumentation,
+ clearCacheAfterParsing = false
+ ).currentState.wmState.isTablet
)
tapl.setExpectedRotationCheckEnabled(true)
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
index 75900df978df..d08cb5525d5e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
@@ -26,4 +26,9 @@ import com.android.server.wm.flicker.rules.ChangeDisplayOrientationRule
* @param rotation New device rotation
*/
fun Flicker.setRotation(rotation: Int) =
- ChangeDisplayOrientationRule.setRotation(rotation, instrumentation, wmHelper)
+ ChangeDisplayOrientationRule.setRotation(
+ rotation,
+ instrumentation,
+ clearCacheAfterParsing = false,
+ wmHelper = wmHelper
+)
diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
index c4cb33da4a6d..44265511ee50 100644
--- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
@@ -123,16 +123,27 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test {
}
boolean found = false;
+ boolean remountSystem = false;
+ boolean remountVendor = false;
for (String file : files) {
CommandResult result = getDevice().executeShellV2Command("ls " + file);
if (result.getStatus() == CommandStatus.SUCCESS) {
found = true;
- break;
+ if (file.startsWith("/system")) {
+ remountSystem = true;
+ } else if (file.startsWith("/vendor")) {
+ remountVendor = true;
+ }
}
}
if (found) {
- getDevice().remountSystemWritable();
+ if (remountSystem) {
+ getDevice().remountSystemWritable();
+ }
+ if (remountVendor) {
+ getDevice().remountVendorWritable();
+ }
for (String file : files) {
getDevice().executeShellCommand("rm -rf " + file);
}
@@ -150,7 +161,11 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test {
if (!getDevice().isAdbRoot()) {
getDevice().enableAdbRoot();
}
- getDevice().remountSystemWritable();
+ if ("system".equals(partition)) {
+ getDevice().remountSystemWritable();
+ } else if ("vendor".equals(partition)) {
+ getDevice().remountVendorWritable();
+ }
assertTrue(getDevice().pushFile(apex, "/" + partition + "/apex/" + fileName));
}
@@ -158,7 +173,7 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test {
if (!getDevice().isAdbRoot()) {
getDevice().enableAdbRoot();
}
- getDevice().remountSystemWritable();
+ getDevice().remountVendorWritable();
File file = File.createTempFile("test-vendor-apex-allow-list", ".xml");
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
final String fmt =