summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/Activity.java38
-rw-r--r--core/java/android/app/ActivityManagerInternal.java5
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java37
-rw-r--r--core/java/android/appwidget/AppWidgetProvider.java7
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintManager.java1
-rw-r--r--core/java/android/preference/SeekBarVolumizer.java22
-rw-r--r--core/java/android/provider/Settings.java7
-rw-r--r--core/java/android/service/voice/VoiceInteractionSession.java22
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl25
-rw-r--r--core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java10
-rw-r--r--core/res/AndroidManifest.xml1
-rw-r--r--core/res/res/values/bools.xml1
-rw-r--r--core/res/res/values/config.xml14
-rw-r--r--core/res/res/values/symbols.xml6
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java26
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS2
-rw-r--r--libs/WindowManager/Shell/tests/OWNERS1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java1
-rw-r--r--packages/SystemUI/Android.bp2
-rw-r--r--packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml1
-rw-r--r--packages/SystemUI/res/values-sw600dp-h900dp/dimens.xml25
-rw-r--r--packages/SystemUI/res/values-sw600dp-port/dimens.xml2
-rw-r--r--packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml24
-rw-r--r--packages/SystemUI/res/values-sw720dp-port/dimens.xml2
-rw-r--r--packages/SystemUI/res/values/config.xml15
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java21
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt9
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt (renamed from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl)14
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl1
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java258
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java40
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java191
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java239
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java42
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt1
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java11
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java1
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java78
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIApplication.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt147
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java54
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java61
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java53
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt128
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt81
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt189
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt (renamed from services/tests/servicestests/src/com/android/server/appwidget/DummyAppWidget.java)25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSlotPickerRepresentation.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/motiontool/MotionToolStartable.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProvider.kt71
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java81
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java94
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java81
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt86
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java82
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java115
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java88
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java106
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt125
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt127
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt152
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt65
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt41
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt307
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt74
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt41
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt42
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java6
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java40
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java11
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java29
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java25
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java2
-rw-r--r--services/core/java/com/android/server/dreams/DreamManagerService.java2
-rw-r--r--services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java21
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java73
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java6
-rw-r--r--services/core/java/com/android/server/wm/AppTransitionController.java6
-rw-r--r--services/core/java/com/android/server/wm/StartingData.java3
-rw-r--r--services/core/java/com/android/server/wm/Task.java10
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java8
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java14
-rw-r--r--services/tests/servicestests/src/com/android/server/appwidget/TestAppWidgetProvider.java50
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java14
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java27
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java1
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java23
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java20
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java229
158 files changed, 4103 insertions, 1395 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 855366abae4e..81c3e8957cd1 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -87,10 +87,12 @@ import android.os.Parcelable;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.StrictMode;
import android.os.Trace;
import android.os.UserHandle;
+import android.service.voice.VoiceInteractionSession;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
@@ -154,6 +156,7 @@ import android.window.WindowOnBackInvokedDispatcher;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IVoiceInteractionManagerService;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.ToolbarActionBar;
import com.android.internal.app.WindowDecorActionBar;
@@ -1601,6 +1604,25 @@ public class Activity extends ContextThemeWrapper
return callbacks;
}
+ private void notifyVoiceInteractionManagerServiceActivityEvent(
+ @VoiceInteractionSession.VoiceInteractionActivityEventType int type) {
+
+ final IVoiceInteractionManagerService service =
+ IVoiceInteractionManagerService.Stub.asInterface(
+ ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
+ if (service == null) {
+ Log.w(TAG, "notifyVoiceInteractionManagerServiceActivityEvent: Can not get "
+ + "VoiceInteractionManagerService");
+ return;
+ }
+
+ try {
+ service.notifyActivityEventChanged(mToken, type);
+ } catch (RemoteException e) {
+ // Empty
+ }
+ }
+
/**
* Called when the activity is starting. This is where most initialization
* should go: calling {@link #setContentView(int)} to inflate the
@@ -1876,6 +1898,9 @@ public class Activity extends ContextThemeWrapper
mCalled = true;
notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_START);
+
+ notifyVoiceInteractionManagerServiceActivityEvent(
+ VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_START);
}
/**
@@ -2019,6 +2044,12 @@ public class Activity extends ContextThemeWrapper
final Window win = getWindow();
if (win != null) win.makeActive();
if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true);
+
+ // Because the test case "com.android.launcher3.jank.BinderTests#testPressHome" doesn't
+ // allow any binder call in onResume, we call this method in onPostResume.
+ notifyVoiceInteractionManagerServiceActivityEvent(
+ VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_RESUME);
+
mCalled = true;
}
@@ -2394,6 +2425,10 @@ public class Activity extends ContextThemeWrapper
getAutofillClientController().onActivityPaused();
notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_PAUSE);
+
+ notifyVoiceInteractionManagerServiceActivityEvent(
+ VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE);
+
mCalled = true;
}
@@ -2623,6 +2658,9 @@ public class Activity extends ContextThemeWrapper
getAutofillClientController().onActivityStopped(mIntent, mChangingConfigurations);
mEnterAnimationComplete = false;
+
+ notifyVoiceInteractionManagerServiceActivityEvent(
+ VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_STOP);
}
/**
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index f2ea060b1694..c95a7deba61f 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -731,10 +731,9 @@ public abstract class ActivityManagerInternal {
*/
public interface VoiceInteractionManagerProvider {
/**
- * Notifies the service when a high-level activity event has been changed, for example,
- * an activity was resumed or stopped.
+ * Notifies the service when an activity is destroyed.
*/
- void notifyActivityEventChanged();
+ void notifyActivityDestroyed(IBinder activityToken);
}
/**
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index a432b8dec2cb..fd9496947628 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -42,12 +42,15 @@ import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.widget.RemoteViews;
import com.android.internal.appwidget.IAppWidgetService;
+import com.android.internal.os.BackgroundThread;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
/**
* Updates AppWidget state; gets information about installed AppWidget providers and other
@@ -63,6 +66,7 @@ import java.util.List;
@RequiresFeature(PackageManager.FEATURE_APP_WIDGETS)
public class AppWidgetManager {
+
/**
* Activity action to launch from your {@link AppWidgetHost} activity when you want to
* pick an AppWidget to display. The AppWidget picker activity will be launched.
@@ -332,6 +336,17 @@ public class AppWidgetManager {
public static final String ACTION_APPWIDGET_UPDATE = "android.appwidget.action.APPWIDGET_UPDATE";
/**
+ * A combination broadcast of APPWIDGET_ENABLED and APPWIDGET_UPDATE.
+ * Sent during boot time and when the host is binding the widget for the very first time
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @BroadcastBehavior(explicitOnly = true)
+ public static final String ACTION_APPWIDGET_ENABLE_AND_UPDATE = "android.appwidget.action"
+ + ".APPWIDGET_ENABLE_AND_UPDATE";
+
+ /**
* Sent when the custom extras for an AppWidget change.
*
* <p class="note">This is a protected intent that can only be sent
@@ -456,6 +471,8 @@ public class AppWidgetManager {
public static final String ACTION_APPWIDGET_HOST_RESTORED
= "android.appwidget.action.APPWIDGET_HOST_RESTORED";
+ private static final String TAG = "AppWidgetManager";
+
/**
* An intent extra that contains multiple appWidgetIds. These are id values as
* they were provided to the application during a recent restore from backup. It is
@@ -511,6 +528,26 @@ public class AppWidgetManager {
mPackageName = context.getOpPackageName();
mService = service;
mDisplayMetrics = context.getResources().getDisplayMetrics();
+ if (mService == null) {
+ return;
+ }
+ BackgroundThread.getExecutor().execute(() -> {
+ try {
+ mService.notifyProviderInheritance(getInstalledProvidersForPackage(mPackageName,
+ null)
+ .stream().filter(Objects::nonNull)
+ .map(info -> info.provider).filter(p -> {
+ try {
+ Class clazz = Class.forName(p.getClassName());
+ return AppWidgetProvider.class.isAssignableFrom(clazz);
+ } catch (Exception e) {
+ return false;
+ }
+ }).toArray(ComponentName[]::new));
+ } catch (Exception e) {
+ Log.e(TAG, "Nofity service of inheritance info", e);
+ }
+ });
}
/**
diff --git a/core/java/android/appwidget/AppWidgetProvider.java b/core/java/android/appwidget/AppWidgetProvider.java
index a5d2198a6e17..3344ebc083a2 100644
--- a/core/java/android/appwidget/AppWidgetProvider.java
+++ b/core/java/android/appwidget/AppWidgetProvider.java
@@ -58,7 +58,12 @@ public class AppWidgetProvider extends BroadcastReceiver {
// Protect against rogue update broadcasts (not really a security issue,
// just filter bad broacasts out so subclasses are less likely to crash).
String action = intent.getAction();
- if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
+ if (AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE.equals(action)) {
+ this.onReceive(context, new Intent(intent)
+ .setAction(AppWidgetManager.ACTION_APPWIDGET_ENABLED));
+ this.onReceive(context, new Intent(intent)
+ .setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE));
+ } else if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null) {
int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 0fd164de8ffb..085bfca158c1 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -918,7 +918,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
}
-
/**
* Forwards BiometricStateListener to FingerprintService
* @param listener new BiometricStateListener being added
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 16f9a12953f8..36e0dc35cb8e 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -16,7 +16,9 @@
package android.preference;
+import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.app.NotificationManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
@@ -35,6 +37,7 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.preference.VolumePreference.VolumeStore;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Settings.System;
@@ -44,6 +47,7 @@ import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.os.SomeArgs;
import java.util.concurrent.TimeUnit;
@@ -115,7 +119,6 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
private final int mMaxStreamVolume;
private boolean mAffectedByRingerMode;
private boolean mNotificationOrRing;
- private final boolean mNotifAliasRing;
private final Receiver mReceiver = new Receiver();
private Handler mHandler;
@@ -158,6 +161,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
this(context, streamType, defaultUri, callback, true /* playSample */);
}
+ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public SeekBarVolumizer(
Context context,
int streamType,
@@ -180,8 +184,6 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
if (mNotificationOrRing) {
mRingerMode = mAudioManager.getRingerModeInternal();
}
- mNotifAliasRing = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_alias_ring_notif_stream_types);
mZenMode = mNotificationManager.getZenMode();
if (hasAudioProductStrategies()) {
@@ -288,7 +290,9 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
* so that when user attempts to slide the notification seekbar out of vibrate the
* seekbar doesn't wrongly snap back to 0 when the streams aren't aliased
*/
- if (mNotifAliasRing || mStreamType == AudioManager.STREAM_RING
+ if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
+ || mStreamType == AudioManager.STREAM_RING
|| (mStreamType == AudioManager.STREAM_NOTIFICATION && mMuted)) {
mSeekBar.setProgress(0, true);
}
@@ -365,7 +369,9 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
// set the time of stop volume
if ((mStreamType == AudioManager.STREAM_VOICE_CALL
|| mStreamType == AudioManager.STREAM_RING
- || (!mNotifAliasRing && mStreamType == AudioManager.STREAM_NOTIFICATION)
+ || (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
+ && mStreamType == AudioManager.STREAM_NOTIFICATION)
|| mStreamType == AudioManager.STREAM_ALARM)) {
sStopVolumeTime = java.lang.System.currentTimeMillis();
}
@@ -643,8 +649,10 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
}
private void updateVolumeSlider(int streamType, int streamValue) {
- final boolean streamMatch = mNotifAliasRing && mNotificationOrRing
- ? isNotificationOrRing(streamType) : streamType == mStreamType;
+ final boolean streamMatch = !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
+ && mNotificationOrRing ? isNotificationOrRing(streamType) :
+ streamType == mStreamType;
if (mSeekBar != null && streamMatch && streamValue != -1) {
final boolean muted = mAudioManager.isStreamMute(mStreamType)
|| streamValue == 0;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c9fd1295a6e1..a955dbba97e7 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9793,6 +9793,13 @@ public final class Settings {
"fingerprint_side_fps_auth_downtime";
/**
+ * Whether or not a SFPS device is required to be interactive for auth to unlock the device.
+ * @hide
+ */
+ public static final String SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED =
+ "sfps_require_screen_on_to_auth_enabled";
+
+ /**
* Whether or not debugging is enabled.
* @hide
*/
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index df727e9b7d3d..48f732ec9f12 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -19,6 +19,7 @@ package android.service.voice;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -71,6 +72,8 @@ import com.android.internal.util.function.pooled.PooledLambda;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
@@ -143,6 +146,25 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall
*/
public static final int SHOW_SOURCE_AUTOMOTIVE_SYSTEM_UI = 1 << 7;
+ /** @hide */
+ public static final int VOICE_INTERACTION_ACTIVITY_EVENT_START = 1;
+ /** @hide */
+ public static final int VOICE_INTERACTION_ACTIVITY_EVENT_RESUME = 2;
+ /** @hide */
+ public static final int VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE = 3;
+ /** @hide */
+ public static final int VOICE_INTERACTION_ACTIVITY_EVENT_STOP = 4;
+
+ /** @hide */
+ @IntDef(prefix = { "VOICE_INTERACTION_ACTIVITY_EVENT_" }, value = {
+ VOICE_INTERACTION_ACTIVITY_EVENT_START,
+ VOICE_INTERACTION_ACTIVITY_EVENT_RESUME,
+ VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE,
+ VOICE_INTERACTION_ACTIVITY_EVENT_STOP
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface VoiceInteractionActivityEventType{}
+
final Context mContext;
final HandlerCaller mHandlerCaller;
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 681693b1dbad..bd51f12539e8 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -18,6 +18,8 @@ package com.android.internal.app;
import android.content.ComponentName;
import android.content.Intent;
+import android.hardware.soundtrigger.KeyphraseMetadata;
+import android.hardware.soundtrigger.SoundTrigger;
import android.media.AudioFormat;
import android.media.permission.Identity;
import android.os.Bundle;
@@ -25,18 +27,17 @@ import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.SharedMemory;
+import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
+import android.service.voice.IVoiceInteractionService;
+import android.service.voice.IVoiceInteractionSession;
+import android.service.voice.VisibleActivityInfo;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVoiceActionCheckCallback;
-import com.android.internal.app.IVoiceInteractionSessionShowCallback;
-import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.IVoiceInteractionSessionListener;
+import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
-import android.hardware.soundtrigger.KeyphraseMetadata;
-import android.hardware.soundtrigger.SoundTrigger;
-import android.service.voice.IVoiceInteractionService;
-import android.service.voice.IVoiceInteractionSession;
-import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
+import com.android.internal.app.IVoiceInteractor;
interface IVoiceInteractionManagerService {
void showSession(in Bundle sessionArgs, int flags);
@@ -289,4 +290,14 @@ interface IVoiceInteractionManagerService {
* Notifies when the session window is shown or hidden.
*/
void setSessionWindowVisible(in IBinder token, boolean visible);
+
+ /**
+ * Notifies when the Activity lifecycle event changed.
+ *
+ * @param activityToken The token of activity.
+ * @param type The type of lifecycle event of the activity lifecycle.
+ */
+ oneway void notifyActivityEventChanged(
+ in IBinder activityToken,
+ int type);
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 6c689ff2b725..0f64f6da63f7 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -556,6 +556,11 @@ public final class SystemUiDeviceConfigFlags {
"show_stop_button_for_user_allowlisted_apps";
/**
+ * (boolean) Whether to show notification volume control slider separate from ring.
+ */
+ public static final String VOLUME_SEPARATE_NOTIFICATION = "volume_separate_notification";
+
+ /**
* (boolean) Whether the clipboard overlay is enabled.
*/
public static final String CLIPBOARD_OVERLAY_ENABLED = "clipboard_overlay_enabled";
@@ -578,6 +583,11 @@ public final class SystemUiDeviceConfigFlags {
*/
public static final String CLIPBOARD_OVERLAY_SHOW_ACTIONS = "clipboard_overlay_show_actions";
+ /**
+ * (boolean) Whether to combine the broadcasts APPWIDGET_ENABLED and APPWIDGET_UPDATE
+ */
+ public static final String COMBINED_BROADCAST_ENABLED = "combined_broadcast_enabled";
+
private SystemUiDeviceConfigFlags() {
}
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f7467b5a9f86..7787b7c61593 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -142,6 +142,7 @@
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_ENABLED" />
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_HOST_RESTORED" />
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_RESTORED" />
+ <protected-broadcast android:name="android.appwidget.action.APPWIDGET_ENABLE_AND_UPDATE" />
<protected-broadcast android:name="android.os.action.SETTING_RESTORED" />
diff --git a/core/res/res/values/bools.xml b/core/res/res/values/bools.xml
index 988303ea7bc5..4b27bf2849fb 100644
--- a/core/res/res/values/bools.xml
+++ b/core/res/res/values/bools.xml
@@ -31,5 +31,4 @@
lockscreen, setting this to true should come with customized drawables. -->
<bool name="use_lock_pattern_drawable">false</bool>
<bool name="resolver_landscape_phone">true</bool>
- <bool name="system_server_plays_face_haptics">true</bool>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index aa9a9499ed97..07e05c6a830e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2070,10 +2070,6 @@
STREAM_MUSIC as if it's on TV platform. -->
<bool name="config_single_volume">false</bool>
- <!-- Flag indicating whether notification and ringtone volumes
- are controlled together (aliasing is true) or not. -->
- <bool name="config_alias_ring_notif_stream_types">true</bool>
-
<!-- The number of volume steps for the notification stream -->
<integer name="config_audio_notif_vol_steps">7</integer>
@@ -2491,8 +2487,10 @@
assistant activities (ACTIVITY_TYPE_ASSISTANT) -->
<bool name="config_dismissDreamOnActivityStart">false</bool>
- <!-- The prefix of dream component names that are loggable. If empty, logs "other" for all. -->
- <string name="config_loggable_dream_prefix" translatable="false"></string>
+ <!-- The prefixes of dream component names that are loggable.
+ Matched against ComponentName#flattenToString() for dream components.
+ If empty, logs "other" for all. -->
+ <string-array name="config_loggable_dream_prefixes"></string-array>
<!-- ComponentName of a dream to show whenever the system would otherwise have
gone to sleep. When the PowerManager is asked to go to sleep, it will instead
@@ -4967,6 +4965,10 @@
<!-- If face auth sends the user directly to home/last open app, or stays on keyguard -->
<bool name="config_faceAuthDismissesKeyguard">true</bool>
+ <!-- Default value for whether a SFPS device is required to be interactive for fingerprint auth
+ to unlock the device. -->
+ <bool name="config_requireScreenOnToAuthEnabled">false</bool>
+
<!-- The component name for the default profile supervisor, which can be set as a profile owner
even after user setup is complete. The defined component should be used for supervision purposes
only. The component must be part of a system app. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7a7b43a3632a..8e7da4a5eac3 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -278,7 +278,6 @@
<java-symbol type="attr" name="autofillSaveCustomSubtitleMaxHeight"/>
<java-symbol type="bool" name="action_bar_embed_tabs" />
<java-symbol type="bool" name="action_bar_expanded_action_views_exclusive" />
- <java-symbol type="bool" name="config_alias_ring_notif_stream_types" />
<java-symbol type="integer" name="config_audio_notif_vol_default" />
<java-symbol type="integer" name="config_audio_notif_vol_steps" />
<java-symbol type="integer" name="config_audio_ring_vol_default" />
@@ -2245,7 +2244,7 @@
<java-symbol type="integer" name="config_dreamOverlayReconnectTimeoutMs" />
<java-symbol type="integer" name="config_dreamOverlayMaxReconnectAttempts" />
<java-symbol type="integer" name="config_minDreamOverlayDurationMs" />
- <java-symbol type="string" name="config_loggable_dream_prefix" />
+ <java-symbol type="array" name="config_loggable_dream_prefixes" />
<java-symbol type="string" name="config_dozeComponent" />
<java-symbol type="string" name="enable_explore_by_touch_warning_title" />
<java-symbol type="string" name="enable_explore_by_touch_warning_message" />
@@ -2723,6 +2722,7 @@
<java-symbol type="array" name="config_face_acquire_vendor_biometricprompt_ignorelist" />
<java-symbol type="bool" name="config_faceAuthSupportsSelfIllumination" />
<java-symbol type="bool" name="config_faceAuthDismissesKeyguard" />
+ <java-symbol type="bool" name="config_requireScreenOnToAuthEnabled" />
<!-- Face config -->
<java-symbol type="integer" name="config_faceMaxTemplatesPerUser" />
@@ -4861,6 +4861,4 @@
<java-symbol type="id" name="language_picker_header" />
<java-symbol type="dimen" name="status_bar_height_default" />
-
- <java-symbol type="bool" name="system_server_plays_face_haptics" />
</resources>
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 1d513e444050..16760e26b3f1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -1873,6 +1873,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Override
public void onActivityPreCreated(@NonNull Activity activity,
@Nullable Bundle savedInstanceState) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
synchronized (mLock) {
final IBinder activityToken = activity.getActivityToken();
final IBinder initialTaskFragmentToken =
@@ -1904,6 +1909,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Override
public void onActivityPostCreated(@NonNull Activity activity,
@Nullable Bundle savedInstanceState) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
// Calling after Activity#onCreate is complete to allow the app launch something
// first. In case of a configured placeholder activity we want to make sure
// that we don't launch it if an activity itself already requested something to be
@@ -1921,6 +1931,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Override
public void onActivityConfigurationChanged(@NonNull Activity activity) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
synchronized (mLock) {
final TransactionRecord transactionRecord = mTransactionManager
.startNewTransaction();
@@ -1934,6 +1949,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Override
public void onActivityPostDestroyed(@NonNull Activity activity) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
synchronized (mLock) {
SplitController.this.onActivityDestroyed(activity);
}
@@ -1969,7 +1989,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (who instanceof Activity) {
// We will check if the new activity should be split with the activity that launched
// it.
- launchingActivity = (Activity) who;
+ final Activity activity = (Activity) who;
+ // For Activity that is child of another Activity (ActivityGroup), treat the parent
+ // Activity as the launching one because it's window will just be a child of the
+ // parent Activity window.
+ launchingActivity = activity.isChild() ? activity.getParent() : activity;
if (isInPictureInPicture(launchingActivity)) {
// We don't embed activity when it is in PIP.
return super.onStartActivity(who, intent, options);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 79603233ae14..362f1fa096cc 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -227,7 +227,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity(
secondaryActivity);
TaskFragmentContainer containerToAvoid = primaryContainer;
- if (curSecondaryContainer != null
+ if (curSecondaryContainer != null && curSecondaryContainer != primaryContainer
&& (rule.shouldClearTop() || primaryContainer.isAbove(curSecondaryContainer))) {
// Do not reuse the current TaskFragment if the rule is to clear top, or if it is below
// the primary TaskFragment.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS
new file mode 100644
index 000000000000..7237d2bde39f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS
@@ -0,0 +1,2 @@
+# WM shell sub-modules splitscreen owner
+chenghsiuchang@google.com
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index f4efc374ecc2..1c28c3d58ccb 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -6,3 +6,4 @@ pablogamito@google.com
lbill@google.com
madym@google.com
hwwang@google.com
+chenghsiuchang@google.com
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 453a71327b50..8946516af8a2 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -122,6 +122,7 @@ public class SecureSettings {
Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW,
Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME,
+ Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
Settings.Secure.ACTIVE_UNLOCK_ON_WAKE,
Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT,
Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index a39735ffe2c7..cbf79530a6b6 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -177,6 +177,7 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, NON_NEGATIVE_INTEGER_VALIDATOR);
+ VALIDATORS.put(Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SHOW_MEDIA_WHEN_BYPASSING, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_APP_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, BOOLEAN_VALIDATOR);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 2f5b5f456d0b..6d542700fceb 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -125,6 +125,7 @@ android_library {
"jsr330",
"lottie",
"LowLightDreamLib",
+ "motion_tool_lib",
],
manifest: "AndroidManifest.xml",
@@ -229,6 +230,7 @@ android_library {
"jsr330",
"WindowManager-Shell",
"LowLightDreamLib",
+ "motion_tool_lib",
],
libs: [
"android.test.runner",
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
index 006b260434bc..9add32c6ee0a 100644
--- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -18,6 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/dream_overlay_status_bar"
+ android:visibility="invisible"
android:layout_width="match_parent"
android:layout_height="@dimen/dream_overlay_status_bar_height"
android:paddingEnd="@dimen/dream_overlay_status_bar_margin"
diff --git a/packages/SystemUI/res/values-sw600dp-h900dp/dimens.xml b/packages/SystemUI/res/values-sw600dp-h900dp/dimens.xml
new file mode 100644
index 000000000000..aab914f19878
--- /dev/null
+++ b/packages/SystemUI/res/values-sw600dp-h900dp/dimens.xml
@@ -0,0 +1,25 @@
+<?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.
+ -->
+
+<!-- Intended for wide devices that are currently oriented with a lot of available height,
+ such as tablets. 'hxxxdp' is used instead of 'port' in order to avoid this being applied
+ to wide devices that are shorter in height, like foldables. -->
+<resources>
+ <!-- Space between status view and notification shelf -->
+ <dimen name="keyguard_status_view_bottom_margin">35dp</dimen>
+ <dimen name="keyguard_clock_top_margin">40dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values-sw600dp-port/dimens.xml b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
index 347cf2965c77..d9df3373bef1 100644
--- a/packages/SystemUI/res/values-sw600dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
@@ -17,8 +17,6 @@
<resources>
<dimen name="notification_panel_margin_horizontal">48dp</dimen>
<dimen name="status_view_margin_horizontal">62dp</dimen>
- <dimen name="keyguard_clock_top_margin">40dp</dimen>
- <dimen name="keyguard_status_view_bottom_margin">40dp</dimen>
<dimen name="bouncer_user_switcher_y_trans">20dp</dimen>
<!-- qs_tiles_page_horizontal_margin should be margin / 2, otherwise full space between two
diff --git a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
new file mode 100644
index 000000000000..97ead01669a9
--- /dev/null
+++ b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
@@ -0,0 +1,24 @@
+<?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.
+ -->
+<!-- Intended for wide devices that are currently oriented with a lot of available height,
+ such as tablets. 'hxxxdp' is used instead of 'port' in order to avoid this being applied
+ to wide devices that are shorter in height, like foldables. -->
+<resources>
+ <!-- Space between status view and notification shelf -->
+ <dimen name="keyguard_status_view_bottom_margin">70dp</dimen>
+ <dimen name="keyguard_clock_top_margin">80dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
index 3d8da8a6c3e8..17f82b50d7be 100644
--- a/packages/SystemUI/res/values-sw720dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
@@ -21,8 +21,6 @@
for different hardware and product builds. -->
<resources>
<dimen name="status_view_margin_horizontal">124dp</dimen>
- <dimen name="keyguard_clock_top_margin">80dp</dimen>
- <dimen name="keyguard_status_view_bottom_margin">80dp</dimen>
<dimen name="bouncer_user_switcher_y_trans">200dp</dimen>
<dimen name="large_screen_shade_header_left_padding">24dp</dimen>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 93982cb2c5b9..ce9829b318cd 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -743,6 +743,17 @@
<integer name="complicationRestoreMs">1000</integer>
+ <!-- Duration in milliseconds of the dream in un-blur animation. -->
+ <integer name="config_dreamOverlayInBlurDurationMs">249</integer>
+ <!-- Delay in milliseconds of the dream in un-blur animation. -->
+ <integer name="config_dreamOverlayInBlurDelayMs">133</integer>
+ <!-- Duration in milliseconds of the dream in complications fade-in animation. -->
+ <integer name="config_dreamOverlayInComplicationsDurationMs">282</integer>
+ <!-- Delay in milliseconds of the dream in top complications fade-in animation. -->
+ <integer name="config_dreamOverlayInTopComplicationsDelayMs">216</integer>
+ <!-- Delay in milliseconds of the dream in bottom complications fade-in animation. -->
+ <integer name="config_dreamOverlayInBottomComplicationsDelayMs">299</integer>
+
<!-- Icons that don't show in a collapsed non-keyguard statusbar -->
<string-array name="config_collapsed_statusbar_icon_blocklist" translatable="false">
<item>@*android:string/status_bar_volume</item>
@@ -783,4 +794,8 @@
<item>@color/dream_overlay_aqi_very_unhealthy</item>
<item>@color/dream_overlay_aqi_hazardous</item>
</integer-array>
+
+ <!-- Whether the device should display hotspot UI. If true, UI will display only when tethering
+ is available. If false, UI will never show regardless of tethering availability" -->
+ <bool name="config_show_wifi_tethering">true</bool>
</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java b/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
index 8aa3abac831f..a14f97128662 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
@@ -291,8 +291,10 @@ public class KeyButtonRipple extends Drawable {
}
private void endAnimations(String reason, boolean cancel) {
- Trace.beginSection("KeyButtonRipple.endAnim: reason=" + reason + " cancel=" + cancel);
- Trace.endSection();
+ if (Trace.isEnabled()) {
+ Trace.instant(Trace.TRACE_TAG_APP,
+ "KeyButtonRipple.endAnim: reason=" + reason + " cancel=" + cancel);
+ }
mVisible = false;
mTmpArray.addAll(mRunningAnimations);
int size = mTmpArray.size();
@@ -502,20 +504,23 @@ public class KeyButtonRipple extends Drawable {
@Override
public void onAnimationStart(Animator animation) {
- Trace.beginSection("KeyButtonRipple.start." + mName);
- Trace.endSection();
+ if (Trace.isEnabled()) {
+ Trace.instant(Trace.TRACE_TAG_APP, "KeyButtonRipple.start." + mName);
+ }
}
@Override
public void onAnimationCancel(Animator animation) {
- Trace.beginSection("KeyButtonRipple.cancel." + mName);
- Trace.endSection();
+ if (Trace.isEnabled()) {
+ Trace.instant(Trace.TRACE_TAG_APP, "KeyButtonRipple.cancel." + mName);
+ }
}
@Override
public void onAnimationEnd(Animator animation) {
- Trace.beginSection("KeyButtonRipple.end." + mName);
- Trace.endSection();
+ if (Trace.isEnabled()) {
+ Trace.instant(Trace.TRACE_TAG_APP, "KeyButtonRipple.end." + mName);
+ }
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 3961438ff591..ca780c8dd3c9 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -188,13 +188,10 @@ class DefaultClockController(
dozeFraction: Float,
foldFraction: Float,
) : ClockAnimations {
- private var foldState = AnimationState(0f)
- private var dozeState = AnimationState(0f)
+ private val dozeState = AnimationState(dozeFraction)
+ private val foldState = AnimationState(foldFraction)
init {
- dozeState = AnimationState(dozeFraction)
- foldState = AnimationState(foldFraction)
-
if (foldState.isActive) {
clocks.forEach { it.animateFoldAppear(false) }
} else {
@@ -235,7 +232,7 @@ class DefaultClockController(
private class AnimationState(
var fraction: Float,
) {
- var isActive: Boolean = fraction < 0.5f
+ var isActive: Boolean = fraction > 0.5f
fun update(newFraction: Float): Pair<Boolean, Boolean> {
if (newFraction == fraction) {
return Pair(isActive, false)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt
index 1550ab3bed63..2dc7a280e423 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt
@@ -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.
@@ -12,8 +12,16 @@
* 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.systemui.shared.system;
+package com.android.systemui.shared.keyguard.shared.model
-parcelable RemoteTransitionCompat;
+/**
+ * Collection of all supported "slots", placements where keyguard quick affordances can appear on
+ * the lock screen.
+ */
+object KeyguardQuickAffordanceSlots {
+ const val SLOT_ID_BOTTOM_START = "bottom_start"
+ const val SLOT_ID_BOTTOM_END = "bottom_end"
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index b99b72bb4275..1c532fe7a529 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -24,7 +24,6 @@ import android.os.UserHandle;
import android.view.MotionEvent;
import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.RemoteTransitionCompat;
/**
* Temporary callbacks into SystemUI.
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
deleted file mode 100644
index 37e706a9a4c9..000000000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2018 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.systemui.shared.system;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_NONE;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.TransitionOldType;
-import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
-
-import android.annotation.SuppressLint;
-import android.app.IApplicationThread;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.window.IRemoteTransition;
-import android.window.IRemoteTransitionFinishedCallback;
-import android.window.RemoteTransition;
-import android.window.TransitionInfo;
-
-import com.android.wm.shell.util.CounterRotator;
-
-/**
- * @see RemoteAnimationAdapter
- */
-public class RemoteAnimationAdapterCompat {
-
- private final RemoteAnimationAdapter mWrapped;
- private final RemoteTransitionCompat mRemoteTransition;
-
- public RemoteAnimationAdapterCompat(RemoteAnimationRunnerCompat runner, long duration,
- long statusBarTransitionDelay, IApplicationThread appThread) {
- mWrapped = new RemoteAnimationAdapter(wrapRemoteAnimationRunner(runner), duration,
- statusBarTransitionDelay);
- mRemoteTransition = buildRemoteTransition(runner, appThread);
- }
-
- public RemoteAnimationAdapter getWrapped() {
- return mWrapped;
- }
-
- /** Helper to just build a remote transition. Use this if the legacy adapter isn't needed. */
- public static RemoteTransitionCompat buildRemoteTransition(RemoteAnimationRunnerCompat runner,
- IApplicationThread appThread) {
- return new RemoteTransitionCompat(
- new RemoteTransition(wrapRemoteTransition(runner), appThread));
- }
-
- public RemoteTransitionCompat getRemoteTransition() {
- return mRemoteTransition;
- }
-
- /** Wraps a RemoteAnimationRunnerCompat in an IRemoteAnimationRunner. */
- public static IRemoteAnimationRunner.Stub wrapRemoteAnimationRunner(
- final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
- return new IRemoteAnimationRunner.Stub() {
- @Override
- public void onAnimationStart(@TransitionOldType int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- final IRemoteAnimationFinishedCallback finishedCallback) {
- final Runnable animationFinishedCallback = new Runnable() {
- @Override
- public void run() {
- try {
- finishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
- + " finished callback", e);
- }
- }
- };
- remoteAnimationAdapter.onAnimationStart(transit, apps, wallpapers,
- nonApps, animationFinishedCallback);
- }
-
- @Override
- public void onAnimationCancelled(boolean isKeyguardOccluded) {
- remoteAnimationAdapter.onAnimationCancelled();
- }
- };
- }
-
- private static IRemoteTransition.Stub wrapRemoteTransition(
- final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
- return new IRemoteTransition.Stub() {
- final ArrayMap<IBinder, Runnable> mFinishRunnables = new ArrayMap<>();
-
- @Override
- public void startAnimation(IBinder token, TransitionInfo info,
- SurfaceControl.Transaction t,
- IRemoteTransitionFinishedCallback finishCallback) {
- final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
- final RemoteAnimationTarget[] apps =
- RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);
- final RemoteAnimationTarget[] wallpapers =
- RemoteAnimationTargetCompat.wrapNonApps(
- info, true /* wallpapers */, t, leashMap);
- final RemoteAnimationTarget[] nonApps =
- RemoteAnimationTargetCompat.wrapNonApps(
- info, false /* wallpapers */, t, leashMap);
-
- // TODO(b/177438007): Move this set-up logic into launcher's animation impl.
- boolean isReturnToHome = false;
- TransitionInfo.Change launcherTask = null;
- TransitionInfo.Change wallpaper = null;
- int launcherLayer = 0;
- int rotateDelta = 0;
- float displayW = 0;
- float displayH = 0;
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- // skip changes that we didn't wrap
- if (!leashMap.containsKey(change.getLeash())) continue;
- if (change.getTaskInfo() != null
- && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) {
- isReturnToHome = change.getMode() == TRANSIT_OPEN
- || change.getMode() == TRANSIT_TO_FRONT;
- launcherTask = change;
- launcherLayer = info.getChanges().size() - i;
- } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
- wallpaper = change;
- }
- if (change.getParent() == null && change.getEndRotation() >= 0
- && change.getEndRotation() != change.getStartRotation()) {
- rotateDelta = change.getEndRotation() - change.getStartRotation();
- displayW = change.getEndAbsBounds().width();
- displayH = change.getEndAbsBounds().height();
- }
- }
-
- // Prepare for rotation if there is one
- final CounterRotator counterLauncher = new CounterRotator();
- final CounterRotator counterWallpaper = new CounterRotator();
- if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) {
- counterLauncher.setup(t, info.getChange(launcherTask.getParent()).getLeash(),
- rotateDelta, displayW, displayH);
- if (counterLauncher.getSurface() != null) {
- t.setLayer(counterLauncher.getSurface(), launcherLayer);
- }
- }
-
- if (isReturnToHome) {
- if (counterLauncher.getSurface() != null) {
- t.setLayer(counterLauncher.getSurface(), info.getChanges().size() * 3);
- }
- // Need to "boost" the closing things since that's what launcher expects.
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- final SurfaceControl leash = leashMap.get(change.getLeash());
- // skip changes that we didn't wrap
- if (leash == null) continue;
- final int mode = info.getChanges().get(i).getMode();
- // Only deal with independent layers
- if (!TransitionInfo.isIndependent(change, info)) continue;
- if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
- t.setLayer(leash, info.getChanges().size() * 3 - i);
- counterLauncher.addChild(t, leash);
- }
- }
- // Make wallpaper visible immediately since launcher apparently won't do this.
- for (int i = wallpapers.length - 1; i >= 0; --i) {
- t.show(wallpapers[i].leash);
- t.setAlpha(wallpapers[i].leash, 1.f);
- }
- } else {
- if (launcherTask != null) {
- counterLauncher.addChild(t, leashMap.get(launcherTask.getLeash()));
- }
- if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) {
- counterWallpaper.setup(t, info.getChange(wallpaper.getParent()).getLeash(),
- rotateDelta, displayW, displayH);
- if (counterWallpaper.getSurface() != null) {
- t.setLayer(counterWallpaper.getSurface(), -1);
- counterWallpaper.addChild(t, leashMap.get(wallpaper.getLeash()));
- }
- }
- }
- t.apply();
-
- final Runnable animationFinishedCallback = new Runnable() {
- @Override
- @SuppressLint("NewApi")
- public void run() {
- final SurfaceControl.Transaction finishTransaction =
- new SurfaceControl.Transaction();
- counterLauncher.cleanUp(finishTransaction);
- counterWallpaper.cleanUp(finishTransaction);
- // Release surface references now. This is apparently to free GPU memory
- // while doing quick operations (eg. during CTS).
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- info.getChanges().get(i).getLeash().release();
- }
- // Don't release here since launcher might still be using them. Instead
- // let launcher release them (eg. via RemoteAnimationTargets)
- leashMap.clear();
- try {
- finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
- } catch (RemoteException e) {
- Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
- + " finished callback", e);
- }
- }
- };
- synchronized (mFinishRunnables) {
- mFinishRunnables.put(token, animationFinishedCallback);
- }
- // TODO(bc-unlcok): Pass correct transit type.
- remoteAnimationAdapter.onAnimationStart(TRANSIT_OLD_NONE,
- apps, wallpapers, nonApps, () -> {
- synchronized (mFinishRunnables) {
- if (mFinishRunnables.remove(token) == null) return;
- }
- animationFinishedCallback.run();
- });
- }
-
- @Override
- public void mergeAnimation(IBinder token, TransitionInfo info,
- SurfaceControl.Transaction t, IBinder mergeTarget,
- IRemoteTransitionFinishedCallback finishCallback) {
- // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt
- // to legacy cancel.
- final Runnable finishRunnable;
- synchronized (mFinishRunnables) {
- finishRunnable = mFinishRunnables.remove(mergeTarget);
- }
- if (finishRunnable == null) return;
- remoteAnimationAdapter.onAnimationCancelled();
- finishRunnable.run();
- }
- };
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java
deleted file mode 100644
index ab55037159ef..000000000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2018 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.systemui.shared.system;
-
-import android.view.RemoteAnimationDefinition;
-
-/**
- * @see RemoteAnimationDefinition
- */
-public class RemoteAnimationDefinitionCompat {
-
- private final RemoteAnimationDefinition mWrapped = new RemoteAnimationDefinition();
-
- public void addRemoteAnimation(int transition, RemoteAnimationAdapterCompat adapter) {
- mWrapped.addRemoteAnimation(transition, adapter.getWrapped());
- }
-
- public void addRemoteAnimation(int transition, int activityTypeFilter,
- RemoteAnimationAdapterCompat adapter) {
- mWrapped.addRemoteAnimation(transition, activityTypeFilter, adapter.getWrapped());
- }
-
- public RemoteAnimationDefinition getWrapped() {
- return mWrapped;
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 5809c8124946..93c807352521 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -16,12 +16,197 @@
package com.android.systemui.shared.system;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_NONE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
import android.view.WindowManager;
+import android.view.WindowManager.TransitionOldType;
+import android.window.IRemoteTransition;
+import android.window.IRemoteTransitionFinishedCallback;
+import android.window.TransitionInfo;
+
+import com.android.wm.shell.util.CounterRotator;
+
+public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner.Stub {
-public interface RemoteAnimationRunnerCompat {
- void onAnimationStart(@WindowManager.TransitionOldType int transit,
+ public abstract void onAnimationStart(@WindowManager.TransitionOldType int transit,
RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps, Runnable finishedCallback);
- void onAnimationCancelled();
+
+ @Override
+ public final void onAnimationStart(@TransitionOldType int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ final IRemoteAnimationFinishedCallback finishedCallback) {
+
+ onAnimationStart(transit, apps, wallpapers,
+ nonApps, () -> {
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
+ + " finished callback", e);
+ }
+ });
+ }
+
+ public IRemoteTransition toRemoteTransition() {
+ return new IRemoteTransition.Stub() {
+ final ArrayMap<IBinder, Runnable> mFinishRunnables = new ArrayMap<>();
+
+ @Override
+ public void startAnimation(IBinder token, TransitionInfo info,
+ SurfaceControl.Transaction t,
+ IRemoteTransitionFinishedCallback finishCallback) {
+ final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
+ final RemoteAnimationTarget[] apps =
+ RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);
+ final RemoteAnimationTarget[] wallpapers =
+ RemoteAnimationTargetCompat.wrapNonApps(
+ info, true /* wallpapers */, t, leashMap);
+ final RemoteAnimationTarget[] nonApps =
+ RemoteAnimationTargetCompat.wrapNonApps(
+ info, false /* wallpapers */, t, leashMap);
+
+ // TODO(b/177438007): Move this set-up logic into launcher's animation impl.
+ boolean isReturnToHome = false;
+ TransitionInfo.Change launcherTask = null;
+ TransitionInfo.Change wallpaper = null;
+ int launcherLayer = 0;
+ int rotateDelta = 0;
+ float displayW = 0;
+ float displayH = 0;
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ // skip changes that we didn't wrap
+ if (!leashMap.containsKey(change.getLeash())) continue;
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) {
+ isReturnToHome = change.getMode() == TRANSIT_OPEN
+ || change.getMode() == TRANSIT_TO_FRONT;
+ launcherTask = change;
+ launcherLayer = info.getChanges().size() - i;
+ } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
+ wallpaper = change;
+ }
+ if (change.getParent() == null && change.getEndRotation() >= 0
+ && change.getEndRotation() != change.getStartRotation()) {
+ rotateDelta = change.getEndRotation() - change.getStartRotation();
+ displayW = change.getEndAbsBounds().width();
+ displayH = change.getEndAbsBounds().height();
+ }
+ }
+
+ // Prepare for rotation if there is one
+ final CounterRotator counterLauncher = new CounterRotator();
+ final CounterRotator counterWallpaper = new CounterRotator();
+ if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) {
+ counterLauncher.setup(t, info.getChange(launcherTask.getParent()).getLeash(),
+ rotateDelta, displayW, displayH);
+ if (counterLauncher.getSurface() != null) {
+ t.setLayer(counterLauncher.getSurface(), launcherLayer);
+ }
+ }
+
+ if (isReturnToHome) {
+ if (counterLauncher.getSurface() != null) {
+ t.setLayer(counterLauncher.getSurface(), info.getChanges().size() * 3);
+ }
+ // Need to "boost" the closing things since that's what launcher expects.
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ final SurfaceControl leash = leashMap.get(change.getLeash());
+ // skip changes that we didn't wrap
+ if (leash == null) continue;
+ final int mode = info.getChanges().get(i).getMode();
+ // Only deal with independent layers
+ if (!TransitionInfo.isIndependent(change, info)) continue;
+ if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
+ t.setLayer(leash, info.getChanges().size() * 3 - i);
+ counterLauncher.addChild(t, leash);
+ }
+ }
+ // Make wallpaper visible immediately since launcher apparently won't do this.
+ for (int i = wallpapers.length - 1; i >= 0; --i) {
+ t.show(wallpapers[i].leash);
+ t.setAlpha(wallpapers[i].leash, 1.f);
+ }
+ } else {
+ if (launcherTask != null) {
+ counterLauncher.addChild(t, leashMap.get(launcherTask.getLeash()));
+ }
+ if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) {
+ counterWallpaper.setup(t, info.getChange(wallpaper.getParent()).getLeash(),
+ rotateDelta, displayW, displayH);
+ if (counterWallpaper.getSurface() != null) {
+ t.setLayer(counterWallpaper.getSurface(), -1);
+ counterWallpaper.addChild(t, leashMap.get(wallpaper.getLeash()));
+ }
+ }
+ }
+ t.apply();
+
+ final Runnable animationFinishedCallback = () -> {
+ final SurfaceControl.Transaction finishTransaction =
+ new SurfaceControl.Transaction();
+ counterLauncher.cleanUp(finishTransaction);
+ counterWallpaper.cleanUp(finishTransaction);
+ // Release surface references now. This is apparently to free GPU memory
+ // while doing quick operations (eg. during CTS).
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ info.getChanges().get(i).getLeash().release();
+ }
+ // Don't release here since launcher might still be using them. Instead
+ // let launcher release them (eg. via RemoteAnimationTargets)
+ leashMap.clear();
+ try {
+ finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
+ } catch (RemoteException e) {
+ Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
+ + " finished callback", e);
+ }
+ };
+ synchronized (mFinishRunnables) {
+ mFinishRunnables.put(token, animationFinishedCallback);
+ }
+ // TODO(bc-unlcok): Pass correct transit type.
+ onAnimationStart(TRANSIT_OLD_NONE,
+ apps, wallpapers, nonApps, () -> {
+ synchronized (mFinishRunnables) {
+ if (mFinishRunnables.remove(token) == null) return;
+ }
+ animationFinishedCallback.run();
+ });
+ }
+
+ @Override
+ public void mergeAnimation(IBinder token, TransitionInfo info,
+ SurfaceControl.Transaction t, IBinder mergeTarget,
+ IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
+ // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt
+ // to legacy cancel.
+ final Runnable finishRunnable;
+ synchronized (mFinishRunnables) {
+ finishRunnable = mFinishRunnables.remove(mergeTarget);
+ }
+ if (finishRunnable == null) return;
+ onAnimationCancelled(false /* isKeyguardOccluded */);
+ finishRunnable.run();
+ }
+ };
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index d6655a74219c..d4d3d2579b10 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -18,29 +18,22 @@ package com.android.systemui.shared.system;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.window.TransitionFilter.CONTAINER_ORDER_TOP;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.newTarget;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.IApplicationThread;
-import android.content.ComponentName;
import android.graphics.Rect;
import android.os.IBinder;
-import android.os.Parcelable;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
@@ -53,72 +46,23 @@ import android.window.IRemoteTransitionFinishedCallback;
import android.window.PictureInPictureSurfaceTransaction;
import android.window.RemoteTransition;
import android.window.TaskSnapshot;
-import android.window.TransitionFilter;
import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.DataClass;
import com.android.systemui.shared.recents.model.ThumbnailData;
import java.util.ArrayList;
-import java.util.concurrent.Executor;
/**
- * Wrapper to expose RemoteTransition (shell transitions) to Launcher.
- *
- * @see IRemoteTransition
- * @see TransitionFilter
+ * Helper class to build {@link RemoteTransition} objects
*/
-@DataClass
-public class RemoteTransitionCompat implements Parcelable {
+public class RemoteTransitionCompat {
private static final String TAG = "RemoteTransitionCompat";
- @NonNull final RemoteTransition mTransition;
- @Nullable TransitionFilter mFilter = null;
-
- RemoteTransitionCompat(RemoteTransition transition) {
- mTransition = transition;
- }
-
- public RemoteTransitionCompat(@NonNull RemoteTransitionRunner runner,
- @NonNull Executor executor, @Nullable IApplicationThread appThread) {
- IRemoteTransition remote = new IRemoteTransition.Stub() {
- @Override
- public void startAnimation(IBinder transition, TransitionInfo info,
- SurfaceControl.Transaction t,
- IRemoteTransitionFinishedCallback finishedCallback) {
- final Runnable finishAdapter = () -> {
- try {
- finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to call transition finished callback", e);
- }
- };
- executor.execute(() -> runner.startAnimation(transition, info, t, finishAdapter));
- }
-
- @Override
- public void mergeAnimation(IBinder transition, TransitionInfo info,
- SurfaceControl.Transaction t, IBinder mergeTarget,
- IRemoteTransitionFinishedCallback finishedCallback) {
- final Runnable finishAdapter = () -> {
- try {
- finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to call transition finished callback", e);
- }
- };
- executor.execute(() -> runner.mergeAnimation(transition, info, t, mergeTarget,
- finishAdapter));
- }
- };
- mTransition = new RemoteTransition(remote, appThread);
- }
-
/** Constructor specifically for recents animation */
- public RemoteTransitionCompat(RecentsAnimationListener recents,
+ public static RemoteTransition newRemoteTransition(RecentsAnimationListener recents,
RecentsAnimationControllerCompat controller, IApplicationThread appThread) {
IRemoteTransition remote = new IRemoteTransition.Stub() {
final RecentsControllerWrap mRecentsSession = new RecentsControllerWrap();
@@ -193,25 +137,7 @@ public class RemoteTransitionCompat implements Parcelable {
mRecentsSession.commitTasksAppearedIfNeeded(recents);
}
};
- mTransition = new RemoteTransition(remote, appThread);
- }
-
- /** Adds a filter check that restricts this remote transition to home open transitions. */
- public void addHomeOpenCheck(ComponentName homeActivity) {
- if (mFilter == null) {
- mFilter = new TransitionFilter();
- }
- // No need to handle the transition that also dismisses keyguard.
- mFilter.mNotFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
- mFilter.mRequirements =
- new TransitionFilter.Requirement[]{new TransitionFilter.Requirement(),
- new TransitionFilter.Requirement()};
- mFilter.mRequirements[0].mActivityType = ACTIVITY_TYPE_HOME;
- mFilter.mRequirements[0].mTopActivity = homeActivity;
- mFilter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
- mFilter.mRequirements[0].mOrder = CONTAINER_ORDER_TOP;
- mFilter.mRequirements[1].mActivityType = ACTIVITY_TYPE_STANDARD;
- mFilter.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
+ return new RemoteTransition(remote, appThread);
}
/**
@@ -505,161 +431,4 @@ public class RemoteTransitionCompat implements Parcelable {
@Override public void animateNavigationBarToApp(long duration) {
}
}
-
-
-
- // Code below generated by codegen v1.0.23.
- //
- // DO NOT MODIFY!
- // CHECKSTYLE:OFF Generated code
- //
- // To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
- @DataClass.Generated.Member
- /* package-private */ RemoteTransitionCompat(
- @NonNull RemoteTransition transition,
- @Nullable TransitionFilter filter) {
- this.mTransition = transition;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mTransition);
- this.mFilter = filter;
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- @DataClass.Generated.Member
- public @NonNull RemoteTransition getTransition() {
- return mTransition;
- }
-
- @DataClass.Generated.Member
- public @Nullable TransitionFilter getFilter() {
- return mFilter;
- }
-
- @Override
- @DataClass.Generated.Member
- public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
- // You can override field parcelling by defining methods like:
- // void parcelFieldName(Parcel dest, int flags) { ... }
-
- byte flg = 0;
- if (mFilter != null) flg |= 0x2;
- dest.writeByte(flg);
- dest.writeTypedObject(mTransition, flags);
- if (mFilter != null) dest.writeTypedObject(mFilter, flags);
- }
-
- @Override
- @DataClass.Generated.Member
- public int describeContents() { return 0; }
-
- /** @hide */
- @SuppressWarnings({"unchecked", "RedundantCast"})
- @DataClass.Generated.Member
- protected RemoteTransitionCompat(@NonNull android.os.Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- byte flg = in.readByte();
- RemoteTransition transition = (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR);
- TransitionFilter filter = (flg & 0x2) == 0 ? null : (TransitionFilter) in.readTypedObject(TransitionFilter.CREATOR);
-
- this.mTransition = transition;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mTransition);
- this.mFilter = filter;
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- @DataClass.Generated.Member
- public static final @NonNull Parcelable.Creator<RemoteTransitionCompat> CREATOR
- = new Parcelable.Creator<RemoteTransitionCompat>() {
- @Override
- public RemoteTransitionCompat[] newArray(int size) {
- return new RemoteTransitionCompat[size];
- }
-
- @Override
- public RemoteTransitionCompat createFromParcel(@NonNull android.os.Parcel in) {
- return new RemoteTransitionCompat(in);
- }
- };
-
- /**
- * A builder for {@link RemoteTransitionCompat}
- */
- @SuppressWarnings("WeakerAccess")
- @DataClass.Generated.Member
- public static class Builder {
-
- private @NonNull RemoteTransition mTransition;
- private @Nullable TransitionFilter mFilter;
-
- private long mBuilderFieldsSet = 0L;
-
- public Builder(
- @NonNull RemoteTransition transition) {
- mTransition = transition;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mTransition);
- }
-
- @DataClass.Generated.Member
- public @NonNull Builder setTransition(@NonNull RemoteTransition value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x1;
- mTransition = value;
- return this;
- }
-
- @DataClass.Generated.Member
- public @NonNull Builder setFilter(@NonNull TransitionFilter value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x2;
- mFilter = value;
- return this;
- }
-
- /** Builds the instance. This builder should not be touched after calling this! */
- public @NonNull RemoteTransitionCompat build() {
- checkNotUsed();
- mBuilderFieldsSet |= 0x4; // Mark builder used
-
- if ((mBuilderFieldsSet & 0x2) == 0) {
- mFilter = null;
- }
- RemoteTransitionCompat o = new RemoteTransitionCompat(
- mTransition,
- mFilter);
- return o;
- }
-
- private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x4) != 0) {
- throw new IllegalStateException(
- "This Builder should not be reused. Use a new Builder instance instead");
- }
- }
- }
-
- @DataClass.Generated(
- time = 1629321609807L,
- codegenVersion = "1.0.23",
- sourceFile = "frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java",
- inputSignatures = "private static final java.lang.String TAG\nfinal @android.annotation.NonNull android.window.RemoteTransition mTransition\n @android.annotation.Nullable android.window.TransitionFilter mFilter\npublic void addHomeOpenCheck(android.content.ComponentName)\nclass RemoteTransitionCompat extends java.lang.Object implements [android.os.Parcelable]\nprivate com.android.systemui.shared.system.RecentsAnimationControllerCompat mWrapped\nprivate android.window.IRemoteTransitionFinishedCallback mFinishCB\nprivate android.window.WindowContainerToken mPausingTask\nprivate android.window.WindowContainerToken mPipTask\nprivate android.window.TransitionInfo mInfo\nprivate android.view.SurfaceControl mOpeningLeash\nprivate android.util.ArrayMap<android.view.SurfaceControl,android.view.SurfaceControl> mLeashMap\nprivate android.window.PictureInPictureSurfaceTransaction mPipTransaction\nprivate android.os.IBinder mTransition\n void setup(com.android.systemui.shared.system.RecentsAnimationControllerCompat,android.window.TransitionInfo,android.window.IRemoteTransitionFinishedCallback,android.window.WindowContainerToken,android.window.WindowContainerToken,android.util.ArrayMap<android.view.SurfaceControl,android.view.SurfaceControl>,android.os.IBinder)\n @android.annotation.SuppressLint boolean merge(android.window.TransitionInfo,android.view.SurfaceControl.Transaction,com.android.systemui.shared.system.RecentsAnimationListener)\npublic @java.lang.Override com.android.systemui.shared.recents.model.ThumbnailData screenshotTask(int)\npublic @java.lang.Override void setInputConsumerEnabled(boolean)\npublic @java.lang.Override void setAnimationTargetsBehindSystemBars(boolean)\npublic @java.lang.Override void hideCurrentInputMethod()\npublic @java.lang.Override void setFinishTaskTransaction(int,android.window.PictureInPictureSurfaceTransaction,android.view.SurfaceControl)\npublic @java.lang.Override @android.annotation.SuppressLint void finish(boolean,boolean)\npublic @java.lang.Override void setDeferCancelUntilNextTransition(boolean,boolean)\npublic @java.lang.Override void cleanupScreenshot()\npublic @java.lang.Override void setWillFinishToHome(boolean)\npublic @java.lang.Override boolean removeTask(int)\npublic @java.lang.Override void detachNavigationBarFromApp(boolean)\npublic @java.lang.Override void animateNavigationBarToApp(long)\nclass RecentsControllerWrap extends com.android.systemui.shared.system.RecentsAnimationControllerCompat implements []\n@com.android.internal.util.DataClass")
- @Deprecated
- private void __metadata() {}
-
-
- //@formatter:on
- // End of generated code
-
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java
deleted file mode 100644
index accc456c4209..000000000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2021 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.systemui.shared.system;
-
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.window.TransitionInfo;
-
-/** Interface for something that runs a remote transition animation. */
-public interface RemoteTransitionRunner {
- /**
- * Starts a transition animation. Once complete, the implementation should call
- * `finishCallback`.
- */
- void startAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
- Runnable finishCallback);
-
- /**
- * Attempts to merge a transition into the currently-running animation. If merge is not
- * possible/supported, this should do nothing. Otherwise, the implementation should call
- * `finishCallback` immediately to indicate that it merged the transition.
- *
- * @param transition The transition that wants to be merged into the running animation.
- * @param mergeTarget The transition to merge into (that this runner is currently animating).
- */
- default void mergeAnimation(IBinder transition, TransitionInfo info,
- SurfaceControl.Transaction t, IBinder mergeTarget, Runnable finishCallback) { }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index d48d7ffcd824..c9b8712bdde9 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -228,7 +228,7 @@ open class ClockEventController @Inject constructor(
listenForDozing(this)
if (featureFlags.isEnabled(DOZING_MIGRATION_1)) {
listenForDozeAmountTransition(this)
- listenForGoneToAodTransition(this)
+ listenForAnyStateToAodTransition(this)
} else {
listenForDozeAmount(this)
}
@@ -286,10 +286,10 @@ open class ClockEventController @Inject constructor(
* dozing.
*/
@VisibleForTesting
- internal fun listenForGoneToAodTransition(scope: CoroutineScope): Job {
+ internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job {
return scope.launch {
- keyguardTransitionInteractor.goneToAodTransition.filter {
- it.transitionState == TransitionState.STARTED
+ keyguardTransitionInteractor.anyStateToAodTransition.filter {
+ it.transitionState == TransitionState.FINISHED
}.collect {
dozeAmount = 1f
clock?.animations?.doze(dozeAmount)
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index 71470e8870de..a0206f1f1e70 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -35,6 +35,7 @@ data class KeyguardFingerprintListenModel(
val keyguardOccluded: Boolean,
val occludingAppRequestingFp: Boolean,
val primaryUser: Boolean,
+ val shouldListenSfpsState: Boolean,
val shouldListenForFingerprintAssistant: Boolean,
val switchingUser: Boolean,
val udfps: Boolean,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index c756a17976bf..2bb3a5f437f5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -106,8 +106,9 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
static final int USER_TYPE_WORK_PROFILE = 2;
static final int USER_TYPE_SECONDARY_USER = 3;
- @IntDef({MODE_DEFAULT, MODE_ONE_HANDED, MODE_USER_SWITCHER})
+ @IntDef({MODE_UNINITIALIZED, MODE_DEFAULT, MODE_ONE_HANDED, MODE_USER_SWITCHER})
public @interface Mode {}
+ static final int MODE_UNINITIALIZED = -1;
static final int MODE_DEFAULT = 0;
static final int MODE_ONE_HANDED = 1;
static final int MODE_USER_SWITCHER = 2;
@@ -154,7 +155,11 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
private boolean mDisappearAnimRunning;
private SwipeListener mSwipeListener;
private ViewMode mViewMode = new DefaultViewMode();
- private @Mode int mCurrentMode = MODE_DEFAULT;
+ /*
+ * Using MODE_UNINITIALIZED to mean the view mode is set to DefaultViewMode, but init() has not
+ * yet been called on it. This will happen when the ViewController is initialized.
+ */
+ private @Mode int mCurrentMode = MODE_UNINITIALIZED;
private int mWidth = -1;
private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
@@ -347,6 +352,8 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
private String modeToString(@Mode int mode) {
switch (mode) {
+ case MODE_UNINITIALIZED:
+ return "Uninitialized";
case MODE_DEFAULT:
return "Default";
case MODE_ONE_HANDED:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 79a01b9c9717..fbb114c72add 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -318,6 +318,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
@Override
public void onInit() {
mSecurityViewFlipperController.init();
+ configureMode();
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index bad75e8dd862..558869c46373 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -151,6 +151,7 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.Assert;
+import com.android.systemui.util.settings.SecureSettings;
import com.google.android.collect.Lists;
@@ -337,17 +338,20 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
mCallbacks = Lists.newArrayList();
private ContentObserver mDeviceProvisionedObserver;
+ private ContentObserver mSfpsRequireScreenOnToAuthPrefObserver;
private final ContentObserver mTimeFormatChangeObserver;
private boolean mSwitchingUser;
private boolean mDeviceInteractive;
+ private boolean mSfpsRequireScreenOnToAuthPrefEnabled;
private final SubscriptionManager mSubscriptionManager;
private final TelephonyListenerManager mTelephonyListenerManager;
private final TrustManager mTrustManager;
private final UserManager mUserManager;
private final DevicePolicyManager mDevicePolicyManager;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final SecureSettings mSecureSettings;
private final InteractionJankMonitor mInteractionJankMonitor;
private final LatencyTracker mLatencyTracker;
private final StatusBarStateController mStatusBarStateController;
@@ -396,6 +400,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
protected Handler getHandler() {
return mHandler;
}
+
private final Handler mHandler;
private final IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback =
@@ -720,6 +725,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
/**
* Request to listen for face authentication when an app is occluding keyguard.
+ *
* @param request if true and mKeyguardOccluded, request face auth listening, else default
* to normal behavior.
* See {@link KeyguardUpdateMonitor#shouldListenForFace()}
@@ -732,6 +738,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
/**
* Request to listen for fingerprint when an app is occluding keyguard.
+ *
* @param request if true and mKeyguardOccluded, request fingerprint listening, else default
* to normal behavior.
* See {@link KeyguardUpdateMonitor#shouldListenForFingerprint(boolean)}
@@ -1946,6 +1953,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
Context context,
@Main Looper mainLooper,
BroadcastDispatcher broadcastDispatcher,
+ SecureSettings secureSettings,
DumpManager dumpManager,
@Background Executor backgroundExecutor,
@Main Executor mainExecutor,
@@ -1988,6 +1996,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mStatusBarState = mStatusBarStateController.getState();
mLockPatternUtils = lockPatternUtils;
mAuthController = authController;
+ mSecureSettings = secureSettings;
dumpManager.registerDumpable(getClass().getName(), this);
mSensorPrivacyManager = sensorPrivacyManager;
mActiveUnlockConfig = activeUnlockConfiguration;
@@ -2229,9 +2238,35 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
Settings.System.TIME_12_24)));
}
};
+
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.TIME_12_24),
false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
+
+ updateSfpsRequireScreenOnToAuthPref();
+ mSfpsRequireScreenOnToAuthPrefObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateSfpsRequireScreenOnToAuthPref();
+ }
+ };
+
+ mContext.getContentResolver().registerContentObserver(
+ mSecureSettings.getUriFor(
+ Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED),
+ false,
+ mSfpsRequireScreenOnToAuthPrefObserver,
+ getCurrentUser());
+ }
+
+ protected void updateSfpsRequireScreenOnToAuthPref() {
+ final int defaultSfpsRequireScreenOnToAuthValue =
+ mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_requireScreenOnToAuthEnabled) ? 1 : 0;
+ mSfpsRequireScreenOnToAuthPrefEnabled = mSecureSettings.getIntForUser(
+ Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
+ defaultSfpsRequireScreenOnToAuthValue,
+ getCurrentUser()) != 0;
}
private void initializeSimState() {
@@ -2276,6 +2311,22 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
/**
+ * @return true if there's at least one sfps enrollment for the current user.
+ */
+ public boolean isSfpsEnrolled() {
+ return mAuthController.isSfpsEnrolled(getCurrentUser());
+ }
+
+ /**
+ * @return true if sfps HW is supported on this device. Can return true even if the user has
+ * not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered.
+ */
+ public boolean isSfpsSupported() {
+ return mAuthController.getSfpsProps() != null
+ && !mAuthController.getSfpsProps().isEmpty();
+ }
+
+ /**
* @return true if there's at least one face enrolled
*/
public boolean isFaceEnrolled() {
@@ -2598,13 +2649,21 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
!(mFingerprintLockedOut && mBouncerIsOrWillBeShowing && mCredentialAttempted);
final boolean isEncryptedOrLockdownForUser = isEncryptedOrLockdown(user);
+
final boolean shouldListenUdfpsState = !isUdfps
|| (!userCanSkipBouncer
- && !isEncryptedOrLockdownForUser
- && userDoesNotHaveTrust);
+ && !isEncryptedOrLockdownForUser
+ && userDoesNotHaveTrust);
+
+ boolean shouldListenSideFpsState = true;
+ if (isSfpsSupported() && isSfpsEnrolled()) {
+ shouldListenSideFpsState =
+ mSfpsRequireScreenOnToAuthPrefEnabled ? isDeviceInteractive() : true;
+ }
- final boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
- && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut();
+ boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
+ && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut()
+ && shouldListenSideFpsState;
maybeLogListenerModelData(
new KeyguardFingerprintListenModel(
@@ -2626,6 +2685,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mKeyguardOccluded,
mOccludingAppRequestingFp,
mIsPrimaryUser,
+ shouldListenSideFpsState,
shouldListenForFingerprintAssistant,
mSwitchingUser,
isUdfps,
@@ -3727,6 +3787,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mContext.getContentResolver().unregisterContentObserver(mTimeFormatChangeObserver);
}
+ if (mSfpsRequireScreenOnToAuthPrefObserver != null) {
+ mContext.getContentResolver().unregisterContentObserver(
+ mSfpsRequireScreenOnToAuthPrefObserver);
+ }
+
try {
ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver);
} catch (RemoteException e) {
@@ -3799,6 +3864,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
pw.println(" mBouncerIsOrWillBeShowing=" + mBouncerIsOrWillBeShowing);
pw.println(" mStatusBarState=" + StatusBarState.toString(mStatusBarState));
pw.println(" mUdfpsBouncerShowing=" + mUdfpsBouncerShowing);
+ } else if (isSfpsSupported()) {
+ pw.println(" sfpsEnrolled=" + isSfpsEnrolled());
+ pw.println(" shouldListenForSfps=" + shouldListenForFingerprint(false));
+ pw.println(" mSfpsRequireScreenOnToAuthPrefEnabled="
+ + mSfpsRequireScreenOnToAuthPrefEnabled);
}
}
if (mFaceManager != null && mFaceManager.isHardwareDetected()) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index b888d54956cf..83747b4f8636 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -278,7 +278,11 @@ public class SystemUIApplication extends Application implements
}
private static void notifyBootCompleted(CoreStartable coreStartable) {
- Trace.beginSection(coreStartable.getClass().getSimpleName() + ".onBootCompleted()");
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(
+ Trace.TRACE_TAG_APP,
+ coreStartable.getClass().getSimpleName() + ".onBootCompleted()");
+ }
coreStartable.onBootCompleted();
Trace.endSection();
}
@@ -300,14 +304,18 @@ public class SystemUIApplication extends Application implements
private static CoreStartable startAdditionalStartable(String clsName) {
CoreStartable startable;
if (DEBUG) Log.d(TAG, "loading: " + clsName);
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(
+ Trace.TRACE_TAG_APP, clsName + ".newInstance()");
+ }
try {
- Trace.beginSection(clsName + ".newInstance()");
startable = (CoreStartable) Class.forName(clsName).newInstance();
- Trace.endSection();
} catch (ClassNotFoundException
| IllegalAccessException
| InstantiationException ex) {
throw new RuntimeException(ex);
+ } finally {
+ Trace.endSection();
}
return startStartable(startable);
@@ -315,7 +323,10 @@ public class SystemUIApplication extends Application implements
private static CoreStartable startStartable(String clsName, Provider<CoreStartable> provider) {
if (DEBUG) Log.d(TAG, "loading: " + clsName);
- Trace.beginSection("Provider<" + clsName + ">.get()");
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(
+ Trace.TRACE_TAG_APP, "Provider<" + clsName + ">.get()");
+ }
CoreStartable startable = provider.get();
Trace.endSection();
return startStartable(startable);
@@ -323,7 +334,10 @@ public class SystemUIApplication extends Application implements
private static CoreStartable startStartable(CoreStartable startable) {
if (DEBUG) Log.d(TAG, "running: " + startable);
- Trace.beginSection(startable.getClass().getSimpleName() + ".start()");
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(
+ Trace.TRACE_TAG_APP, startable.getClass().getSimpleName() + ".start()");
+ }
startable.start();
Trace.endSection();
@@ -364,15 +378,22 @@ public class SystemUIApplication extends Application implements
public void onConfigurationChanged(Configuration newConfig) {
if (mServicesStarted) {
ConfigurationController configController = mSysUIComponent.getConfigurationController();
- Trace.beginSection(
- configController.getClass().getSimpleName() + ".onConfigurationChanged()");
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(
+ Trace.TRACE_TAG_APP,
+ configController.getClass().getSimpleName() + ".onConfigurationChanged()");
+ }
configController.onConfigurationChanged(newConfig);
Trace.endSection();
int len = mServices.length;
for (int i = 0; i < len; i++) {
if (mServices[i] != null) {
- Trace.beginSection(
- mServices[i].getClass().getSimpleName() + ".onConfigurationChanged()");
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(
+ Trace.TRACE_TAG_APP,
+ mServices[i].getClass().getSimpleName()
+ + ".onConfigurationChanged()");
+ }
mServices[i].onConfigurationChanged(newConfig);
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index c015a21c7db4..ff18eeea45a5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -78,6 +78,7 @@ import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
@@ -150,6 +151,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
@Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps;
@NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
+ @NonNull private final SparseBooleanArray mSfpsEnrolledForUser;
@NonNull private final SensorPrivacyManager mSensorPrivacyManager;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private boolean mAllFingerprintAuthenticatorsRegistered;
@@ -159,6 +161,19 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
private final @Background DelayableExecutor mBackgroundExecutor;
private final DisplayInfo mCachedDisplayInfo = new DisplayInfo();
+
+ private final VibratorHelper mVibratorHelper;
+
+ private void vibrateSuccess(int modality) {
+ mVibratorHelper.vibrateAuthSuccess(
+ getClass().getSimpleName() + ", modality = " + modality + "BP::success");
+ }
+
+ private void vibrateError(int modality) {
+ mVibratorHelper.vibrateAuthError(
+ getClass().getSimpleName() + ", modality = " + modality + "BP::error");
+ }
+
@VisibleForTesting
final TaskStackListener mTaskStackListener = new TaskStackListener() {
@Override
@@ -325,6 +340,16 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
}
}
}
+
+ if (mSidefpsProps == null) {
+ Log.d(TAG, "handleEnrollmentsChanged, mSidefpsProps is null");
+ } else {
+ for (FingerprintSensorPropertiesInternal prop : mSidefpsProps) {
+ if (prop.sensorId == sensorId) {
+ mSfpsEnrolledForUser.put(userId, hasEnrollments);
+ }
+ }
+ }
for (Callback cb : mCallbacks) {
cb.onEnrollmentsChanged();
}
@@ -660,7 +685,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
@NonNull StatusBarStateController statusBarStateController,
@NonNull InteractionJankMonitor jankMonitor,
@Main Handler handler,
- @Background DelayableExecutor bgExecutor) {
+ @Background DelayableExecutor bgExecutor,
+ @NonNull VibratorHelper vibrator) {
mContext = context;
mExecution = execution;
mUserManager = userManager;
@@ -677,6 +703,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
mWindowManager = windowManager;
mInteractionJankMonitor = jankMonitor;
mUdfpsEnrolledForUser = new SparseBooleanArray();
+ mSfpsEnrolledForUser = new SparseBooleanArray();
+ mVibratorHelper = vibrator;
mOrientationListener = new BiometricDisplayListener(
context,
@@ -866,6 +894,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
public void onBiometricAuthenticated(@Modality int modality) {
if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: ");
+ vibrateSuccess(modality);
+
if (mCurrentDialog != null) {
mCurrentDialog.onAuthenticationSucceeded(modality);
} else {
@@ -889,6 +919,11 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
return mUdfpsProps;
}
+ @Nullable
+ public List<FingerprintSensorPropertiesInternal> getSfpsProps() {
+ return mSidefpsProps;
+ }
+
private String getErrorString(@Modality int modality, int error, int vendorCode) {
switch (modality) {
case TYPE_FACE:
@@ -913,6 +948,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
Log.d(TAG, String.format("onBiometricError(%d, %d, %d)", modality, error, vendorCode));
}
+ vibrateError(modality);
+
final boolean isLockout = (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT)
|| (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT);
@@ -1013,6 +1050,17 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
return mUdfpsEnrolledForUser.get(userId);
}
+ /**
+ * Whether the passed userId has enrolled SFPS.
+ */
+ public boolean isSfpsEnrolled(int userId) {
+ if (mSidefpsController == null) {
+ return false;
+ }
+
+ return mSfpsEnrolledForUser.get(userId);
+ }
+
private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
mCurrentDialogArgs = args;
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
index 5850c9537ef0..08c7c0fd0b03 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
@@ -127,7 +127,10 @@ open class UserBroadcastDispatcher(
action,
userId,
{
- Trace.beginSection("registerReceiver act=$action user=$userId")
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(
+ Trace.TRACE_TAG_APP, "registerReceiver act=$action user=$userId")
+ }
context.registerReceiverAsUser(
this,
UserHandle.of(userId),
@@ -141,7 +144,11 @@ open class UserBroadcastDispatcher(
},
{
try {
- Trace.beginSection("unregisterReceiver act=$action user=$userId")
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(
+ Trace.TRACE_TAG_APP,
+ "unregisterReceiver act=$action user=$userId")
+ }
context.unregisterReceiver(this)
Trace.endSection()
logger.logContextReceiverUnregistered(userId, action)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 6db562107357..e46bdb7a8ad8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -47,6 +47,7 @@ import com.android.systemui.keyguard.data.BouncerViewModule;
import com.android.systemui.log.dagger.LogModule;
import com.android.systemui.mediaprojection.appselector.MediaProjectionModule;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.motiontool.MotionToolModule;
import com.android.systemui.navigationbar.NavigationBarComponent;
import com.android.systemui.notetask.NoteTaskModule;
import com.android.systemui.people.PeopleModule;
@@ -134,6 +135,7 @@ import dagger.Provides;
FooterActionsModule.class,
LogModule.class,
MediaProjectionModule.class,
+ MotionToolModule.class,
PeopleHubModule.class,
PeopleModule.class,
PluginModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 60227ee95fc2..937884c79072 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -171,7 +171,10 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
@Override
public void onSensorChanged(SensorEvent event) {
- Trace.beginSection("DozeScreenBrightness.onSensorChanged" + event.values[0]);
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(
+ Trace.TRACE_TAG_APP, "DozeScreenBrightness.onSensorChanged" + event.values[0]);
+ }
try {
if (mRegistered) {
mLastSensorValue = (int) event.values[0];
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
new file mode 100644
index 000000000000..d8dd6a21d4c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -0,0 +1,147 @@
+/*
+ * 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.systemui.dreams
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.view.View
+import androidx.core.animation.doOnEnd
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dreams.complication.ComplicationHostViewController
+import com.android.systemui.dreams.complication.ComplicationLayoutParams
+import com.android.systemui.dreams.dagger.DreamOverlayModule
+import com.android.systemui.statusbar.BlurUtils
+import java.util.function.Consumer
+import javax.inject.Inject
+import javax.inject.Named
+
+/** Controller for dream overlay animations. */
+class DreamOverlayAnimationsController
+@Inject
+constructor(
+ private val mBlurUtils: BlurUtils,
+ private val mComplicationHostViewController: ComplicationHostViewController,
+ private val mStatusBarViewController: DreamOverlayStatusBarViewController,
+ private val mOverlayStateController: DreamOverlayStateController,
+ @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
+ private val mDreamInBlurAnimDuration: Int,
+ @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DELAY) private val mDreamInBlurAnimDelay: Int,
+ @Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
+ private val mDreamInComplicationsAnimDuration: Int,
+ @Named(DreamOverlayModule.DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
+ private val mDreamInTopComplicationsAnimDelay: Int,
+ @Named(DreamOverlayModule.DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
+ private val mDreamInBottomComplicationsAnimDelay: Int
+) {
+
+ var mEntryAnimations: AnimatorSet? = null
+
+ /** Starts the dream content and dream overlay entry animations. */
+ fun startEntryAnimations(view: View) {
+ cancelRunningEntryAnimations()
+
+ mEntryAnimations = AnimatorSet()
+ mEntryAnimations?.apply {
+ playTogether(
+ buildDreamInBlurAnimator(view),
+ buildDreamInTopComplicationsAnimator(),
+ buildDreamInBottomComplicationsAnimator()
+ )
+ doOnEnd { mOverlayStateController.setEntryAnimationsFinished(true) }
+ start()
+ }
+ }
+
+ /** Cancels the dream content and dream overlay animations, if they're currently running. */
+ fun cancelRunningEntryAnimations() {
+ if (mEntryAnimations?.isRunning == true) {
+ mEntryAnimations?.cancel()
+ }
+ mEntryAnimations = null
+ }
+
+ private fun buildDreamInBlurAnimator(view: View): Animator {
+ return ValueAnimator.ofFloat(1f, 0f).apply {
+ duration = mDreamInBlurAnimDuration.toLong()
+ startDelay = mDreamInBlurAnimDelay.toLong()
+ interpolator = Interpolators.LINEAR
+ addUpdateListener { animator: ValueAnimator ->
+ mBlurUtils.applyBlur(
+ view.viewRootImpl,
+ mBlurUtils.blurRadiusOfRatio(animator.animatedValue as Float).toInt(),
+ false /*opaque*/
+ )
+ }
+ }
+ }
+
+ private fun buildDreamInTopComplicationsAnimator(): Animator {
+ return ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = mDreamInComplicationsAnimDuration.toLong()
+ startDelay = mDreamInTopComplicationsAnimDelay.toLong()
+ interpolator = Interpolators.LINEAR
+ addUpdateListener { va: ValueAnimator ->
+ setTopElementsAlpha(va.animatedValue as Float)
+ }
+ }
+ }
+
+ private fun buildDreamInBottomComplicationsAnimator(): Animator {
+ return ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = mDreamInComplicationsAnimDuration.toLong()
+ startDelay = mDreamInBottomComplicationsAnimDelay.toLong()
+ interpolator = Interpolators.LINEAR
+ addUpdateListener { va: ValueAnimator ->
+ setBottomElementsAlpha(va.animatedValue as Float)
+ }
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator) {
+ mComplicationHostViewController
+ .getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM)
+ .forEach(Consumer { v: View -> v.visibility = View.VISIBLE })
+ }
+ }
+ )
+ }
+ }
+
+ /** Sets alpha of top complications and the status bar. */
+ private fun setTopElementsAlpha(alpha: Float) {
+ mComplicationHostViewController
+ .getViewsAtPosition(ComplicationLayoutParams.POSITION_TOP)
+ .forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) })
+ mStatusBarViewController.setAlpha(alpha)
+ }
+
+ /** Sets alpha of bottom complications. */
+ private fun setBottomElementsAlpha(alpha: Float) {
+ mComplicationHostViewController
+ .getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM)
+ .forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) })
+ }
+
+ private fun setAlphaAndEnsureVisible(view: View, alpha: Float) {
+ if (alpha > 0 && view.visibility != View.VISIBLE) {
+ view.visibility = View.VISIBLE
+ }
+
+ view.alpha = alpha
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 733a80dd7f69..d0d01846c3d9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -54,6 +54,8 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
private final DreamOverlayStatusBarViewController mStatusBarViewController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final BlurUtils mBlurUtils;
+ private final DreamOverlayAnimationsController mDreamOverlayAnimationsController;
+ private final DreamOverlayStateController mStateController;
private final ComplicationHostViewController mComplicationHostViewController;
@@ -134,12 +136,16 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
@Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long
burnInProtectionUpdateInterval,
@Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter,
- BouncerCallbackInteractor bouncerCallbackInteractor) {
+ BouncerCallbackInteractor bouncerCallbackInteractor,
+ DreamOverlayAnimationsController animationsController,
+ DreamOverlayStateController stateController) {
super(containerView);
mDreamOverlayContentView = contentView;
mStatusBarViewController = statusBarViewController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mBlurUtils = blurUtils;
+ mDreamOverlayAnimationsController = animationsController;
+ mStateController = stateController;
mComplicationHostViewController = complicationHostViewController;
mDreamOverlayMaxTranslationY = resources.getDimensionPixelSize(
@@ -172,6 +178,11 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
bouncer.addBouncerExpansionCallback(mBouncerExpansionCallback);
}
mBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback);
+
+ // Start dream entry animations. Skip animations for low light clock.
+ if (!mStateController.isLowLightActive()) {
+ mDreamOverlayAnimationsController.startEntryAnimations(mView);
+ }
}
@Override
@@ -182,6 +193,8 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
bouncer.removeBouncerExpansionCallback(mBouncerExpansionCallback);
}
mBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
+
+ mDreamOverlayAnimationsController.cancelRunningEntryAnimations();
}
View getContainerView() {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index d1b73685a3f3..8542412f82f8 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -90,13 +90,15 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
new KeyguardUpdateMonitorCallback() {
@Override
public void onShadeExpandedChanged(boolean expanded) {
- if (mLifecycleRegistry.getCurrentState() != Lifecycle.State.RESUMED
- && mLifecycleRegistry.getCurrentState() != Lifecycle.State.STARTED) {
- return;
- }
-
- mLifecycleRegistry.setCurrentState(
- expanded ? Lifecycle.State.STARTED : Lifecycle.State.RESUMED);
+ mExecutor.execute(() -> {
+ if (getCurrentStateLocked() != Lifecycle.State.RESUMED
+ && getCurrentStateLocked() != Lifecycle.State.STARTED) {
+ return;
+ }
+
+ setCurrentStateLocked(
+ expanded ? Lifecycle.State.STARTED : Lifecycle.State.RESUMED);
+ });
}
};
@@ -146,29 +148,30 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
() -> mExecutor.execute(DreamOverlayService.this::requestExit);
mDreamOverlayComponent = dreamOverlayComponentFactory.create(viewModelStore, host);
mLifecycleRegistry = mDreamOverlayComponent.getLifecycleRegistry();
- setCurrentState(Lifecycle.State.CREATED);
- }
- private void setCurrentState(Lifecycle.State state) {
- mExecutor.execute(() -> mLifecycleRegistry.setCurrentState(state));
+ mExecutor.execute(() -> setCurrentStateLocked(Lifecycle.State.CREATED));
}
@Override
public void onDestroy() {
mKeyguardUpdateMonitor.removeCallback(mKeyguardCallback);
- setCurrentState(Lifecycle.State.DESTROYED);
- resetCurrentDreamOverlay();
+ mExecutor.execute(() -> {
+ setCurrentStateLocked(Lifecycle.State.DESTROYED);
+
+ resetCurrentDreamOverlayLocked();
+
+ mDestroyed = true;
+ });
- mDestroyed = true;
super.onDestroy();
}
@Override
public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
- setCurrentState(Lifecycle.State.STARTED);
-
mExecutor.execute(() -> {
+ setCurrentStateLocked(Lifecycle.State.STARTED);
+
mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
if (mDestroyed) {
@@ -181,7 +184,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
// Reset the current dream overlay before starting a new one. This can happen
// when two dreams overlap (briefly, for a smoother dream transition) and both
// dreams are bound to the dream overlay service.
- resetCurrentDreamOverlay();
+ resetCurrentDreamOverlayLocked();
}
mDreamOverlayContainerViewController =
@@ -191,7 +194,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
mStateController.setShouldShowComplications(shouldShowComplications());
addOverlayWindowLocked(layoutParams);
- setCurrentState(Lifecycle.State.RESUMED);
+ setCurrentStateLocked(Lifecycle.State.RESUMED);
mStateController.setOverlayActive(true);
final ComponentName dreamComponent = getDreamComponent();
mStateController.setLowLightActive(
@@ -202,6 +205,14 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
});
}
+ private Lifecycle.State getCurrentStateLocked() {
+ return mLifecycleRegistry.getCurrentState();
+ }
+
+ private void setCurrentStateLocked(Lifecycle.State state) {
+ mLifecycleRegistry.setCurrentState(state);
+ }
+
/**
* Inserts {@link Window} to host the dream overlay into the dream's parent window. Must be
* called from the main executing thread. The window attributes closely mirror those that are
@@ -231,13 +242,13 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
// Make extra sure the container view has been removed from its old parent (otherwise we
// risk an IllegalStateException in some cases when setting the container view as the
// window's content view and the container view hasn't been properly removed previously).
- removeContainerViewFromParent();
+ removeContainerViewFromParentLocked();
mWindow.setContentView(mDreamOverlayContainerViewController.getContainerView());
mWindowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
}
- private void removeContainerViewFromParent() {
+ private void removeContainerViewFromParentLocked() {
View containerView = mDreamOverlayContainerViewController.getContainerView();
if (containerView == null) {
return;
@@ -250,13 +261,14 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
parentView.removeView(containerView);
}
- private void resetCurrentDreamOverlay() {
+ private void resetCurrentDreamOverlayLocked() {
if (mStarted && mWindow != null) {
mWindowManager.removeView(mWindow.getDecorView());
}
mStateController.setOverlayActive(false);
mStateController.setLowLightActive(false);
+ mStateController.setEntryAnimationsFinished(false);
mDreamOverlayContainerViewController = null;
mDreamOverlayTouchMonitor = null;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index 72feaca59ace..e80d0beabf2b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -51,6 +51,7 @@ public class DreamOverlayStateController implements
public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1;
+ public static final int STATE_DREAM_ENTRY_ANIMATIONS_FINISHED = 1 << 2;
private static final int OP_CLEAR_STATE = 1;
private static final int OP_SET_STATE = 2;
@@ -202,6 +203,14 @@ public class DreamOverlayStateController implements
return containsState(STATE_LOW_LIGHT_ACTIVE);
}
+ /**
+ * Returns whether the dream content and dream overlay entry animations are finished.
+ * @return {@code true} if animations are finished, {@code false} otherwise.
+ */
+ public boolean areEntryAnimationsFinished() {
+ return containsState(STATE_DREAM_ENTRY_ANIMATIONS_FINISHED);
+ }
+
private boolean containsState(int state) {
return (mState & state) != 0;
}
@@ -218,7 +227,7 @@ public class DreamOverlayStateController implements
}
if (existingState != mState) {
- notifyCallbacks(callback -> callback.onStateChanged());
+ notifyCallbacks(Callback::onStateChanged);
}
}
@@ -239,6 +248,15 @@ public class DreamOverlayStateController implements
}
/**
+ * Sets whether dream content and dream overlay entry animations are finished.
+ * @param finished {@code true} if entry animations are finished, {@code false} otherwise.
+ */
+ public void setEntryAnimationsFinished(boolean finished) {
+ modifyState(finished ? OP_SET_STATE : OP_CLEAR_STATE,
+ STATE_DREAM_ENTRY_ANIMATIONS_FINISHED);
+ }
+
+ /**
* Returns the available complication types.
*/
@Complication.ComplicationType
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index bb1c4303041a..d17fbe31c8d2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -16,10 +16,6 @@
package com.android.systemui.dreams;
-import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
-import static android.app.StatusBarManager.WINDOW_STATE_HIDING;
-import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
-
import android.app.AlarmManager;
import android.app.StatusBarManager;
import android.content.res.Resources;
@@ -83,6 +79,9 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
private boolean mIsAttached;
+ // Whether dream entry animations are finished.
+ private boolean mEntryAnimationsFinished = false;
+
private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
.clearCapabilities()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
@@ -109,7 +108,9 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
new DreamOverlayStateController.Callback() {
@Override
public void onStateChanged() {
- updateLowLightState();
+ mEntryAnimationsFinished =
+ mDreamOverlayStateController.areEntryAnimationsFinished();
+ updateVisibility();
}
};
@@ -195,7 +196,6 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
mStatusBarItemsProvider.addCallback(mStatusBarItemsProviderCallback);
mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback);
- updateLowLightState();
mTouchInsetSession.addViewToTracking(mView);
}
@@ -216,6 +216,26 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
mIsAttached = false;
}
+ /**
+ * Sets alpha of the dream overlay status bar.
+ *
+ * No-op if the dream overlay status bar should not be shown.
+ */
+ protected void setAlpha(float alpha) {
+ updateVisibility();
+
+ if (mView.getVisibility() != View.VISIBLE) {
+ return;
+ }
+
+ mView.setAlpha(alpha);
+ }
+
+ private boolean shouldShowStatusBar() {
+ return !mDreamOverlayStateController.isLowLightActive()
+ && !mStatusBarWindowStateController.windowIsShowing();
+ }
+
private void updateWifiUnavailableStatusIcon() {
final NetworkCapabilities capabilities =
mConnectivityManager.getNetworkCapabilities(
@@ -235,13 +255,12 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
hasAlarm ? buildAlarmContentDescription(alarm) : null);
}
- private void updateLowLightState() {
- int visibility = View.VISIBLE;
- if (mDreamOverlayStateController.isLowLightActive()
- || mStatusBarWindowStateController.windowIsShowing()) {
- visibility = View.INVISIBLE;
+ private void updateVisibility() {
+ if (shouldShowStatusBar()) {
+ mView.setVisibility(View.VISIBLE);
+ } else {
+ mView.setVisibility(View.INVISIBLE);
}
- mView.setVisibility(visibility);
}
private String buildAlarmContentDescription(AlarmManager.AlarmClockInfo alarm) {
@@ -298,21 +317,11 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
}
private void onSystemStatusBarStateChanged(@StatusBarManager.WindowVisibleState int state) {
- mMainExecutor.execute(() -> {
- if (!mIsAttached || mDreamOverlayStateController.isLowLightActive()) {
- return;
- }
+ if (!mIsAttached || !mEntryAnimationsFinished) {
+ return;
+ }
- switch (state) {
- case WINDOW_STATE_SHOWING:
- mView.setVisibility(View.INVISIBLE);
- break;
- case WINDOW_STATE_HIDING:
- case WINDOW_STATE_HIDDEN:
- mView.setVisibility(View.VISIBLE);
- break;
- }
- });
+ mMainExecutor.execute(this::updateVisibility);
}
private void onStatusBarItemsChanged(List<StatusBarItem> newItems) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
index fd6cfc0700ad..100ccc35e638 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
@@ -28,6 +28,7 @@ import android.view.View;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.lifecycle.LifecycleOwner;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.util.ViewController;
import java.util.Collection;
@@ -49,20 +50,34 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final ComplicationLayoutEngine mLayoutEngine;
+ private final DreamOverlayStateController mDreamOverlayStateController;
private final LifecycleOwner mLifecycleOwner;
private final ComplicationCollectionViewModel mComplicationCollectionViewModel;
private final HashMap<ComplicationId, Complication.ViewHolder> mComplications = new HashMap<>();
+ // Whether dream entry animations are finished.
+ private boolean mEntryAnimationsFinished = false;
+
@Inject
protected ComplicationHostViewController(
@Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout view,
ComplicationLayoutEngine layoutEngine,
+ DreamOverlayStateController dreamOverlayStateController,
LifecycleOwner lifecycleOwner,
@Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel) {
super(view);
mLayoutEngine = layoutEngine;
mLifecycleOwner = lifecycleOwner;
mComplicationCollectionViewModel = viewModel;
+ mDreamOverlayStateController = dreamOverlayStateController;
+
+ mDreamOverlayStateController.addCallback(new DreamOverlayStateController.Callback() {
+ @Override
+ public void onStateChanged() {
+ mEntryAnimationsFinished =
+ mDreamOverlayStateController.areEntryAnimationsFinished();
+ }
+ });
}
@Override
@@ -123,6 +138,11 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay
final ComplicationId id = complication.getId();
final Complication.ViewHolder viewHolder = complication.getComplication()
.createView(complication);
+ // Complications to be added before dream entry animations are finished are set
+ // to invisible and are animated in.
+ if (!mEntryAnimationsFinished) {
+ viewHolder.getView().setVisibility(View.INVISIBLE);
+ }
mComplications.put(id, viewHolder);
if (viewHolder.getView().getParent() != null) {
Log.e(TAG, "View for complication "
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 4fe1622d73a5..cb012fa42e94 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -47,6 +47,14 @@ public abstract class DreamOverlayModule {
public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL =
"burn_in_protection_update_interval";
public static final String MILLIS_UNTIL_FULL_JITTER = "millis_until_full_jitter";
+ public static final String DREAM_IN_BLUR_ANIMATION_DURATION = "dream_in_blur_anim_duration";
+ public static final String DREAM_IN_BLUR_ANIMATION_DELAY = "dream_in_blur_anim_delay";
+ public static final String DREAM_IN_COMPLICATIONS_ANIMATION_DURATION =
+ "dream_in_complications_anim_duration";
+ public static final String DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY =
+ "dream_in_top_complications_anim_delay";
+ public static final String DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY =
+ "dream_in_bottom_complications_anim_delay";
/** */
@Provides
@@ -114,6 +122,51 @@ public abstract class DreamOverlayModule {
return resources.getInteger(R.integer.config_dreamOverlayMillisUntilFullJitter);
}
+ /**
+ * Duration in milliseconds of the dream in un-blur animation.
+ */
+ @Provides
+ @Named(DREAM_IN_BLUR_ANIMATION_DURATION)
+ static int providesDreamInBlurAnimationDuration(@Main Resources resources) {
+ return resources.getInteger(R.integer.config_dreamOverlayInBlurDurationMs);
+ }
+
+ /**
+ * Delay in milliseconds of the dream in un-blur animation.
+ */
+ @Provides
+ @Named(DREAM_IN_BLUR_ANIMATION_DELAY)
+ static int providesDreamInBlurAnimationDelay(@Main Resources resources) {
+ return resources.getInteger(R.integer.config_dreamOverlayInBlurDelayMs);
+ }
+
+ /**
+ * Duration in milliseconds of the dream in complications fade-in animation.
+ */
+ @Provides
+ @Named(DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
+ static int providesDreamInComplicationsAnimationDuration(@Main Resources resources) {
+ return resources.getInteger(R.integer.config_dreamOverlayInComplicationsDurationMs);
+ }
+
+ /**
+ * Delay in milliseconds of the dream in top complications fade-in animation.
+ */
+ @Provides
+ @Named(DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
+ static int providesDreamInTopComplicationsAnimationDelay(@Main Resources resources) {
+ return resources.getInteger(R.integer.config_dreamOverlayInTopComplicationsDelayMs);
+ }
+
+ /**
+ * Delay in milliseconds of the dream in bottom complications fade-in animation.
+ */
+ @Provides
+ @Named(DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
+ static int providesDreamInBottomComplicationsAnimationDelay(@Main Resources resources) {
+ return resources.getInteger(R.integer.config_dreamOverlayInBottomComplicationsDelayMs);
+ }
+
@Provides
@DreamOverlayComponent.DreamOverlayScope
static LifecycleOwner providesLifecycleOwner(Lazy<LifecycleRegistry> lifecycleRegistryLazy) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 029ffae267d1..974f6058a1a3 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -57,7 +57,7 @@ object Flags {
val INSTANT_VOICE_REPLY = UnreleasedFlag(111, teamfood = true)
// TODO(b/254512425): Tracking Bug
- val NOTIFICATION_MEMORY_MONITOR_ENABLED = UnreleasedFlag(112, teamfood = true)
+ val NOTIFICATION_MEMORY_MONITOR_ENABLED = ReleasedFlag(112)
// TODO(b/254512731): Tracking Bug
@JvmField val NOTIFICATION_DISMISSAL_FADE = UnreleasedFlag(113, teamfood = true)
@@ -125,6 +125,14 @@ object Flags {
// TODO(b/255607168): Tracking Bug
@JvmField val DOZING_MIGRATION_1 = UnreleasedFlag(213)
+ /**
+ * Whether to enable the code powering customizable lock screen quick affordances.
+ *
+ * Note that this flag does not enable individual implementations of quick affordances like the
+ * new camera quick affordance. Look for individual flags for those.
+ */
+ @JvmField val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES = UnreleasedFlag(214, teamfood = false)
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = ReleasedFlag(300)
@@ -171,10 +179,18 @@ object Flags {
@Deprecated("Replaced by mobile and wifi specific flags.")
val NEW_STATUS_BAR_PIPELINE_FRONTEND = UnreleasedFlag(605, teamfood = false)
+ // TODO(b/256614753): Tracking Bug
val NEW_STATUS_BAR_MOBILE_ICONS = UnreleasedFlag(606)
+ // TODO(b/256614210): Tracking Bug
val NEW_STATUS_BAR_WIFI_ICON = UnreleasedFlag(607)
+ // TODO(b/256614751): Tracking Bug
+ val NEW_STATUS_BAR_MOBILE_ICONS_BACKEND = UnreleasedFlag(608)
+
+ // TODO(b/256613548): Tracking Bug
+ val NEW_STATUS_BAR_WIFI_ICON_BACKEND = UnreleasedFlag(609)
+
// 700 - dialer/calls
// TODO(b/254512734): Tracking Bug
val ONGOING_CALL_STATUS_BAR_CHIP = ReleasedFlag(700)
@@ -336,6 +352,9 @@ object Flags {
// 1900 - note task
@JvmField val NOTE_TASKS = SysPropBooleanFlag(1900, "persist.sysui.debug.note_tasks")
+ // 2000 - device controls
+ @Keep val USE_APP_PANELS = UnreleasedFlag(2000, true)
+
// Pay no attention to the reflection behind the curtain.
// ========================== Curtain ==========================
// | |
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
index 546a4093ec7d..450fa1420408 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
@@ -33,6 +33,8 @@ import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.widget.ImageView;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
@@ -61,6 +63,7 @@ public class WorkLockActivity extends Activity {
private UserManager mUserManager;
private PackageManager mPackageManager;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
@Inject
public WorkLockActivity(BroadcastDispatcher broadcastDispatcher, UserManager userManager,
@@ -95,6 +98,10 @@ public class WorkLockActivity extends Activity {
if (badgedIcon != null) {
((ImageView) findViewById(R.id.icon)).setImageDrawable(badgedIcon);
}
+
+ getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ mBackCallback);
}
@VisibleForTesting
@@ -134,11 +141,16 @@ public class WorkLockActivity extends Activity {
@Override
public void onDestroy() {
unregisterBroadcastReceiver();
+ getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mBackCallback);
super.onDestroy();
}
@Override
public void onBackPressed() {
+ onBackInvoked();
+ }
+
+ private void onBackInvoked() {
// Ignore back presses.
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 56a1f1ae936e..f08463bfd5e9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -42,6 +42,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
@@ -71,6 +72,7 @@ import dagger.Provides;
KeyguardUserSwitcherComponent.class},
includes = {
FalsingModule.class,
+ KeyguardDataQuickAffordanceModule.class,
KeyguardQuickAffordanceModule.class,
KeyguardRepositoryModule.class,
StartKeyguardTransitionModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index c600e13cc2dd..d6f521c6dc87 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -53,6 +53,10 @@ constructor(
override val key: String = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+ override val pickerName: String by lazy { context.getString(component.getTileTitleId()) }
+
+ override val pickerIconResourceId: Int by lazy { component.getTileImageId() }
+
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
component.canShowWhileLockedSetting.flatMapLatest { canShowWhileLocked ->
if (canShowWhileLocked) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
new file mode 100644
index 000000000000..bea9363efc81
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.systemui.keyguard.data.quickaffordance
+
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
+
+@Module
+object KeyguardDataQuickAffordanceModule {
+ @Provides
+ @ElementsIntoSet
+ fun quickAffordanceConfigs(
+ home: HomeControlsKeyguardQuickAffordanceConfig,
+ quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
+ qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
+ ): Set<KeyguardQuickAffordanceConfig> {
+ return setOf(
+ home,
+ quickAccessWallet,
+ qrCodeScanner,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
index 0a8090bb11ac..fd40d1dd1a0e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -29,6 +29,10 @@ interface KeyguardQuickAffordanceConfig {
/** Unique identifier for this quick affordance. It must be globally unique. */
val key: String
+ val pickerName: String
+
+ val pickerIconResourceId: Int
+
/**
* The ever-changing state of the affordance.
*
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
new file mode 100644
index 000000000000..9c9354fec695
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.systemui.keyguard.data.quickaffordance
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * Manages and provides access to the current "selections" of keyguard quick affordances, answering
+ * the question "which affordances should the keyguard show?".
+ */
+@SysUISingleton
+class KeyguardQuickAffordanceSelectionManager @Inject constructor() {
+
+ // TODO(b/254858695): implement a persistence layer (database).
+ private val _selections = MutableStateFlow<Map<String, List<String>>>(emptyMap())
+
+ /** IDs of affordances to show, indexed by slot ID, and sorted in descending priority order. */
+ val selections: Flow<Map<String, List<String>>> = _selections.asStateFlow()
+
+ /**
+ * Returns a snapshot of the IDs of affordances to show, indexed by slot ID, and sorted in
+ * descending priority order.
+ */
+ suspend fun getSelections(): Map<String, List<String>> {
+ return _selections.value
+ }
+
+ /**
+ * Updates the IDs of affordances to show at the slot with the given ID. The order of affordance
+ * IDs should be descending priority order.
+ */
+ suspend fun setSelections(
+ slotId: String,
+ affordanceIds: List<String>,
+ ) {
+ // Must make a copy of the map and update it, otherwise, the MutableStateFlow won't emit
+ // when we set its value to the same instance of the original map, even if we change the
+ // map by updating the value of one of its keys.
+ val copy = _selections.value.toMutableMap()
+ copy[slotId] = affordanceIds
+ _selections.value = copy
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index d620b2a1654c..11f72ffa4757 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.quickaffordance
+import android.content.Context
import com.android.systemui.R
import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -24,6 +25,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
@@ -34,11 +36,16 @@ import kotlinx.coroutines.flow.Flow
class QrCodeScannerKeyguardQuickAffordanceConfig
@Inject
constructor(
+ @Application context: Context,
private val controller: QRCodeScannerController,
) : KeyguardQuickAffordanceConfig {
override val key: String = BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
+ override val pickerName = context.getString(R.string.qr_code_scanner_title)
+
+ override val pickerIconResourceId = R.drawable.ic_qr_code_scanner
+
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
conflatedCallbackFlow {
val callback =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index be57a323b5d1..303e6a1a95cc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.quickaffordance
+import android.content.Context
import android.graphics.drawable.Drawable
import android.service.quickaccesswallet.GetWalletCardsError
import android.service.quickaccesswallet.GetWalletCardsResponse
@@ -29,6 +30,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.wallet.controller.QuickAccessWalletController
import javax.inject.Inject
@@ -40,12 +42,17 @@ import kotlinx.coroutines.flow.Flow
class QuickAccessWalletKeyguardQuickAffordanceConfig
@Inject
constructor(
+ @Application context: Context,
private val walletController: QuickAccessWalletController,
private val activityStarter: ActivityStarter,
) : KeyguardQuickAffordanceConfig {
override val key: String = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ override val pickerName = context.getString(R.string.accessibility_wallet_button)
+
+ override val pickerIconResourceId = R.drawable.ic_wallet_lockscreen
+
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
conflatedCallbackFlow {
val callback =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
new file mode 100644
index 000000000000..95f614fbf7b1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.systemui.keyguard.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
+import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Abstracts access to application state related to keyguard quick affordances. */
+@SysUISingleton
+class KeyguardQuickAffordanceRepository
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val selectionManager: KeyguardQuickAffordanceSelectionManager,
+ private val configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>,
+) {
+ /**
+ * List of [KeyguardQuickAffordanceConfig] instances of the affordances at the slot with the
+ * given ID. The configs are sorted in descending priority order.
+ */
+ val selections: StateFlow<Map<String, List<KeyguardQuickAffordanceConfig>>> =
+ selectionManager.selections
+ .map { selectionsBySlotId ->
+ selectionsBySlotId.mapValues { (_, selections) ->
+ configs.filter { selections.contains(it.key) }
+ }
+ }
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.Eagerly,
+ initialValue = emptyMap(),
+ )
+
+ /**
+ * Returns a snapshot of the [KeyguardQuickAffordanceConfig] instances of the affordances at the
+ * slot with the given ID. The configs are sorted in descending priority order.
+ */
+ suspend fun getSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
+ val selections = selectionManager.getSelections().getOrDefault(slotId, emptyList())
+ return configs.filter { selections.contains(it.key) }
+ }
+
+ /**
+ * Returns a snapshot of the IDs of the selected affordances, indexed by slot ID. The configs
+ * are sorted in descending priority order.
+ */
+ suspend fun getSelections(): Map<String, List<String>> {
+ return selectionManager.getSelections()
+ }
+
+ /**
+ * Updates the IDs of affordances to show at the slot with the given ID. The order of affordance
+ * IDs should be descending priority order.
+ */
+ fun setSelections(
+ slotId: String,
+ affordanceIds: List<String>,
+ ) {
+ scope.launch(backgroundDispatcher) {
+ selectionManager.setSelections(
+ slotId = slotId,
+ affordanceIds = affordanceIds,
+ )
+ }
+ }
+
+ /**
+ * Returns the list of representation objects for all known affordances, regardless of what is
+ * selected. This is useful for building experiences like the picker/selector or user settings
+ * so the user can see everything that can be selected in a menu.
+ */
+ fun getAffordancePickerRepresentations(): List<KeyguardQuickAffordancePickerRepresentation> {
+ return configs.map { config ->
+ KeyguardQuickAffordancePickerRepresentation(
+ id = config.key,
+ name = config.pickerName,
+ iconResourceId = config.pickerIconResourceId,
+ )
+ }
+ }
+
+ /**
+ * Returns the list of representation objects for all available slots on the keyguard. This is
+ * useful for building experiences like the picker/selector or user settings so the user can see
+ * each slot and select which affordance(s) is/are installed in each slot on the keyguard.
+ */
+ fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
+ // TODO(b/256195304): source these from a config XML file.
+ return listOf(
+ KeyguardSlotPickerRepresentation(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ ),
+ KeyguardSlotPickerRepresentation(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ ),
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 6baaf5f10024..c867c6e9229b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -23,9 +23,12 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.doze.DozeHost
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.phone.BiometricUnlockController
+import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
@@ -65,6 +68,9 @@ interface KeyguardRepository {
*/
val isKeyguardShowing: Flow<Boolean>
+ /** Observable for whether the bouncer is showing. */
+ val isBouncerShowing: Flow<Boolean>
+
/**
* Observable for whether we are in doze state.
*
@@ -95,6 +101,9 @@ interface KeyguardRepository {
/** Observable for device wake/sleep state */
val wakefulnessState: Flow<WakefulnessModel>
+ /** Observable for biometric unlock modes */
+ val biometricUnlockState: Flow<BiometricUnlockModel>
+
/**
* Returns `true` if the keyguard is showing; `false` otherwise.
*
@@ -125,6 +134,7 @@ constructor(
private val keyguardStateController: KeyguardStateController,
dozeHost: DozeHost,
wakefulnessLifecycle: WakefulnessLifecycle,
+ biometricUnlockController: BiometricUnlockController,
) : KeyguardRepository {
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
override val animateBottomAreaDozingTransitions =
@@ -159,6 +169,29 @@ constructor(
awaitClose { keyguardStateController.removeCallback(callback) }
}
+ override val isBouncerShowing: Flow<Boolean> = conflatedCallbackFlow {
+ val callback =
+ object : KeyguardStateController.Callback {
+ override fun onBouncerShowingChanged() {
+ trySendWithFailureLogging(
+ keyguardStateController.isBouncerShowing,
+ TAG,
+ "updated isBouncerShowing"
+ )
+ }
+ }
+
+ keyguardStateController.addCallback(callback)
+ // Adding the callback does not send an initial update.
+ trySendWithFailureLogging(
+ keyguardStateController.isBouncerShowing,
+ TAG,
+ "initial isBouncerShowing"
+ )
+
+ awaitClose { keyguardStateController.removeCallback(callback) }
+ }
+
override val isDozing: Flow<Boolean> =
conflatedCallbackFlow {
val callback =
@@ -248,6 +281,24 @@ constructor(
awaitClose { wakefulnessLifecycle.removeObserver(callback) }
}
+ override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow {
+ val callback =
+ object : BiometricUnlockController.BiometricModeListener {
+ override fun onModeChanged(@WakeAndUnlockMode mode: Int) {
+ trySendWithFailureLogging(biometricModeIntToObject(mode), TAG, "biometric mode")
+ }
+ }
+
+ biometricUnlockController.addBiometricModeListener(callback)
+ trySendWithFailureLogging(
+ biometricModeIntToObject(biometricUnlockController.getMode()),
+ TAG,
+ "initial biometric mode"
+ )
+
+ awaitClose { biometricUnlockController.removeBiometricModeListener(callback) }
+ }
+
override fun setAnimateDozingTransitions(animate: Boolean) {
_animateBottomAreaDozingTransitions.value = animate
}
@@ -279,6 +330,20 @@ constructor(
}
}
+ private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockModel {
+ return when (value) {
+ 0 -> BiometricUnlockModel.NONE
+ 1 -> BiometricUnlockModel.WAKE_AND_UNLOCK
+ 2 -> BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+ 3 -> BiometricUnlockModel.SHOW_BOUNCER
+ 4 -> BiometricUnlockModel.ONLY_WAKE
+ 5 -> BiometricUnlockModel.UNLOCK_COLLAPSING
+ 6 -> BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+ 7 -> BiometricUnlockModel.DISMISS_BOUNCER
+ else -> throw IllegalArgumentException("Invalid BiometricUnlockModel value: $value")
+ }
+ }
+
companion object {
private const val TAG = "KeyguardRepositoryImpl"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
new file mode 100644
index 000000000000..7e01db34319f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.systemui.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class AodToGoneTransitionInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val keyguardTransitionRepository: KeyguardTransitionRepository,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) : TransitionInteractor("AOD->GONE") {
+
+ private val wakeAndUnlockModes =
+ setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING)
+
+ override fun start() {
+ scope.launch {
+ keyguardInteractor.biometricUnlockState
+ .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+ .collect { pair ->
+ val (biometricUnlockState, keyguardState) = pair
+ if (
+ keyguardState == KeyguardState.AOD &&
+ wakeAndUnlockModes.contains(biometricUnlockState)
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.AOD,
+ KeyguardState.GONE,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun getAnimator(): ValueAnimator {
+ return ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(TRANSITION_DURATION_MS)
+ }
+ }
+
+ companion object {
+ private const val TRANSITION_DURATION_MS = 500L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 03c6a789326e..614ff8d930d8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -19,6 +19,8 @@ package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -39,10 +41,19 @@ constructor(
val dozeAmount: Flow<Float> = repository.dozeAmount
/** Whether the system is in doze mode. */
val isDozing: Flow<Boolean> = repository.isDozing
- /** Whether the keyguard is showing to not. */
+ /** Whether the keyguard is showing or not. */
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+ /** Whether the bouncer is showing or not. */
+ val isBouncerShowing: Flow<Boolean> = repository.isBouncerShowing
/** The device wake/sleep state */
val wakefulnessState: Flow<WakefulnessModel> = repository.wakefulnessState
+ /** Observable for the [StatusBarState] */
+ val statusBarState: Flow<StatusBarState> = repository.statusBarState
+ /**
+ * Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear,
+ * side, under display) is used to unlock the device.
+ */
+ val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState
fun isKeyguardShowing(): Boolean {
return repository.isKeyguardShowing()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 13d97aaf28da..92caa89bb0e8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -18,19 +18,30 @@
package com.android.systemui.keyguard.domain.interactor
import android.content.Intent
+import android.util.Log
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
+import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@SysUISingleton
@@ -43,7 +54,12 @@ constructor(
private val keyguardStateController: KeyguardStateController,
private val userTracker: UserTracker,
private val activityStarter: ActivityStarter,
+ private val featureFlags: FeatureFlags,
+ private val repository: Lazy<KeyguardQuickAffordanceRepository>,
) {
+ private val isUsingRepository: Boolean
+ get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)
+
/** Returns an observable for the quick affordance at the given position. */
fun quickAffordance(
position: KeyguardQuickAffordancePosition
@@ -72,7 +88,19 @@ constructor(
configKey: String,
expandable: Expandable?,
) {
- @Suppress("UNCHECKED_CAST") val config = registry.get(configKey)
+ @Suppress("UNCHECKED_CAST")
+ val config =
+ if (isUsingRepository) {
+ val (slotId, decodedConfigKey) = configKey.decode()
+ repository.get().selections.value[slotId]?.find { it.key == decodedConfigKey }
+ } else {
+ registry.get(configKey)
+ }
+ if (config == null) {
+ Log.e(TAG, "Affordance config with key of \"$configKey\" not found!")
+ return
+ }
+
when (val result = config.onTriggered(expandable)) {
is KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity ->
launchQuickAffordance(
@@ -84,28 +112,138 @@ constructor(
}
}
+ /**
+ * Selects an affordance with the given ID on the slot with the given ID.
+ *
+ * @return `true` if the affordance was selected successfully; `false` otherwise.
+ */
+ suspend fun select(slotId: String, affordanceId: String): Boolean {
+ check(isUsingRepository)
+
+ val slots = repository.get().getSlotPickerRepresentations()
+ val slot = slots.find { it.id == slotId } ?: return false
+ val selections =
+ repository.get().getSelections().getOrDefault(slotId, emptyList()).toMutableList()
+ val alreadySelected = selections.remove(affordanceId)
+ if (!alreadySelected) {
+ while (selections.size > 0 && selections.size >= slot.maxSelectedAffordances) {
+ selections.removeAt(0)
+ }
+ }
+
+ selections.add(affordanceId)
+
+ repository
+ .get()
+ .setSelections(
+ slotId = slotId,
+ affordanceIds = selections,
+ )
+
+ return true
+ }
+
+ /**
+ * Unselects one or all affordances from the slot with the given ID.
+ *
+ * @param slotId The ID of the slot.
+ * @param affordanceId The ID of the affordance to remove; if `null`, removes all affordances
+ * from the slot.
+ * @return `true` if the affordance was successfully removed; `false` otherwise (for example, if
+ * the affordance was not on the slot to begin with).
+ */
+ suspend fun unselect(slotId: String, affordanceId: String?): Boolean {
+ check(isUsingRepository)
+
+ val slots = repository.get().getSlotPickerRepresentations()
+ if (slots.find { it.id == slotId } == null) {
+ return false
+ }
+
+ if (affordanceId.isNullOrEmpty()) {
+ return if (
+ repository.get().getSelections().getOrDefault(slotId, emptyList()).isEmpty()
+ ) {
+ false
+ } else {
+ repository.get().setSelections(slotId = slotId, affordanceIds = emptyList())
+ true
+ }
+ }
+
+ val selections =
+ repository.get().getSelections().getOrDefault(slotId, emptyList()).toMutableList()
+ return if (selections.remove(affordanceId)) {
+ repository
+ .get()
+ .setSelections(
+ slotId = slotId,
+ affordanceIds = selections,
+ )
+ true
+ } else {
+ false
+ }
+ }
+
+ /** Returns affordance IDs indexed by slot ID, for all known slots. */
+ suspend fun getSelections(): Map<String, List<String>> {
+ check(isUsingRepository)
+
+ val selections = repository.get().getSelections()
+ return repository.get().getSlotPickerRepresentations().associate { slotRepresentation ->
+ slotRepresentation.id to (selections[slotRepresentation.id] ?: emptyList())
+ }
+ }
+
private fun quickAffordanceInternal(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceModel> {
- val configs = registry.getAll(position)
+ return if (isUsingRepository) {
+ repository
+ .get()
+ .selections
+ .map { it[position.toSlotId()] ?: emptyList() }
+ .flatMapLatest { configs -> combinedConfigs(position, configs) }
+ } else {
+ combinedConfigs(position, registry.getAll(position))
+ }
+ }
+
+ private fun combinedConfigs(
+ position: KeyguardQuickAffordancePosition,
+ configs: List<KeyguardQuickAffordanceConfig>,
+ ): Flow<KeyguardQuickAffordanceModel> {
+ if (configs.isEmpty()) {
+ return flowOf(KeyguardQuickAffordanceModel.Hidden)
+ }
+
return combine(
configs.map { config ->
- // We emit an initial "Hidden" value to make sure that there's always an initial
- // value and avoid subtle bugs where the downstream isn't receiving any values
- // because one config implementation is not emitting an initial value. For example,
- // see b/244296596.
+ // We emit an initial "Hidden" value to make sure that there's always an
+ // initial value and avoid subtle bugs where the downstream isn't receiving
+ // any values because one config implementation is not emitting an initial
+ // value. For example, see b/244296596.
config.lockScreenState.onStart {
emit(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
}
}
) { states ->
val index =
- states.indexOfFirst { it is KeyguardQuickAffordanceConfig.LockScreenState.Visible }
+ states.indexOfFirst { state ->
+ state is KeyguardQuickAffordanceConfig.LockScreenState.Visible
+ }
if (index != -1) {
val visibleState =
states[index] as KeyguardQuickAffordanceConfig.LockScreenState.Visible
+ val configKey = configs[index].key
KeyguardQuickAffordanceModel.Visible(
- configKey = configs[index].key,
+ configKey =
+ if (isUsingRepository) {
+ configKey.encode(position.toSlotId())
+ } else {
+ configKey
+ },
icon = visibleState.icon,
activationState = visibleState.activationState,
)
@@ -145,4 +283,39 @@ constructor(
)
}
}
+
+ private fun KeyguardQuickAffordancePosition.toSlotId(): String {
+ return when (this) {
+ KeyguardQuickAffordancePosition.BOTTOM_START ->
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+ KeyguardQuickAffordancePosition.BOTTOM_END ->
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END
+ }
+ }
+
+ private fun String.encode(slotId: String): String {
+ return "$slotId$DELIMITER$this"
+ }
+
+ private fun String.decode(): Pair<String, String> {
+ val splitUp = this.split(DELIMITER)
+ return Pair(splitUp[0], splitUp[1])
+ }
+
+ fun getAffordancePickerRepresentations(): List<KeyguardQuickAffordancePickerRepresentation> {
+ check(isUsingRepository)
+
+ return repository.get().getAffordancePickerRepresentations()
+ }
+
+ fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
+ check(isUsingRepository)
+
+ return repository.get().getSlotPickerRepresentations()
+ }
+
+ companion object {
+ private const val TAG = "KeyguardQuickAffordanceInteractor"
+ private const val DELIMITER = "::"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index 83d94325b9d4..57fb4a114700 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -41,11 +41,13 @@ constructor(
}
scope.launch {
- interactor.finishedKeyguardState.collect { logger.i("Finished transition to", it) }
+ interactor.finishedKeyguardTransitionStep.collect {
+ logger.i("Finished transition", it)
+ }
}
scope.launch {
- interactor.startedKeyguardState.collect { logger.i("Started transition to", it) }
+ interactor.startedKeyguardTransitionStep.collect { logger.i("Started transition", it) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index d5ea77b8e729..a7c6d4450336 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -41,6 +41,7 @@ constructor(
is AodLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
is GoneAodTransitionInteractor -> Log.d(TAG, "Started $it")
is LockscreenGoneTransitionInteractor -> Log.d(TAG, "Started $it")
+ is AodToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
}
it.start()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index dffd097a77c5..749183e241e5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -21,7 +21,6 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
-import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -44,8 +43,9 @@ constructor(
/** LOCKSCREEN->AOD transition information. */
val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD)
- /** GONE->AOD information. */
- val goneToAodTransition: Flow<TransitionStep> = repository.transition(GONE, AOD)
+ /** (any)->AOD transition information */
+ val anyStateToAodTransition: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.to == KeyguardState.AOD }
/**
* AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
@@ -57,15 +57,15 @@ constructor(
lockscreenToAodTransition,
)
+ /* The last [TransitionStep] with a [TransitionState] of FINISHED */
+ val finishedKeyguardTransitionStep: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
+
/* The last completed [KeyguardState] transition */
val finishedKeyguardState: Flow<KeyguardState> =
- repository.transitions
- .filter { step -> step.transitionState == TransitionState.FINISHED }
- .map { step -> step.to }
+ finishedKeyguardTransitionStep.map { step -> step.to }
- /* The last started [KeyguardState] transition */
- val startedKeyguardState: Flow<KeyguardState> =
- repository.transitions
- .filter { step -> step.transitionState == TransitionState.STARTED }
- .map { step -> step.to }
+ /* The last [TransitionStep] with a [TransitionState] of STARTED */
+ val startedKeyguardTransitionStep: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
index 761f3fd9d9f9..fd4814d2bc94 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
@@ -16,14 +16,16 @@
package com.android.systemui.keyguard.domain.interactor
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.util.kotlin.sample
import java.util.UUID
@@ -38,7 +40,7 @@ class LockscreenBouncerTransitionInteractor
@Inject
constructor(
@Application private val scope: CoroutineScope,
- private val keyguardRepository: KeyguardRepository,
+ private val keyguardInteractor: KeyguardInteractor,
private val shadeRepository: ShadeRepository,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor
@@ -47,12 +49,47 @@ constructor(
private var transitionId: UUID? = null
override fun start() {
+ listenForDraggingUpToBouncer()
+ listenForBouncerHiding()
+ }
+
+ private fun listenForBouncerHiding() {
+ scope.launch {
+ keyguardInteractor.isBouncerShowing
+ .sample(keyguardInteractor.wakefulnessState, { a, b -> Pair(a, b) })
+ .collect { pair ->
+ val (isBouncerShowing, wakefulnessState) = pair
+ if (!isBouncerShowing) {
+ val to =
+ if (
+ wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP ||
+ wakefulnessState == WakefulnessModel.ASLEEP
+ ) {
+ KeyguardState.AOD
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = name,
+ from = KeyguardState.BOUNCER,
+ to = to,
+ animator = getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ /* Starts transitions when manually dragging up the bouncer from the lockscreen. */
+ private fun listenForDraggingUpToBouncer() {
scope.launch {
shadeRepository.shadeModel
.sample(
combine(
keyguardTransitionInteractor.finishedKeyguardState,
- keyguardRepository.statusBarState,
+ keyguardInteractor.statusBarState,
) { keyguardState, statusBarState ->
Pair(keyguardState, statusBarState)
},
@@ -100,4 +137,15 @@ constructor(
}
}
}
+
+ private fun getAnimator(): ValueAnimator {
+ return ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(TRANSITION_DURATION_MS)
+ }
+ }
+
+ companion object {
+ private const val TRANSITION_DURATION_MS = 300L
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
index 728bafae2a4c..37f33afbf53e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -42,6 +42,8 @@ abstract class StartKeyguardTransitionModule {
@Binds @IntoSet abstract fun goneAod(impl: GoneAodTransitionInteractor): TransitionInteractor
+ @Binds @IntoSet abstract fun aodGone(impl: AodToGoneTransitionInteractor): TransitionInteractor
+
@Binds
@IntoSet
abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
new file mode 100644
index 000000000000..db709b476c5d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.systemui.keyguard.shared.model
+
+/** Model device wakefulness states. */
+enum class BiometricUnlockModel {
+ /** Mode in which we don't need to wake up the device when we authenticate. */
+ NONE,
+ /**
+ * Mode in which we wake up the device, and directly dismiss Keyguard. Active when we acquire a
+ * fingerprint while the screen is off and the device was sleeping.
+ */
+ WAKE_AND_UNLOCK,
+ /**
+ * Mode in which we wake the device up, and fade out the Keyguard contents because they were
+ * already visible while pulsing in doze mode.
+ */
+ WAKE_AND_UNLOCK_PULSING,
+ /**
+ * Mode in which we wake up the device, but play the normal dismiss animation. Active when we
+ * acquire a fingerprint pulsing in doze mode.
+ */
+ SHOW_BOUNCER,
+ /**
+ * Mode in which we only wake up the device, and keyguard was not showing when we authenticated.
+ */
+ ONLY_WAKE,
+ /**
+ * Mode in which fingerprint unlocks the device or passive auth (ie face auth) unlocks the
+ * device while being requested when keyguard is occluded or showing.
+ */
+ UNLOCK_COLLAPSING,
+ /** When bouncer is visible and will be dismissed. */
+ DISMISS_BOUNCER,
+ /** Mode in which fingerprint wakes and unlocks the device from a dream. */
+ WAKE_AND_UNLOCK_FROM_DREAM,
+}
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/DummyAppWidget.java b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
index fd99b21c9538..a56bc900f936 100644
--- a/services/tests/servicestests/src/com/android/server/appwidget/DummyAppWidget.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 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.
@@ -11,21 +11,20 @@
* 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
+ * limitations under the License.
+ *
*/
-package com.android.server.appwidget;
+package com.android.systemui.keyguard.shared.model
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
+import androidx.annotation.DrawableRes
/**
- * Placeholder widget for testing
+ * Representation of a quick affordance for use to build "picker", "selector", or "settings"
+ * experiences.
*/
-public class DummyAppWidget extends BroadcastReceiver {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- }
-}
+data class KeyguardQuickAffordancePickerRepresentation(
+ val id: String,
+ val name: String,
+ @DrawableRes val iconResourceId: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSlotPickerRepresentation.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSlotPickerRepresentation.kt
new file mode 100644
index 000000000000..86f27567ddf8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSlotPickerRepresentation.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.systemui.keyguard.shared.model
+
+/**
+ * Representation of a quick affordance slot (or position) for use to build "picker", "selector", or
+ * "settings" experiences.
+ */
+data class KeyguardSlotPickerRepresentation(
+ val id: String,
+ /** The maximum number of selected affordances that can be present on this slot. */
+ val maxSelectedAffordances: Int = 1,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
index 0ca358210813..732a6f7b887a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
@@ -21,10 +21,11 @@ data class TransitionStep(
val to: KeyguardState = KeyguardState.NONE,
val value: Float = 0f, // constrained [0.0, 1.0]
val transitionState: TransitionState = TransitionState.FINISHED,
+ val ownerName: String = "",
) {
constructor(
info: TransitionInfo,
value: Float,
transitionState: TransitionState,
- ) : this(info.from, info.to, value, transitionState)
+ ) : this(info.from, info.to, value, transitionState, info.ownerName)
}
diff --git a/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt
new file mode 100644
index 000000000000..1324d2c5c27b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.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.systemui.motiontool
+
+import android.view.WindowManagerGlobal
+import com.android.app.motiontool.DdmHandleMotionTool
+import com.android.app.motiontool.MotionToolManager
+import com.android.app.viewcapture.ViewCapture
+import com.android.systemui.CoreStartable
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface MotionToolModule {
+
+ companion object {
+
+ @Provides
+ fun provideDdmHandleMotionTool(motionToolManager: MotionToolManager): DdmHandleMotionTool {
+ return DdmHandleMotionTool.getInstance(motionToolManager)
+ }
+
+ @Provides
+ fun provideMotionToolManager(
+ viewCapture: ViewCapture,
+ windowManagerGlobal: WindowManagerGlobal
+ ): MotionToolManager {
+ return MotionToolManager.getInstance(viewCapture, windowManagerGlobal)
+ }
+
+ @Provides
+ fun provideWindowManagerGlobal(): WindowManagerGlobal = WindowManagerGlobal.getInstance()
+
+ @Provides fun provideViewCapture(): ViewCapture = ViewCapture.getInstance()
+ }
+
+ @Binds
+ @IntoMap
+ @ClassKey(MotionToolStartable::class)
+ fun bindMotionToolStartable(impl: MotionToolStartable): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolStartable.kt b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolStartable.kt
new file mode 100644
index 000000000000..fbb9538316f7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolStartable.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.systemui.motiontool
+
+import com.android.app.motiontool.DdmHandleMotionTool
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class MotionToolStartable
+@Inject
+internal constructor(private val ddmHandleMotionTool: DdmHandleMotionTool) : CoreStartable {
+
+ override fun start() {
+ ddmHandleMotionTool.register()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
index 258f9fc5449f..c070fccf9808 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.media.AudioAttributes;
+import android.os.Process;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
@@ -33,6 +34,7 @@ import java.util.concurrent.Executor;
import javax.inject.Inject;
/**
+ *
*/
@SysUISingleton
public class VibratorHelper {
@@ -40,9 +42,18 @@ public class VibratorHelper {
private final Vibrator mVibrator;
public static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
+
+ private static final VibrationEffect BIOMETRIC_SUCCESS_VIBRATION_EFFECT =
+ VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+ private static final VibrationEffect BIOMETRIC_ERROR_VIBRATION_EFFECT =
+ VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
+ private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
+ VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
+
private final Executor mExecutor;
/**
+ *
*/
@Inject
public VibratorHelper(@Nullable Vibrator vibrator, @Background Executor executor) {
@@ -109,4 +120,23 @@ public class VibratorHelper {
}
mExecutor.execute(mVibrator::cancel);
}
+
+ /**
+ * Perform vibration when biometric authentication success
+ */
+ public void vibrateAuthSuccess(String reason) {
+ vibrate(Process.myUid(),
+ "com.android.systemui",
+ BIOMETRIC_SUCCESS_VIBRATION_EFFECT, reason,
+ HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
+ }
+
+ /**
+ * Perform vibration when biometric authentication error
+ */
+ public void vibrateAuthError(String reason) {
+ vibrate(Process.myUid(), "com.android.systemui",
+ BIOMETRIC_ERROR_VIBRATION_EFFECT, reason,
+ HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 470cbcb842c4..5dbb4f9d70df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -33,6 +33,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
+import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.dagger.IncomingHeader
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
@@ -68,6 +69,7 @@ class HeadsUpCoordinator @Inject constructor(
private val mHeadsUpViewBinder: HeadsUpViewBinder,
private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider,
private val mRemoteInputManager: NotificationRemoteInputManager,
+ private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider,
@IncomingHeader private val mIncomingHeaderController: NodeController,
@Main private val mExecutor: DelayableExecutor,
) : Coordinator {
@@ -380,6 +382,12 @@ class HeadsUpCoordinator @Inject constructor(
* Notification was just added and if it should heads up, bind the view and then show it.
*/
override fun onEntryAdded(entry: NotificationEntry) {
+ // First check whether this notification should launch a full screen intent, and
+ // launch it if needed.
+ if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) {
+ mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry)
+ }
+
// shouldHeadsUp includes check for whether this notification should be filtered
val shouldHeadsUpEver = mNotificationInterruptStateProvider.shouldHeadsUp(entry)
mPostedEntries[entry.key] = PostedEntry(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProvider.kt
new file mode 100644
index 000000000000..74ff78e25a2f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProvider.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.systemui.statusbar.notification.collection.provider
+
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.util.ListenerSet
+import javax.inject.Inject
+
+/**
+ * A class that enables communication of decisions to launch a notification's full screen intent.
+ */
+@SysUISingleton
+class LaunchFullScreenIntentProvider @Inject constructor() {
+ companion object {
+ private const val TAG = "LaunchFullScreenIntentProvider"
+ }
+ private val listeners = ListenerSet<Listener>()
+
+ /**
+ * Registers a listener with this provider. These listeners will be alerted whenever a full
+ * screen intent should be launched for a notification entry.
+ */
+ fun registerListener(listener: Listener) {
+ listeners.addIfAbsent(listener)
+ }
+
+ /** Removes the specified listener. */
+ fun removeListener(listener: Listener) {
+ listeners.remove(listener)
+ }
+
+ /**
+ * Sends a request to launch full screen intent for the given notification entry to all
+ * registered listeners.
+ */
+ fun launchFullScreenIntent(entry: NotificationEntry) {
+ if (listeners.isEmpty()) {
+ // This should never happen, but we should definitely know if it does because having
+ // no listeners would indicate that FSIs are getting entirely dropped on the floor.
+ Log.wtf(TAG, "no listeners found when launchFullScreenIntent requested")
+ }
+ for (listener in listeners) {
+ listener.onFullScreenIntentRequested(entry)
+ }
+ }
+
+ /** Listener interface for passing full screen intent launch decisions. */
+ fun interface Listener {
+ /**
+ * Invoked whenever a full screen intent launch is requested for the given notification
+ * entry.
+ */
+ fun onFullScreenIntentRequested(entry: NotificationEntry)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index a2798f47be65..6e68079706f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -27,11 +27,8 @@ import android.hardware.fingerprint.FingerprintManager;
import android.metrics.LogMaker;
import android.os.Handler;
import android.os.PowerManager;
-import android.os.Process;
import android.os.SystemClock;
import android.os.Trace;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
import android.util.Log;
import androidx.annotation.Nullable;
@@ -70,8 +67,10 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import javax.inject.Inject;
@@ -87,12 +86,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock";
private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
private static final int UDFPS_ATTEMPTS_BEFORE_SHOW_BOUNCER = 3;
- private static final VibrationEffect SUCCESS_VIBRATION_EFFECT =
- VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
- private static final VibrationEffect ERROR_VIBRATION_EFFECT =
- VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
- private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
- VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
@IntDef(prefix = { "MODE_" }, value = {
MODE_NONE,
@@ -167,7 +160,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final SessionTracker mSessionTracker;
private final int mConsecutiveFpFailureThreshold;
- private final boolean mShouldVibrate;
private int mMode;
private BiometricSourceType mBiometricType;
private KeyguardViewController mKeyguardViewController;
@@ -177,7 +169,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
private PendingAuthenticated mPendingAuthenticated = null;
private boolean mHasScreenTurnedOnSinceAuthenticating;
private boolean mFadedAwayAfterWakeAndUnlock;
- private BiometricModeListener mBiometricModeListener;
+ private Set<BiometricModeListener> mBiometricModeListeners = new HashSet<>();
private final MetricsLogger mMetricsLogger;
private final AuthController mAuthController;
@@ -307,8 +299,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
mHandler = handler;
mConsecutiveFpFailureThreshold = resources.getInteger(
R.integer.fp_consecutive_failure_time_ms);
- mShouldVibrate = !(resources.getBoolean(
- com.android.internal.R.bool.system_server_plays_face_haptics));
mKeyguardBypassController = keyguardBypassController;
mKeyguardBypassController.setUnlockController(this);
mMetricsLogger = metricsLogger;
@@ -326,9 +316,14 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
mKeyguardViewController = keyguardViewController;
}
- /** Sets a {@link BiometricModeListener}. */
- public void setBiometricModeListener(BiometricModeListener biometricModeListener) {
- mBiometricModeListener = biometricModeListener;
+ /** Adds a {@link BiometricModeListener}. */
+ public void addBiometricModeListener(BiometricModeListener listener) {
+ mBiometricModeListeners.add(listener);
+ }
+
+ /** Removes a {@link BiometricModeListener}. */
+ public void removeBiometricModeListener(BiometricModeListener listener) {
+ mBiometricModeListeners.remove(listener);
}
private final Runnable mReleaseBiometricWakeLockRunnable = new Runnable() {
@@ -423,10 +418,10 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
public void startWakeAndUnlock(BiometricSourceType biometricSourceType,
boolean isStrongBiometric) {
int mode = calculateMode(biometricSourceType, isStrongBiometric);
- if (BiometricSourceType.FACE == biometricSourceType && (mode == MODE_WAKE_AND_UNLOCK
+ if (mode == MODE_WAKE_AND_UNLOCK
|| mode == MODE_WAKE_AND_UNLOCK_PULSING || mode == MODE_UNLOCK_COLLAPSING
- || mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM || mode == MODE_DISMISS_BOUNCER)) {
- vibrateSuccess();
+ || mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM || mode == MODE_DISMISS_BOUNCER) {
+ vibrateSuccess(biometricSourceType);
}
startWakeAndUnlock(mode);
}
@@ -511,15 +506,12 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
break;
}
onModeChanged(mMode);
- if (mBiometricModeListener != null) {
- mBiometricModeListener.notifyBiometricAuthModeChanged();
- }
Trace.endSection();
}
private void onModeChanged(@WakeAndUnlockMode int mode) {
- if (mBiometricModeListener != null) {
- mBiometricModeListener.onModeChanged(mode);
+ for (BiometricModeListener listener : mBiometricModeListeners) {
+ listener.onModeChanged(mode);
}
}
@@ -659,10 +651,11 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
}
// Suppress all face auth errors if fingerprint can be used to authenticate
- if (biometricSourceType == BiometricSourceType.FACE
+ if ((biometricSourceType == BiometricSourceType.FACE
&& !mUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- KeyguardUpdateMonitor.getCurrentUser())) {
- vibrateError();
+ KeyguardUpdateMonitor.getCurrentUser()))
+ || (biometricSourceType == BiometricSourceType.FINGERPRINT)) {
+ vibrateError(biometricSourceType);
}
cleanup();
@@ -688,24 +681,15 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
cleanup();
}
- private void vibrateSuccess() {
- if (mShouldVibrate) {
- mVibratorHelper.vibrate(Process.myUid(),
- "com.android.systemui",
- SUCCESS_VIBRATION_EFFECT,
- getClass().getSimpleName() + "::success",
- HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
- }
+ //these haptics are for device-entry only
+ private void vibrateSuccess(BiometricSourceType type) {
+ mVibratorHelper.vibrateAuthSuccess(
+ getClass().getSimpleName() + ", type =" + type + "device-entry::success");
}
- private void vibrateError() {
- if (mShouldVibrate) {
- mVibratorHelper.vibrate(Process.myUid(),
- "com.android.systemui",
- ERROR_VIBRATION_EFFECT,
- getClass().getSimpleName() + "::error",
- HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
- }
+ private void vibrateError(BiometricSourceType type) {
+ mVibratorHelper.vibrateAuthError(
+ getClass().getSimpleName() + ", type =" + type + "device-entry::error");
}
private void cleanup() {
@@ -734,9 +718,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
mMode = MODE_NONE;
mBiometricType = null;
mNotificationShadeWindowController.setForceDozeBrightness(false);
- if (mBiometricModeListener != null) {
- mBiometricModeListener.onResetMode();
- mBiometricModeListener.notifyBiometricAuthModeChanged();
+ for (BiometricModeListener listener : mBiometricModeListeners) {
+ listener.onResetMode();
}
mNumConsecutiveFpFailures = 0;
mLastFpFailureUptimeMillis = 0;
@@ -845,10 +828,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
/** An interface to interact with the {@link BiometricUnlockController}. */
public interface BiometricModeListener {
/** Called when {@code mMode} is reset to {@link #MODE_NONE}. */
- void onResetMode();
+ default void onResetMode() {}
/** Called when {@code mMode} has changed in {@link #startWakeAndUnlock(int)}. */
- void onModeChanged(@WakeAndUnlockMode int mode);
- /** Called after processing {@link #onModeChanged(int)}. */
- void notifyBiometricAuthModeChanged();
+ default void onModeChanged(@WakeAndUnlockMode int mode) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index be3052e3b3d1..15cc086586da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1527,11 +1527,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
protected void startKeyguard() {
Trace.beginSection("CentralSurfaces#startKeyguard");
mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
- mBiometricUnlockController.setBiometricModeListener(
+ mBiometricUnlockController.addBiometricModeListener(
new BiometricUnlockController.BiometricModeListener() {
@Override
public void onResetMode() {
setWakeAndUnlocking(false);
+ notifyBiometricAuthModeChanged();
}
@Override
@@ -1542,11 +1543,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
case BiometricUnlockController.MODE_WAKE_AND_UNLOCK:
setWakeAndUnlocking(true);
}
- }
-
- @Override
- public void notifyBiometricAuthModeChanged() {
- CentralSurfacesImpl.this.notifyBiometricAuthModeChanged();
+ notifyBiometricAuthModeChanged();
}
private void setWakeAndUnlocking(boolean wakeAndUnlocking) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 86f6ff850409..0a0ded24ef30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -18,6 +18,7 @@ import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
+import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI_NEW;
import android.annotation.Nullable;
import android.content.Context;
@@ -53,8 +54,9 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder;
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
+import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel;
import com.android.systemui.util.Assert;
import java.util.ArrayList;
@@ -84,7 +86,18 @@ public interface StatusBarIconController {
/** */
void setIcon(String slot, StatusBarIcon icon);
/** */
- void setSignalIcon(String slot, WifiIconState state);
+ void setWifiIcon(String slot, WifiIconState state);
+
+ /**
+ * Sets up a wifi icon using the new data pipeline. No effect if the wifi icon has already been
+ * set up (inflated and added to the view hierarchy).
+ *
+ * This method completely replaces {@link #setWifiIcon} with the information from the new wifi
+ * data pipeline. Icons will automatically keep their state up to date, so we don't have to
+ * worry about funneling state objects through anymore.
+ */
+ void setNewWifiIcon();
+
/** */
void setMobileIcons(String slot, List<MobileIconState> states);
@@ -151,14 +164,14 @@ public interface StatusBarIconController {
LinearLayout linearLayout,
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
- WifiViewModel wifiViewModel,
+ WifiUiAdapter wifiUiAdapter,
MobileUiAdapter mobileUiAdapter,
MobileContextProvider mobileContextProvider,
DarkIconDispatcher darkIconDispatcher) {
super(linearLayout,
location,
statusBarPipelineFlags,
- wifiViewModel,
+ wifiUiAdapter,
mobileUiAdapter,
mobileContextProvider);
mIconHPadding = mContext.getResources().getDimensionPixelSize(
@@ -218,7 +231,7 @@ public interface StatusBarIconController {
@SysUISingleton
public static class Factory {
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
- private final WifiViewModel mWifiViewModel;
+ private final WifiUiAdapter mWifiUiAdapter;
private final MobileContextProvider mMobileContextProvider;
private final MobileUiAdapter mMobileUiAdapter;
private final DarkIconDispatcher mDarkIconDispatcher;
@@ -226,12 +239,12 @@ public interface StatusBarIconController {
@Inject
public Factory(
StatusBarPipelineFlags statusBarPipelineFlags,
- WifiViewModel wifiViewModel,
+ WifiUiAdapter wifiUiAdapter,
MobileContextProvider mobileContextProvider,
MobileUiAdapter mobileUiAdapter,
DarkIconDispatcher darkIconDispatcher) {
mStatusBarPipelineFlags = statusBarPipelineFlags;
- mWifiViewModel = wifiViewModel;
+ mWifiUiAdapter = wifiUiAdapter;
mMobileContextProvider = mobileContextProvider;
mMobileUiAdapter = mobileUiAdapter;
mDarkIconDispatcher = darkIconDispatcher;
@@ -242,7 +255,7 @@ public interface StatusBarIconController {
group,
location,
mStatusBarPipelineFlags,
- mWifiViewModel,
+ mWifiUiAdapter,
mMobileUiAdapter,
mMobileContextProvider,
mDarkIconDispatcher);
@@ -260,14 +273,14 @@ public interface StatusBarIconController {
ViewGroup group,
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
- WifiViewModel wifiViewModel,
+ WifiUiAdapter wifiUiAdapter,
MobileUiAdapter mobileUiAdapter,
MobileContextProvider mobileContextProvider
) {
super(group,
location,
statusBarPipelineFlags,
- wifiViewModel,
+ wifiUiAdapter,
mobileUiAdapter,
mobileContextProvider);
}
@@ -302,19 +315,19 @@ public interface StatusBarIconController {
@SysUISingleton
public static class Factory {
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
- private final WifiViewModel mWifiViewModel;
+ private final WifiUiAdapter mWifiUiAdapter;
private final MobileContextProvider mMobileContextProvider;
private final MobileUiAdapter mMobileUiAdapter;
@Inject
public Factory(
StatusBarPipelineFlags statusBarPipelineFlags,
- WifiViewModel wifiViewModel,
+ WifiUiAdapter wifiUiAdapter,
MobileUiAdapter mobileUiAdapter,
MobileContextProvider mobileContextProvider
) {
mStatusBarPipelineFlags = statusBarPipelineFlags;
- mWifiViewModel = wifiViewModel;
+ mWifiUiAdapter = wifiUiAdapter;
mMobileUiAdapter = mobileUiAdapter;
mMobileContextProvider = mobileContextProvider;
}
@@ -324,7 +337,7 @@ public interface StatusBarIconController {
group,
location,
mStatusBarPipelineFlags,
- mWifiViewModel,
+ mWifiUiAdapter,
mMobileUiAdapter,
mMobileContextProvider);
}
@@ -336,10 +349,9 @@ public interface StatusBarIconController {
*/
class IconManager implements DemoModeCommandReceiver {
protected final ViewGroup mGroup;
- private final StatusBarLocation mLocation;
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
- private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
+ private final LocationBasedWifiViewModel mWifiViewModel;
private final MobileIconsViewModel mMobileIconsViewModel;
protected final Context mContext;
@@ -359,26 +371,33 @@ public interface StatusBarIconController {
ViewGroup group,
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
- WifiViewModel wifiViewModel,
+ WifiUiAdapter wifiUiAdapter,
MobileUiAdapter mobileUiAdapter,
MobileContextProvider mobileContextProvider
) {
mGroup = group;
- mLocation = location;
mStatusBarPipelineFlags = statusBarPipelineFlags;
- mWifiViewModel = wifiViewModel;
mMobileContextProvider = mobileContextProvider;
mContext = group.getContext();
mIconSize = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_icon_size);
- if (statusBarPipelineFlags.useNewMobileIcons()) {
- // This starts the flow for the new pipeline, and will notify us of changes
+ if (statusBarPipelineFlags.runNewMobileIconsBackend()) {
+ // This starts the flow for the new pipeline, and will notify us of changes if
+ // {@link StatusBarPipelineFlags#useNewMobileIcons} is also true.
mMobileIconsViewModel = mobileUiAdapter.createMobileIconsViewModel();
MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
} else {
mMobileIconsViewModel = null;
}
+
+ if (statusBarPipelineFlags.runNewWifiIconBackend()) {
+ // This starts the flow for the new pipeline, and will notify us of changes if
+ // {@link StatusBarPipelineFlags#useNewWifiIcon} is also true.
+ mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, location);
+ } else {
+ mWifiViewModel = null;
+ }
}
public boolean isDemoable() {
@@ -429,6 +448,9 @@ public interface StatusBarIconController {
case TYPE_WIFI:
return addWifiIcon(index, slot, holder.getWifiState());
+ case TYPE_WIFI_NEW:
+ return addNewWifiIcon(index, slot);
+
case TYPE_MOBILE:
return addMobileIcon(index, slot, holder.getMobileState());
@@ -450,16 +472,13 @@ public interface StatusBarIconController {
@VisibleForTesting
protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
- final BaseStatusBarFrameLayout view;
if (mStatusBarPipelineFlags.useNewWifiIcon()) {
- view = onCreateModernStatusBarWifiView(slot);
- // When [ModernStatusBarWifiView] is created, it will automatically apply the
- // correct view state so we don't need to call applyWifiState.
- } else {
- StatusBarWifiView wifiView = onCreateStatusBarWifiView(slot);
- wifiView.applyWifiState(state);
- view = wifiView;
+ throw new IllegalStateException("Attempting to add a mobile icon while the new "
+ + "icons are enabled is not supported");
}
+
+ final StatusBarWifiView view = onCreateStatusBarWifiView(slot);
+ view.applyWifiState(state);
mGroup.addView(view, index, onCreateLayoutParams());
if (mIsInDemoMode) {
@@ -468,6 +487,17 @@ public interface StatusBarIconController {
return view;
}
+ protected StatusIconDisplayable addNewWifiIcon(int index, String slot) {
+ if (!mStatusBarPipelineFlags.useNewWifiIcon()) {
+ throw new IllegalStateException("Attempting to add a wifi icon using the new"
+ + "pipeline, but the enabled flag is false.");
+ }
+
+ ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot);
+ mGroup.addView(view, index, onCreateLayoutParams());
+ return view;
+ }
+
@VisibleForTesting
protected StatusIconDisplayable addMobileIcon(
int index,
@@ -523,8 +553,7 @@ public interface StatusBarIconController {
}
private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) {
- return ModernStatusBarWifiView.constructAndBind(
- mContext, slot, mWifiViewModel, mLocation);
+ return ModernStatusBarWifiView.constructAndBind(mContext, slot, mWifiViewModel);
}
private StatusBarMobileView onCreateStatusBarMobileView(int subId, String slot) {
@@ -600,7 +629,8 @@ public interface StatusBarIconController {
onSetMobileIcon(viewIndex, holder.getMobileState());
return;
case TYPE_MOBILE_NEW:
- // Nothing, the icon updates itself now
+ case TYPE_WIFI_NEW:
+ // Nothing, the new icons update themselves
return;
default:
break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 31e960ad7d69..674e5747e331 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -195,12 +195,13 @@ public class StatusBarIconControllerImpl implements Tunable,
}
}
- /**
- * Signal icons need to be handled differently, because they can be
- * composite views
- */
@Override
- public void setSignalIcon(String slot, WifiIconState state) {
+ public void setWifiIcon(String slot, WifiIconState state) {
+ if (mStatusBarPipelineFlags.useNewWifiIcon()) {
+ Log.d(TAG, "ignoring old pipeline callback because the new wifi icon is enabled");
+ return;
+ }
+
if (state == null) {
removeIcon(slot, 0);
return;
@@ -216,6 +217,24 @@ public class StatusBarIconControllerImpl implements Tunable,
}
}
+
+ @Override
+ public void setNewWifiIcon() {
+ if (!mStatusBarPipelineFlags.useNewWifiIcon()) {
+ Log.d(TAG, "ignoring new pipeline callback because the new wifi icon is disabled");
+ return;
+ }
+
+ String slot = mContext.getString(com.android.internal.R.string.status_bar_wifi);
+ StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, /* tag= */ 0);
+ if (holder == null) {
+ holder = StatusBarIconHolder.forNewWifiIcon();
+ setIcon(slot, holder);
+ } else {
+ // Don't have to do anything in the new world
+ }
+ }
+
/**
* Accept a list of MobileIconStates, which all live in the same slot(?!), and then are sorted
* by subId. Don't worry this definitely makes sense and works.
@@ -225,7 +244,7 @@ public class StatusBarIconControllerImpl implements Tunable,
@Override
public void setMobileIcons(String slot, List<MobileIconState> iconStates) {
if (mStatusBarPipelineFlags.useNewMobileIcons()) {
- Log.d(TAG, "ignoring old pipeline callbacks, because the new "
+ Log.d(TAG, "ignoring old pipeline callbacks, because the new mobile "
+ "icons are enabled");
return;
}
@@ -251,10 +270,11 @@ public class StatusBarIconControllerImpl implements Tunable,
public void setNewMobileIconSubIds(List<Integer> subIds) {
if (!mStatusBarPipelineFlags.useNewMobileIcons()) {
Log.d(TAG, "ignoring new pipeline callback, "
- + "since the new icons are disabled");
+ + "since the new mobile icons are disabled");
return;
}
- Slot mobileSlot = mStatusBarIconList.getSlot("mobile");
+ String slotName = mContext.getString(com.android.internal.R.string.status_bar_mobile);
+ Slot mobileSlot = mStatusBarIconList.getSlot(slotName);
Collections.reverse(subIds);
@@ -262,7 +282,7 @@ public class StatusBarIconControllerImpl implements Tunable,
StatusBarIconHolder holder = mobileSlot.getHolderForTag(subId);
if (holder == null) {
holder = StatusBarIconHolder.fromSubIdForModernMobileIcon(subId);
- setIcon("mobile", holder);
+ setIcon(slotName, holder);
} else {
// Don't have to do anything in the new world
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index 68a203e30f98..f6c0da8da8c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -51,11 +51,24 @@ public class StatusBarIconHolder {
@Deprecated
public static final int TYPE_MOBILE_NEW = 3;
+ /**
+ * TODO (b/238425913): address this once the new pipeline is in place
+ * This type exists so that the new wifi pipeline can be used to inform the old view system
+ * about the existence of the wifi icon. The design of the new pipeline should allow for removal
+ * of this icon holder type, and obsolete the need for this entire class.
+ *
+ * @deprecated This field only exists so the new status bar pipeline can interface with the
+ * view holder system.
+ */
+ @Deprecated
+ public static final int TYPE_WIFI_NEW = 4;
+
@IntDef({
TYPE_ICON,
TYPE_WIFI,
TYPE_MOBILE,
- TYPE_MOBILE_NEW
+ TYPE_MOBILE_NEW,
+ TYPE_WIFI_NEW
})
@Retention(RetentionPolicy.SOURCE)
@interface IconType {}
@@ -95,6 +108,13 @@ public class StatusBarIconHolder {
return holder;
}
+ /** Creates a new holder with for the new wifi icon. */
+ public static StatusBarIconHolder forNewWifiIcon() {
+ StatusBarIconHolder holder = new StatusBarIconHolder();
+ holder.mType = TYPE_WIFI_NEW;
+ return holder;
+ }
+
/** */
public static StatusBarIconHolder fromMobileIconState(MobileIconState state) {
StatusBarIconHolder holder = new StatusBarIconHolder();
@@ -172,9 +192,10 @@ public class StatusBarIconHolder {
case TYPE_MOBILE:
return mMobileState.visible;
case TYPE_MOBILE_NEW:
- //TODO (b/249790733), the new pipeline can control visibility via the ViewModel
+ case TYPE_WIFI_NEW:
+ // The new pipeline controls visibilities via the view model and view binder, so
+ // this is effectively an unused return value.
return true;
-
default:
return true;
}
@@ -199,7 +220,9 @@ public class StatusBarIconHolder {
break;
case TYPE_MOBILE_NEW:
- //TODO (b/249790733), the new pipeline can control visibility via the ViewModel
+ case TYPE_WIFI_NEW:
+ // The new pipeline controls visibilities via the view model and view binder, so
+ // ignore setVisible.
break;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 5cd2ba1b1cf3..b6ae4a088880 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -23,7 +23,6 @@ import static com.android.systemui.statusbar.phone.CentralSurfaces.getActivityOp
import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.app.Notification;
-import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.TaskStackBuilder;
import android.content.Context;
@@ -60,9 +59,8 @@ import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -126,7 +124,6 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
Context context,
Handler mainThreadHandler,
Executor uiBgExecutor,
- NotifPipeline notifPipeline,
NotificationVisibilityProvider visibilityProvider,
HeadsUpManagerPhone headsUpManager,
ActivityStarter activityStarter,
@@ -151,7 +148,8 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
NotificationPresenter presenter,
NotificationPanelViewController panel,
ActivityLaunchAnimator activityLaunchAnimator,
- NotificationLaunchAnimatorControllerProvider notificationAnimationProvider) {
+ NotificationLaunchAnimatorControllerProvider notificationAnimationProvider,
+ LaunchFullScreenIntentProvider launchFullScreenIntentProvider) {
mContext = context;
mMainThreadHandler = mainThreadHandler;
mUiBgExecutor = uiBgExecutor;
@@ -182,12 +180,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
mActivityLaunchAnimator = activityLaunchAnimator;
mNotificationAnimationProvider = notificationAnimationProvider;
- notifPipeline.addCollectionListener(new NotifCollectionListener() {
- @Override
- public void onEntryAdded(NotificationEntry entry) {
- handleFullScreenIntent(entry);
- }
- });
+ launchFullScreenIntentProvider.registerListener(entry -> launchFullScreenIntent(entry));
}
/**
@@ -549,38 +542,36 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
}
@VisibleForTesting
- void handleFullScreenIntent(NotificationEntry entry) {
- if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) {
- if (shouldSuppressFullScreenIntent(entry)) {
- mLogger.logFullScreenIntentSuppressedByDnD(entry);
- } else if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
- mLogger.logFullScreenIntentNotImportantEnough(entry);
- } else {
- // Stop screensaver if the notification has a fullscreen intent.
- // (like an incoming phone call)
- mUiBgExecutor.execute(() -> {
- try {
- mDreamManager.awaken();
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- });
+ void launchFullScreenIntent(NotificationEntry entry) {
+ // Skip if device is in VR mode.
+ if (mPresenter.isDeviceInVrMode()) {
+ mLogger.logFullScreenIntentSuppressedByVR(entry);
+ return;
+ }
- // not immersive & a fullscreen alert should be shown
- final PendingIntent fullscreenIntent =
- entry.getSbn().getNotification().fullScreenIntent;
- mLogger.logSendingFullScreenIntent(entry, fullscreenIntent);
- try {
- EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
- entry.getKey());
- mCentralSurfaces.wakeUpForFullScreenIntent();
- fullscreenIntent.send();
- entry.notifyFullScreenIntentLaunched();
- mMetricsLogger.count("note_fullscreen", 1);
- } catch (PendingIntent.CanceledException e) {
- // ignore
- }
+ // Stop screensaver if the notification has a fullscreen intent.
+ // (like an incoming phone call)
+ mUiBgExecutor.execute(() -> {
+ try {
+ mDreamManager.awaken();
+ } catch (RemoteException e) {
+ e.printStackTrace();
}
+ });
+
+ // not immersive & a fullscreen alert should be shown
+ final PendingIntent fullscreenIntent =
+ entry.getSbn().getNotification().fullScreenIntent;
+ mLogger.logSendingFullScreenIntent(entry, fullscreenIntent);
+ try {
+ EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
+ entry.getKey());
+ mCentralSurfaces.wakeUpForFullScreenIntent();
+ fullscreenIntent.send();
+ entry.notifyFullScreenIntentLaunched();
+ mMetricsLogger.count("note_fullscreen", 1);
+ } catch (PendingIntent.CanceledException e) {
+ // ignore
}
}
@@ -607,12 +598,4 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
mMainThreadHandler.post(mShadeController::collapsePanel);
}
}
-
- private boolean shouldSuppressFullScreenIntent(NotificationEntry entry) {
- if (mPresenter.isDeviceInVrMode()) {
- return true;
- }
-
- return entry.shouldSuppressFullScreenIntent();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
index 81edff45c505..1f0b96a58da6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
@@ -96,19 +96,11 @@ class StatusBarNotificationActivityStarterLogger @Inject constructor(
})
}
- fun logFullScreenIntentSuppressedByDnD(entry: NotificationEntry) {
+ fun logFullScreenIntentSuppressedByVR(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
str1 = entry.logKey
}, {
- "No Fullscreen intent: suppressed by DND: $str1"
- })
- }
-
- fun logFullScreenIntentNotImportantEnough(entry: NotificationEntry) {
- buffer.log(TAG, DEBUG, {
- str1 = entry.logKey
- }, {
- "No Fullscreen intent: not important enough: $str1"
+ "No Fullscreen intent: suppressed by VR mode: $str1"
})
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index 492734e93dca..de7bf3c021dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -212,7 +212,7 @@ public class StatusBarSignalPolicy implements SignalCallback,
private void updateWifiIconWithState(WifiIconState state) {
if (DEBUG) Log.d(TAG, "WifiIconState: " + state == null ? "" : state.toString());
if (state.visible && state.resId > 0) {
- mIconController.setSignalIcon(mSlotWifi, state);
+ mIconController.setWifiIcon(mSlotWifi, state);
mIconController.setIconVisibility(mSlotWifi, true);
} else {
mIconController.setIconVisibility(mSlotWifi, false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
index 06cd12dd1a0d..946d7e4a3e75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -27,11 +27,26 @@ class StatusBarPipelineFlags @Inject constructor(private val featureFlags: Featu
/** True if we should display the mobile icons using the new status bar data pipeline. */
fun useNewMobileIcons(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS)
+ /**
+ * True if we should run the new mobile icons backend to get the logging.
+ *
+ * Does *not* affect whether we render the mobile icons using the new backend data. See
+ * [useNewMobileIcons] for that.
+ */
+ fun runNewMobileIconsBackend(): Boolean =
+ featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS_BACKEND) || useNewMobileIcons()
+
/** True if we should display the wifi icon using the new status bar data pipeline. */
fun useNewWifiIcon(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON)
- // TODO(b/238425913): Add flags to only run the mobile backend or wifi backend so we get the
- // logging without getting the UI effects.
+ /**
+ * True if we should run the new wifi icon backend to get the logging.
+ *
+ * Does *not* affect whether we render the wifi icon using the new backend data. See
+ * [useNewWifiIcon] for that.
+ */
+ fun runNewWifiIconBackend(): Boolean =
+ featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON_BACKEND) || useNewWifiIcon()
/**
* Returns true if we should apply some coloring to the wifi icon that was rendered with the new
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index 380017cd3418..c7e0ce173ece 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -20,6 +20,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import javax.inject.Inject
@@ -50,6 +51,7 @@ constructor(
private val iconController: StatusBarIconController,
private val iconsViewModelFactory: MobileIconsViewModel.Factory,
@Application scope: CoroutineScope,
+ private val statusBarPipelineFlags: StatusBarPipelineFlags,
) {
private val mobileSubIds: Flow<List<Int>> =
interactor.filteredSubscriptions.mapLatest { infos ->
@@ -66,8 +68,14 @@ constructor(
private val mobileSubIdsState: StateFlow<List<Int>> =
mobileSubIds
.onEach {
- // Notify the icon controller here so that it knows to add icons
- iconController.setNewMobileIconSubIds(it)
+ // Only notify the icon controller if we want to *render* the new icons.
+ // Note that this flow may still run if
+ // [statusBarPipelineFlags.runNewMobileIconsBackend] is true because we may want to
+ // get the logging data without rendering.
+ if (statusBarPipelineFlags.useNewMobileIcons()) {
+ // Notify the icon controller here so that it knows to add icons
+ iconController.setNewMobileIconSubIds(it)
+ }
}
.stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
new file mode 100644
index 000000000000..b816364ed4cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.systemui.statusbar.pipeline.wifi.ui
+
+import android.view.ViewGroup
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.phone.StatusBarIconController
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/**
+ * This class serves as a bridge between the old UI classes and the new data pipeline.
+ *
+ * Once the new pipeline notifies [wifiViewModel] that the wifi icon should be visible, this class
+ * notifies [iconController] to inflate the wifi icon (if needed). After that, the [wifiViewModel]
+ * has sole responsibility for updating the wifi icon drawable, visibility, etc. and the
+ * [iconController] will not do any updates to the icon.
+ */
+@SysUISingleton
+class WifiUiAdapter
+@Inject
+constructor(
+ private val iconController: StatusBarIconController,
+ private val wifiViewModel: WifiViewModel,
+ private val statusBarPipelineFlags: StatusBarPipelineFlags,
+) {
+ /**
+ * Binds the container for all the status bar icons to a view model, so that we inflate the wifi
+ * view once we receive a valid icon from the data pipeline.
+ *
+ * NOTE: This should go away as we better integrate the data pipeline with the UI.
+ *
+ * @return the view model used for this particular group in the given [location].
+ */
+ fun bindGroup(
+ statusBarIconGroup: ViewGroup,
+ location: StatusBarLocation,
+ ): LocationBasedWifiViewModel {
+ val locationViewModel =
+ when (location) {
+ StatusBarLocation.HOME -> wifiViewModel.home
+ StatusBarLocation.KEYGUARD -> wifiViewModel.keyguard
+ StatusBarLocation.QS -> wifiViewModel.qs
+ }
+
+ statusBarIconGroup.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ locationViewModel.wifiIcon.collect { wifiIcon ->
+ // Only notify the icon controller if we want to *render* the new icon.
+ // Note that this flow may still run if
+ // [statusBarPipelineFlags.runNewWifiIconBackend] is true because we may
+ // want to get the logging data without rendering.
+ if (wifiIcon != null && statusBarPipelineFlags.useNewWifiIcon()) {
+ iconController.setNewWifiIcon()
+ }
+ }
+ }
+ }
+ }
+
+ return locationViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 25537b948517..345f8cb75660 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -30,9 +30,7 @@ import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
-import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
@@ -62,26 +60,9 @@ object WifiViewBinder {
fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int)
}
- /**
- * Binds the view to the appropriate view-model based on the given location. The view will
- * continue to be updated following updates from the view-model.
- */
- @JvmStatic
- fun bind(
- view: ViewGroup,
- wifiViewModel: WifiViewModel,
- location: StatusBarLocation,
- ): Binding {
- return when (location) {
- StatusBarLocation.HOME -> bind(view, wifiViewModel.home)
- StatusBarLocation.KEYGUARD -> bind(view, wifiViewModel.keyguard)
- StatusBarLocation.QS -> bind(view, wifiViewModel.qs)
- }
- }
-
/** Binds the view to the view-model, continuing to update the former based on the latter. */
@JvmStatic
- private fun bind(
+ fun bind(
view: ViewGroup,
viewModel: LocationBasedWifiViewModel,
): Binding {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index 0cd9bd7d97b0..a45076b53356 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -26,9 +26,8 @@ import com.android.systemui.statusbar.BaseStatusBarFrameLayout
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
-import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
/**
* A new and more modern implementation of [com.android.systemui.statusbar.StatusBarWifiView] that
@@ -81,12 +80,11 @@ class ModernStatusBarWifiView(
private fun initView(
slotName: String,
- wifiViewModel: WifiViewModel,
- location: StatusBarLocation,
+ wifiViewModel: LocationBasedWifiViewModel,
) {
slot = slotName
initDotView()
- binding = WifiViewBinder.bind(this, wifiViewModel, location)
+ binding = WifiViewBinder.bind(this, wifiViewModel)
}
// Mostly duplicated from [com.android.systemui.statusbar.StatusBarWifiView].
@@ -116,14 +114,13 @@ class ModernStatusBarWifiView(
fun constructAndBind(
context: Context,
slot: String,
- wifiViewModel: WifiViewModel,
- location: StatusBarLocation,
+ wifiViewModel: LocationBasedWifiViewModel,
): ModernStatusBarWifiView {
return (
LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
as ModernStatusBarWifiView
).also {
- it.initView(slot, wifiViewModel, location)
+ it.initView(slot, wifiViewModel)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index 89b96b7bc75d..0782bbb774eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -145,7 +145,8 @@ constructor(
else -> null
}
}
- .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
+ .logOutputChange(logger, "icon") { icon -> icon?.contentDescription.toString() }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
/** The wifi activity status. Null if we shouldn't display the activity status. */
private val activity: Flow<WifiActivityModel?> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
index 1d414745e6ed..7acdaffb48c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
@@ -118,7 +118,10 @@ public final class DeviceStateRotationLockSettingController
private void updateDeviceState(int state) {
Log.v(TAG, "updateDeviceState [state=" + state + "]");
- Trace.beginSection("updateDeviceState [state=" + state + "]");
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(
+ Trace.TRACE_TAG_APP, "updateDeviceState [state=" + state + "]");
+ }
try {
if (mDeviceState == state) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index bd2123a251c3..69b55c81f48b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -33,6 +33,7 @@ import android.util.Log;
import androidx.annotation.NonNull;
import com.android.internal.util.ConcurrentUtils;
+import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -63,6 +64,7 @@ public class HotspotControllerImpl implements HotspotController, WifiManager.Sof
private volatile int mNumConnectedDevices;
// Assume tethering is available until told otherwise
private volatile boolean mIsTetheringSupported = true;
+ private final boolean mIsTetheringSupportedConfig;
private volatile boolean mHasTetherableWifiRegexs = true;
private boolean mWaitingForTerminalState;
@@ -100,23 +102,29 @@ public class HotspotControllerImpl implements HotspotController, WifiManager.Sof
mTetheringManager = context.getSystemService(TetheringManager.class);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
mMainHandler = mainHandler;
- mTetheringManager.registerTetheringEventCallback(
- new HandlerExecutor(backgroundHandler), mTetheringCallback);
+ mIsTetheringSupportedConfig = context.getResources()
+ .getBoolean(R.bool.config_show_wifi_tethering);
+ if (mIsTetheringSupportedConfig) {
+ mTetheringManager.registerTetheringEventCallback(
+ new HandlerExecutor(backgroundHandler), mTetheringCallback);
+ }
dumpManager.registerDumpable(getClass().getSimpleName(), this);
}
/**
* Whether hotspot is currently supported.
*
- * This will return {@code true} immediately on creation of the controller, but may be updated
- * later. Callbacks from this controllers will notify if the state changes.
+ * This may return {@code true} immediately on creation of the controller, but may be updated
+ * later as capabilities are collected from System Server.
+ *
+ * Callbacks from this controllers will notify if the state changes.
*
* @return {@code true} if hotspot is supported (or we haven't been told it's not)
* @see #addCallback
*/
@Override
public boolean isHotspotSupported() {
- return mIsTetheringSupported && mHasTetherableWifiRegexs
+ return mIsTetheringSupportedConfig && mIsTetheringSupported && mHasTetherableWifiRegexs
&& UserManager.get(mContext).isUserAdmin(ActivityManager.getCurrentUser());
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
index 07e5cf9d9df2..208061433325 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
@@ -28,6 +28,8 @@ import android.util.Log
import android.view.WindowManagerGlobal
import android.widget.Toast
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -58,6 +60,8 @@ constructor(
private val devicePolicyManager: DevicePolicyManager,
private val refreshUsersScheduler: RefreshUsersScheduler,
private val uiEventLogger: UiEventLogger,
+ resumeSessionReceiver: GuestResumeSessionReceiver,
+ resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver,
) {
/** Whether the device is configured to always have a guest user available. */
val isGuestUserAutoCreated: Boolean = repository.isGuestUserAutoCreated
@@ -65,6 +69,11 @@ constructor(
/** Whether the guest user is currently being reset. */
val isGuestUserResetting: Boolean = repository.isGuestUserResetting
+ init {
+ resumeSessionReceiver.register()
+ resetOrExitSessionReceiver.register()
+ }
+
/** Notifies that the device has finished booting. */
fun onDeviceBootCompleted() {
applicationScope.launch {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index aca60c033bac..131cf7d33e3a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -72,6 +72,7 @@ private fun fingerprintModel(user: Int) = KeyguardFingerprintListenModel(
keyguardOccluded = false,
occludingAppRequestingFp = false,
primaryUser = false,
+ shouldListenSfpsState = false,
shouldListenForFingerprintAssistant = false,
switchingUser = false,
udfps = false,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 52f8ef8b7ebc..0a2b3d8498c4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -194,6 +194,15 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
}
@Test
+ public void onInitConfiguresViewMode() {
+ mKeyguardSecurityContainerController.onInit();
+ verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
+ eq(mUserSwitcherController),
+ any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
+ eq(mFalsingA11yDelegate));
+ }
+
+ @Test
public void showSecurityScreen_canInflateAllModes() {
SecurityMode[] modes = SecurityMode.values();
for (SecurityMode mode : modes) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 5104f84f3bc6..a8284d29197d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -20,6 +20,7 @@ import static android.app.StatusBarManager.SESSION_KEYGUARD;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
+import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
@@ -38,6 +39,7 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -53,6 +55,7 @@ import android.app.trust.IStrongAuthTracker;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -60,18 +63,21 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
+import android.database.ContentObserver;
import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.biometrics.SensorProperties;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.net.Uri;
import android.nfc.NfcAdapter;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -110,6 +116,7 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
import org.junit.Assert;
@@ -181,6 +188,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
@Mock
+ private SecureSettings mSecureSettings;
+ @Mock
private TelephonyManager mTelephonyManager;
@Mock
private SensorPrivacyManager mSensorPrivacyManager;
@@ -214,6 +223,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
private GlobalSettings mGlobalSettings;
private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
+
private final int mCurrentUserId = 100;
private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
@@ -223,6 +233,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Captor
private ArgumentCaptor<FaceManager.AuthenticationCallback> mAuthenticationCallbackCaptor;
+ @Mock
+ private Uri mURI;
+
// Direct executor
private final Executor mBackgroundExecutor = Runnable::run;
private final Executor mMainExecutor = Runnable::run;
@@ -305,6 +318,15 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mTestableLooper = TestableLooper.get(this);
allowTestableLooperAsMainThread();
+
+ when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI);
+
+ final ContentResolver contentResolver = mContext.getContentResolver();
+ ExtendedMockito.spyOn(contentResolver);
+ doNothing().when(contentResolver)
+ .registerContentObserver(any(Uri.class), anyBoolean(), any(ContentObserver.class),
+ anyInt());
+
mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
verify(mBiometricManager)
@@ -1136,6 +1158,64 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
+ public void testStartsListeningForSfps_whenKeyguardIsVisible_ifRequireScreenOnToAuthEnabled()
+ throws RemoteException {
+ // SFPS supported and enrolled
+ final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
+ props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON));
+ when(mAuthController.getSfpsProps()).thenReturn(props);
+ when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+
+ // WHEN require screen on to auth is disabled, and keyguard is not awake
+ when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(0);
+ mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref();
+
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_requireScreenOnToAuthEnabled, true);
+
+ // Preconditions for sfps auth to run
+ keyguardNotGoingAway();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+
+ statusBarShadeIsLocked();
+ mTestableLooper.processAllMessages();
+
+ // THEN we should listen for sfps when screen off, because require screen on is disabled
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
+
+ // WHEN require screen on to auth is enabled, and keyguard is not awake
+ when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(1);
+ mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref();
+
+ // THEN we shouldn't listen for sfps when screen off, because require screen on is enabled
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse();
+
+ // Device now awake & keyguard is now interactive
+ deviceNotGoingToSleep();
+ deviceIsInteractive();
+ keyguardIsVisible();
+
+ // THEN we should listen for sfps when screen on, and require screen on is enabled
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
+ }
+
+
+ private FingerprintSensorPropertiesInternal newFingerprintSensorPropertiesInternal(
+ @FingerprintSensorProperties.SensorType int sensorType) {
+ return new FingerprintSensorPropertiesInternal(
+ 0 /* sensorId */,
+ SensorProperties.STRENGTH_STRONG,
+ 1 /* maxEnrollmentsPerUser */,
+ new ArrayList<ComponentInfoInternal>(),
+ sensorType,
+ true /* resetLockoutRequiresHardwareAuthToken */);
+ }
+
+ @Test
public void testShouldNotListenForUdfps_whenTrustEnabled() {
// GIVEN a "we should listen for udfps" state
mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
@@ -1804,7 +1884,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
protected TestableKeyguardUpdateMonitor(Context context) {
super(context,
TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
- mBroadcastDispatcher, mDumpManager,
+ mBroadcastDispatcher, mSecureSettings, mDumpManager,
mBackgroundExecutor, mMainExecutor,
mStatusBarStateController, mLockPatternUtils,
mAuthController, mTelephonyListenerManager,
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 a02dfa3935d7..a275c8d33751 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -87,6 +87,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
import com.android.systemui.util.concurrency.FakeExecution;
@@ -173,6 +174,9 @@ public class AuthControllerTest extends SysuiTestCase {
private DelayableExecutor mBackgroundExecutor;
private TestableAuthController mAuthController;
+ @Mock
+ private VibratorHelper mVibratorHelper;
+
@Before
public void setup() throws RemoteException {
mContextSpy = spy(mContext);
@@ -221,7 +225,8 @@ public class AuthControllerTest extends SysuiTestCase {
mAuthController = new TestableAuthController(mContextSpy, mExecution, mCommandQueue,
mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager,
- () -> mUdfpsController, () -> mSidefpsController, mStatusBarStateController);
+ () -> mUdfpsController, () -> mSidefpsController, mStatusBarStateController,
+ mVibratorHelper);
mAuthController.start();
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
@@ -246,11 +251,13 @@ public class AuthControllerTest extends SysuiTestCase {
// This test is sensitive to prior FingerprintManager interactions.
reset(mFingerprintManager);
+ when(mVibratorHelper.hasVibrator()).thenReturn(true);
+
// This test requires an uninitialized AuthController.
AuthController authController = new TestableAuthController(mContextSpy, mExecution,
mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
mFaceManager, () -> mUdfpsController, () -> mSidefpsController,
- mStatusBarStateController);
+ mStatusBarStateController, mVibratorHelper);
authController.start();
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
@@ -270,11 +277,13 @@ public class AuthControllerTest extends SysuiTestCase {
// This test is sensitive to prior FingerprintManager interactions.
reset(mFingerprintManager);
+ when(mVibratorHelper.hasVibrator()).thenReturn(true);
+
// This test requires an uninitialized AuthController.
AuthController authController = new TestableAuthController(mContextSpy, mExecution,
mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
mFaceManager, () -> mUdfpsController, () -> mSidefpsController,
- mStatusBarStateController);
+ mStatusBarStateController, mVibratorHelper);
authController.start();
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
@@ -928,12 +937,13 @@ public class AuthControllerTest extends SysuiTestCase {
FaceManager faceManager,
Provider<UdfpsController> udfpsControllerFactory,
Provider<SidefpsController> sidefpsControllerFactory,
- StatusBarStateController statusBarStateController) {
+ StatusBarStateController statusBarStateController,
+ VibratorHelper vibratorHelper) {
super(context, execution, commandQueue, activityTaskManager, windowManager,
fingerprintManager, faceManager, udfpsControllerFactory,
sidefpsControllerFactory, mDisplayManager, mWakefulnessLifecycle,
mUserManager, mLockPatternUtils, statusBarStateController,
- mInteractionJankMonitor, mHandler, mBackgroundExecutor);
+ mInteractionJankMonitor, mHandler, mBackgroundExecutor, vibratorHelper);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index c5a7de410eb0..c234178f8af3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -92,6 +92,12 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
@Mock
BouncerCallbackInteractor mBouncerCallbackInteractor;
+ @Mock
+ DreamOverlayAnimationsController mAnimationsController;
+
+ @Mock
+ DreamOverlayStateController mStateController;
+
DreamOverlayContainerViewController mController;
@Before
@@ -115,7 +121,9 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
MAX_BURN_IN_OFFSET,
BURN_IN_PROTECTION_UPDATE_INTERVAL,
MILLIS_UNTIL_FULL_JITTER,
- mBouncerCallbackInteractor);
+ mBouncerCallbackInteractor,
+ mAnimationsController,
+ mStateController);
}
@Test
@@ -188,4 +196,31 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
verify(mBlurUtils).blurRadiusOfRatio(1 - scaledFraction);
verify(mBlurUtils).applyBlur(mViewRoot, (int) blurRadius, false);
}
+
+ @Test
+ public void testStartDreamEntryAnimationsOnAttachedNonLowLight() {
+ when(mStateController.isLowLightActive()).thenReturn(false);
+
+ mController.onViewAttached();
+
+ verify(mAnimationsController).startEntryAnimations(mDreamOverlayContainerView);
+ verify(mAnimationsController, never()).cancelRunningEntryAnimations();
+ }
+
+ @Test
+ public void testNeverStartDreamEntryAnimationsOnAttachedForLowLight() {
+ when(mStateController.isLowLightActive()).thenReturn(true);
+
+ mController.onViewAttached();
+
+ verify(mAnimationsController, never()).startEntryAnimations(mDreamOverlayContainerView);
+ }
+
+ @Test
+ public void testCancelDreamEntryAnimationsOnDetached() {
+ mController.onViewAttached();
+ mController.onViewDetached();
+
+ verify(mAnimationsController).cancelRunningEntryAnimations();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index f370be190761..f04a37f4c3fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -253,6 +253,7 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED);
verify(mStateController).setOverlayActive(false);
verify(mStateController).setLowLightActive(false);
+ verify(mStateController).setEntryAnimationsFinished(false);
}
@Test
@@ -273,24 +274,28 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
@Test
public void testDecorViewNotAddedToWindowAfterDestroy() throws Exception {
- when(mDreamOverlayContainerView.getParent())
- .thenReturn(mDreamOverlayContainerViewParent)
- .thenReturn(null);
-
final IBinder proxy = mService.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+ // Destroy the service.
+ mService.onDestroy();
+ mMainExecutor.runAllReady();
+
// Inform the overlay service of dream starting.
overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
false /*shouldShowComplication*/);
+ mMainExecutor.runAllReady();
- // Destroy the service.
- mService.onDestroy();
+ verify(mWindowManager, never()).addView(any(), any());
+ }
- // Run executor tasks.
+ @Test
+ public void testNeverRemoveDecorViewIfNotAdded() {
+ // Service destroyed before dream started.
+ mService.onDestroy();
mMainExecutor.runAllReady();
- verify(mWindowManager, never()).addView(any(), any());
+ verify(mWindowManager, never()).removeView(any());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index d1d32a13589a..c21c7a2aacbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -234,4 +234,20 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {
verify(mCallback, times(1)).onStateChanged();
assertThat(stateController.isLowLightActive()).isTrue();
}
+
+ @Test
+ public void testNotifyEntryAnimationsFinishedChanged() {
+ final DreamOverlayStateController stateController =
+ new DreamOverlayStateController(mExecutor);
+
+ stateController.addCallback(mCallback);
+ mExecutor.runAllReady();
+ assertThat(stateController.areEntryAnimationsFinished()).isFalse();
+
+ stateController.setEntryAnimationsFinished(true);
+ mExecutor.runAllReady();
+
+ verify(mCallback, times(1)).onStateChanged();
+ assertThat(stateController.areEntryAnimationsFinished()).isTrue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index aa021781296a..85c28190d77b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -19,16 +19,20 @@ package com.android.systemui.dreams;
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AlarmManager;
+import android.content.Context;
import android.content.res.Resources;
import android.hardware.SensorPrivacyManager;
import android.net.ConnectivityManager;
@@ -55,6 +59,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -69,7 +74,7 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
"{count, plural, =1 {# notification} other {# notifications}}";
@Mock
- DreamOverlayStatusBarView mView;
+ MockDreamOverlayStatusBarView mView;
@Mock
ConnectivityManager mConnectivityManager;
@Mock
@@ -105,6 +110,9 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
@Mock
DreamOverlayStateController mDreamOverlayStateController;
+ @Captor
+ private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor;
+
private final Executor mMainExecutor = Runnable::run;
DreamOverlayStatusBarViewController mController;
@@ -115,6 +123,8 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
when(mResources.getString(R.string.dream_overlay_status_bar_notification_indicator))
.thenReturn(NOTIFICATION_INDICATOR_FORMATTER_STRING);
+ doCallRealMethod().when(mView).setVisibility(anyInt());
+ doCallRealMethod().when(mView).getVisibility();
mController = new DreamOverlayStatusBarViewController(
mView,
@@ -454,12 +464,10 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
public void testStatusBarHiddenWhenSystemStatusBarShown() {
mController.onViewAttached();
- final ArgumentCaptor<StatusBarWindowStateListener>
- callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
- verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
- callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_SHOWING);
+ updateEntryAnimationsFinished();
+ updateStatusBarWindowState(true);
- verify(mView).setVisibility(View.INVISIBLE);
+ assertThat(mView.getVisibility()).isEqualTo(View.INVISIBLE);
}
@Test
@@ -467,29 +475,43 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
mController.onViewAttached();
reset(mView);
- final ArgumentCaptor<StatusBarWindowStateListener>
- callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
- verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
- callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_HIDDEN);
+ updateEntryAnimationsFinished();
+ updateStatusBarWindowState(false);
- verify(mView).setVisibility(View.VISIBLE);
+ assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void testUnattachedStatusBarVisibilityUnchangedWhenSystemStatusBarHidden() {
mController.onViewAttached();
+ updateEntryAnimationsFinished();
mController.onViewDetached();
reset(mView);
- final ArgumentCaptor<StatusBarWindowStateListener>
- callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
- verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
- callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_SHOWING);
+ updateStatusBarWindowState(true);
verify(mView, never()).setVisibility(anyInt());
}
@Test
+ public void testNoChangeToVisibilityBeforeDreamStartedWhenStatusBarHidden() {
+ mController.onViewAttached();
+
+ // Trigger status bar window state change.
+ final StatusBarWindowStateListener listener = updateStatusBarWindowState(false);
+
+ // Verify no visibility change because dream not started.
+ verify(mView, never()).setVisibility(anyInt());
+
+ // Dream entry animations finished.
+ updateEntryAnimationsFinished();
+
+ // Trigger another status bar window state change, and verify visibility change.
+ listener.onStatusBarWindowStateChanged(WINDOW_STATE_HIDDEN);
+ assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
public void testExtraStatusBarItemSetWhenItemsChange() {
mController.onViewAttached();
when(mStatusBarItem.getView()).thenReturn(mStatusBarItemView);
@@ -507,16 +529,75 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
public void testLowLightHidesStatusBar() {
when(mDreamOverlayStateController.isLowLightActive()).thenReturn(true);
mController.onViewAttached();
+ updateEntryAnimationsFinished();
+
+ final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
+ callbackCapture.getValue().onStateChanged();
- verify(mView).setVisibility(View.INVISIBLE);
+ assertThat(mView.getVisibility()).isEqualTo(View.INVISIBLE);
reset(mView);
when(mDreamOverlayStateController.isLowLightActive()).thenReturn(false);
+ callbackCapture.getValue().onStateChanged();
+
+ assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void testNoChangeToVisibilityBeforeDreamStartedWhenLowLightStateChange() {
+ when(mDreamOverlayStateController.isLowLightActive()).thenReturn(false);
+ mController.onViewAttached();
+
+ // No change to visibility because dream not fully started.
+ verify(mView, never()).setVisibility(anyInt());
+
+ // Dream entry animations finished.
+ updateEntryAnimationsFinished();
+
+ // Trigger state change and verify visibility changed.
final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
callbackCapture.getValue().onStateChanged();
- verify(mView).setVisibility(View.VISIBLE);
+ assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ private StatusBarWindowStateListener updateStatusBarWindowState(boolean show) {
+ when(mStatusBarWindowStateController.windowIsShowing()).thenReturn(show);
+ final ArgumentCaptor<StatusBarWindowStateListener>
+ callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
+ verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
+ final StatusBarWindowStateListener listener = callbackCapture.getValue();
+ listener.onStatusBarWindowStateChanged(show ? WINDOW_STATE_SHOWING : WINDOW_STATE_HIDDEN);
+ return listener;
+ }
+
+ private void updateEntryAnimationsFinished() {
+ when(mDreamOverlayStateController.areEntryAnimationsFinished()).thenReturn(true);
+
+ verify(mDreamOverlayStateController).addCallback(mCallbackCaptor.capture());
+ final DreamOverlayStateController.Callback callback = mCallbackCaptor.getValue();
+ callback.onStateChanged();
+ }
+
+ private static class MockDreamOverlayStatusBarView extends DreamOverlayStatusBarView {
+ private int mVisibility = View.VISIBLE;
+
+ private MockDreamOverlayStatusBarView(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ mVisibility = visibility;
+ }
+
+ @Override
+ public int getVisibility() {
+ return mVisibility;
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
index 3b9e398141ee..b477592f8fbc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.dreams.complication;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -29,16 +30,18 @@ import androidx.lifecycle.Observer;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
@SmallTest
@@ -77,9 +80,20 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase {
@Mock
ComplicationLayoutParams mComplicationLayoutParams;
+ @Mock
+ DreamOverlayStateController mDreamOverlayStateController;
+
+ @Captor
+ private ArgumentCaptor<Observer<Collection<ComplicationViewModel>>> mObserverCaptor;
+
+ @Captor
+ private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor;
+
@Complication.Category
static final int COMPLICATION_CATEGORY = Complication.CATEGORY_SYSTEM;
+ private ComplicationHostViewController mController;
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
@@ -91,32 +105,28 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase {
when(mViewHolder.getCategory()).thenReturn(COMPLICATION_CATEGORY);
when(mViewHolder.getLayoutParams()).thenReturn(mComplicationLayoutParams);
when(mComplicationView.getParent()).thenReturn(mComplicationHostView);
- }
- /**
- * Ensures the lifecycle of complications is properly handled.
- */
- @Test
- public void testViewModelObservation() {
- final ArgumentCaptor<Observer<Collection<ComplicationViewModel>>> observerArgumentCaptor =
- ArgumentCaptor.forClass(Observer.class);
- final ComplicationHostViewController controller = new ComplicationHostViewController(
+ mController = new ComplicationHostViewController(
mComplicationHostView,
mLayoutEngine,
+ mDreamOverlayStateController,
mLifecycleOwner,
mViewModel);
- controller.init();
-
- verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner),
- observerArgumentCaptor.capture());
+ mController.init();
+ }
+ /**
+ * Ensures the lifecycle of complications is properly handled.
+ */
+ @Test
+ public void testViewModelObservation() {
final Observer<Collection<ComplicationViewModel>> observer =
- observerArgumentCaptor.getValue();
+ captureComplicationViewModelsObserver();
- // Add complication and ensure it is added to the view.
+ // Add a complication and ensure it is added to the view.
final HashSet<ComplicationViewModel> complications = new HashSet<>(
- Arrays.asList(mComplicationViewModel));
+ Collections.singletonList(mComplicationViewModel));
observer.onChanged(complications);
verify(mLayoutEngine).addComplication(eq(mComplicationId), eq(mComplicationView),
@@ -127,4 +137,48 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase {
verify(mLayoutEngine).removeComplication(eq(mComplicationId));
}
+
+ @Test
+ public void testNewComplicationsBeforeEntryAnimationsFinishSetToInvisible() {
+ final Observer<Collection<ComplicationViewModel>> observer =
+ captureComplicationViewModelsObserver();
+
+ // Add a complication before entry animations are finished.
+ final HashSet<ComplicationViewModel> complications = new HashSet<>(
+ Collections.singletonList(mComplicationViewModel));
+ observer.onChanged(complications);
+
+ // The complication view should be set to invisible.
+ verify(mComplicationView).setVisibility(View.INVISIBLE);
+ }
+
+ @Test
+ public void testNewComplicationsAfterEntryAnimationsFinishNotSetToInvisible() {
+ final Observer<Collection<ComplicationViewModel>> observer =
+ captureComplicationViewModelsObserver();
+
+ // Dream entry animations finished.
+ when(mDreamOverlayStateController.areEntryAnimationsFinished()).thenReturn(true);
+ final DreamOverlayStateController.Callback stateCallback = captureOverlayStateCallback();
+ stateCallback.onStateChanged();
+
+ // Add a complication after entry animations are finished.
+ final HashSet<ComplicationViewModel> complications = new HashSet<>(
+ Collections.singletonList(mComplicationViewModel));
+ observer.onChanged(complications);
+
+ // The complication view should not be set to invisible.
+ verify(mComplicationView, never()).setVisibility(View.INVISIBLE);
+ }
+
+ private Observer<Collection<ComplicationViewModel>> captureComplicationViewModelsObserver() {
+ verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner),
+ mObserverCaptor.capture());
+ return mObserverCaptor.getValue();
+ }
+
+ private DreamOverlayStateController.Callback captureOverlayStateCallback() {
+ verify(mDreamOverlayStateController).addCallback(mCallbackCaptor.capture());
+ return mCallbackCaptor.getValue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java
deleted file mode 100644
index 640e6dc0461d..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2017 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.systemui.keyguard;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.UserIdInt;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.os.UserManager;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * runtest systemui -c com.android.systemui.keyguard.WorkLockActivityTest
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class WorkLockActivityTest extends SysuiTestCase {
- private static final @UserIdInt int USER_ID = 270;
- private static final String CALLING_PACKAGE_NAME = "com.android.test";
-
- private @Mock UserManager mUserManager;
- private @Mock PackageManager mPackageManager;
- private @Mock Context mContext;
- private @Mock BroadcastDispatcher mBroadcastDispatcher;
- private @Mock Drawable mDrawable;
- private @Mock Drawable mBadgedDrawable;
-
- private WorkLockActivity mActivity;
-
- private static class WorkLockActivityTestable extends WorkLockActivity {
- WorkLockActivityTestable(Context baseContext, BroadcastDispatcher broadcastDispatcher,
- UserManager userManager, PackageManager packageManager) {
- super(broadcastDispatcher, userManager, packageManager);
- attachBaseContext(baseContext);
- }
- }
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
- mActivity = new WorkLockActivityTestable(mContext, mBroadcastDispatcher, mUserManager,
- mPackageManager);
- }
-
- @Test
- public void testGetBadgedIcon() throws Exception {
- ApplicationInfo info = new ApplicationInfo();
- when(mPackageManager.getApplicationInfoAsUser(eq(CALLING_PACKAGE_NAME), any(),
- eq(USER_ID))).thenReturn(info);
- when(mPackageManager.getApplicationIcon(eq(info))).thenReturn(mDrawable);
- when(mUserManager.getBadgedIconForUser(any(), eq(UserHandle.of(USER_ID)))).thenReturn(
- mBadgedDrawable);
- mActivity.setIntent(new Intent()
- .putExtra(Intent.EXTRA_USER_ID, USER_ID)
- .putExtra(Intent.EXTRA_PACKAGE_NAME, CALLING_PACKAGE_NAME));
-
- assertEquals(mBadgedDrawable, mActivity.getBadgedIcon());
- }
-
- @Test
- public void testUnregisteredFromDispatcher() {
- mActivity.unregisterBroadcastReceiver();
- verify(mBroadcastDispatcher).unregisterReceiver(any());
- verify(mContext, never()).unregisterReceiver(any());
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt
new file mode 100644
index 000000000000..c7f1decac670
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2017 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.systemui.keyguard
+
+import android.annotation.UserIdInt
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ApplicationInfoFlags
+import android.graphics.drawable.Drawable
+import android.os.Looper
+import android.os.UserHandle
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+/** runtest systemui -c com.android.systemui.keyguard.WorkLockActivityTest */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WorkLockActivityTest : SysuiTestCase() {
+ private val context: Context = mock()
+ private val userManager: UserManager = mock()
+ private val packageManager: PackageManager = mock()
+ private val broadcastDispatcher: BroadcastDispatcher = mock()
+ private val drawable: Drawable = mock()
+ private val badgedDrawable: Drawable = mock()
+ private lateinit var activity: WorkLockActivity
+
+ private class WorkLockActivityTestable
+ constructor(
+ baseContext: Context,
+ broadcastDispatcher: BroadcastDispatcher,
+ userManager: UserManager,
+ packageManager: PackageManager,
+ ) : WorkLockActivity(broadcastDispatcher, userManager, packageManager) {
+ init {
+ attachBaseContext(baseContext)
+ }
+ }
+
+ @Before
+ fun setUp() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare()
+ }
+ activity =
+ WorkLockActivityTestable(
+ baseContext = context,
+ broadcastDispatcher = broadcastDispatcher,
+ userManager = userManager,
+ packageManager = packageManager
+ )
+ }
+
+ @Test
+ fun testGetBadgedIcon() {
+ val info = ApplicationInfo()
+ whenever(
+ packageManager.getApplicationInfoAsUser(
+ eq(CALLING_PACKAGE_NAME),
+ any<ApplicationInfoFlags>(),
+ eq(USER_ID)
+ )
+ )
+ .thenReturn(info)
+ whenever(packageManager.getApplicationIcon(ArgumentMatchers.eq(info))).thenReturn(drawable)
+ whenever(userManager.getBadgedIconForUser(any(), eq(UserHandle.of(USER_ID))))
+ .thenReturn(badgedDrawable)
+ activity.intent =
+ Intent()
+ .putExtra(Intent.EXTRA_USER_ID, USER_ID)
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, CALLING_PACKAGE_NAME)
+ assertEquals(badgedDrawable, activity.badgedIcon)
+ }
+
+ @Test
+ fun testUnregisteredFromDispatcher() {
+ activity.unregisterBroadcastReceiver()
+ verify(broadcastDispatcher).unregisterReceiver(any())
+ verify(context, never()).unregisterReceiver(nullable())
+ }
+
+ @Test
+ fun onBackPressed_finishActivity() {
+ assertFalse(activity.isFinishing)
+
+ activity.onBackPressed()
+
+ assertFalse(activity.isFinishing)
+ }
+
+ companion object {
+ @UserIdInt private val USER_ID = 270
+ private const val CALLING_PACKAGE_NAME = "com.android.test"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
index f18acbad14f2..0fb181d41484 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
@@ -23,14 +23,11 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.yield
-/**
- * Fake implementation of a quick affordance data source.
- *
- * This class is abstract to force tests to provide extensions of it as the system that references
- * these configs uses each implementation's class type to refer to them.
- */
-abstract class FakeKeyguardQuickAffordanceConfig(
+/** Fake implementation of a quick affordance data source. */
+class FakeKeyguardQuickAffordanceConfig(
override val key: String,
+ override val pickerName: String = key,
+ override val pickerIconResourceId: Int = 0,
) : KeyguardQuickAffordanceConfig {
var onTriggeredResult: OnTriggeredResult = OnTriggeredResult.Handled
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
new file mode 100644
index 000000000000..d2422ad7b53b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
@@ -0,0 +1,127 @@
+/*
+ * 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.systemui.keyguard.data.quickaffordance
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() {
+
+ private lateinit var underTest: KeyguardQuickAffordanceSelectionManager
+
+ @Before
+ fun setUp() {
+ underTest = KeyguardQuickAffordanceSelectionManager()
+ }
+
+ @Test
+ fun setSelections() =
+ runBlocking(IMMEDIATE) {
+ var affordanceIdsBySlotId: Map<String, List<String>>? = null
+ val job = underTest.selections.onEach { affordanceIdsBySlotId = it }.launchIn(this)
+ val slotId1 = "slot1"
+ val slotId2 = "slot2"
+ val affordanceId1 = "affordance1"
+ val affordanceId2 = "affordance2"
+ val affordanceId3 = "affordance3"
+
+ underTest.setSelections(
+ slotId = slotId1,
+ affordanceIds = listOf(affordanceId1),
+ )
+ assertSelections(
+ affordanceIdsBySlotId,
+ mapOf(
+ slotId1 to listOf(affordanceId1),
+ ),
+ )
+
+ underTest.setSelections(
+ slotId = slotId2,
+ affordanceIds = listOf(affordanceId2),
+ )
+ assertSelections(
+ affordanceIdsBySlotId,
+ mapOf(
+ slotId1 to listOf(affordanceId1),
+ slotId2 to listOf(affordanceId2),
+ )
+ )
+
+ underTest.setSelections(
+ slotId = slotId1,
+ affordanceIds = listOf(affordanceId1, affordanceId3),
+ )
+ assertSelections(
+ affordanceIdsBySlotId,
+ mapOf(
+ slotId1 to listOf(affordanceId1, affordanceId3),
+ slotId2 to listOf(affordanceId2),
+ )
+ )
+
+ underTest.setSelections(
+ slotId = slotId1,
+ affordanceIds = listOf(affordanceId3),
+ )
+ assertSelections(
+ affordanceIdsBySlotId,
+ mapOf(
+ slotId1 to listOf(affordanceId3),
+ slotId2 to listOf(affordanceId2),
+ )
+ )
+
+ underTest.setSelections(
+ slotId = slotId2,
+ affordanceIds = listOf(),
+ )
+ assertSelections(
+ affordanceIdsBySlotId,
+ mapOf(
+ slotId1 to listOf(affordanceId3),
+ slotId2 to listOf(),
+ )
+ )
+
+ job.cancel()
+ }
+
+ private suspend fun assertSelections(
+ observed: Map<String, List<String>>?,
+ expected: Map<String, List<String>>,
+ ) {
+ assertThat(underTest.getSelections()).isEqualTo(expected)
+ assertThat(observed).isEqualTo(expected)
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index 61a3f9f07600..2bd8e9aabab3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -50,7 +50,7 @@ class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
whenever(controller.intent).thenReturn(INTENT_1)
- underTest = QrCodeScannerKeyguardQuickAffordanceConfig(controller)
+ underTest = QrCodeScannerKeyguardQuickAffordanceConfig(mock(), controller)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index c05beef6d624..5178154bdeee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -59,6 +59,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
underTest =
QuickAccessWalletKeyguardQuickAffordanceConfig(
+ mock(),
walletController,
activityStarter,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
new file mode 100644
index 000000000000..5a7f2bb5cb37
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -0,0 +1,152 @@
+/*
+ * 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.systemui.keyguard.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
+import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() {
+
+ private lateinit var underTest: KeyguardQuickAffordanceRepository
+
+ private lateinit var config1: FakeKeyguardQuickAffordanceConfig
+ private lateinit var config2: FakeKeyguardQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ config1 = FakeKeyguardQuickAffordanceConfig("built_in:1")
+ config2 = FakeKeyguardQuickAffordanceConfig("built_in:2")
+ underTest =
+ KeyguardQuickAffordanceRepository(
+ scope = CoroutineScope(IMMEDIATE),
+ backgroundDispatcher = IMMEDIATE,
+ selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ configs = setOf(config1, config2),
+ )
+ }
+
+ @Test
+ fun setSelections() =
+ runBlocking(IMMEDIATE) {
+ var configsBySlotId: Map<String, List<KeyguardQuickAffordanceConfig>>? = null
+ val job = underTest.selections.onEach { configsBySlotId = it }.launchIn(this)
+ val slotId1 = "slot1"
+ val slotId2 = "slot2"
+
+ underTest.setSelections(slotId1, listOf(config1.key))
+ assertSelections(
+ configsBySlotId,
+ mapOf(
+ slotId1 to listOf(config1),
+ ),
+ )
+
+ underTest.setSelections(slotId2, listOf(config2.key))
+ assertSelections(
+ configsBySlotId,
+ mapOf(
+ slotId1 to listOf(config1),
+ slotId2 to listOf(config2),
+ ),
+ )
+
+ underTest.setSelections(slotId1, emptyList())
+ underTest.setSelections(slotId2, listOf(config1.key))
+ assertSelections(
+ configsBySlotId,
+ mapOf(
+ slotId1 to emptyList(),
+ slotId2 to listOf(config1),
+ ),
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun getAffordancePickerRepresentations() {
+ assertThat(underTest.getAffordancePickerRepresentations())
+ .isEqualTo(
+ listOf(
+ KeyguardQuickAffordancePickerRepresentation(
+ id = config1.key,
+ name = config1.pickerName,
+ iconResourceId = config1.pickerIconResourceId,
+ ),
+ KeyguardQuickAffordancePickerRepresentation(
+ id = config2.key,
+ name = config2.pickerName,
+ iconResourceId = config2.pickerIconResourceId,
+ ),
+ )
+ )
+ }
+
+ @Test
+ fun getSlotPickerRepresentations() {
+ assertThat(underTest.getSlotPickerRepresentations())
+ .isEqualTo(
+ listOf(
+ KeyguardSlotPickerRepresentation(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ maxSelectedAffordances = 1,
+ ),
+ KeyguardSlotPickerRepresentation(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ maxSelectedAffordances = 1,
+ ),
+ )
+ )
+ }
+
+ private suspend fun assertSelections(
+ observed: Map<String, List<KeyguardQuickAffordanceConfig>>?,
+ expected: Map<String, List<KeyguardQuickAffordanceConfig>>,
+ ) {
+ assertThat(observed).isEqualTo(expected)
+ assertThat(underTest.getSelections())
+ .isEqualTo(expected.mapValues { (_, configs) -> configs.map { it.key } })
+ expected.forEach { (slotId, configs) ->
+ assertThat(underTest.getSelections(slotId)).isEqualTo(configs)
+ }
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index b9ab9d37a805..53d9b87b2346 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -21,8 +21,10 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Position
import com.android.systemui.doze.DozeHost
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
@@ -46,6 +48,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var dozeHost: DozeHost
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock private lateinit var biometricUnlockController: BiometricUnlockController
private lateinit var underTest: KeyguardRepositoryImpl
@@ -59,6 +62,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
keyguardStateController,
dozeHost,
wakefulnessLifecycle,
+ biometricUnlockController,
)
}
@@ -190,7 +194,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
}
@Test
- fun wakefullness() = runBlockingTest {
+ fun wakefulness() = runBlockingTest {
val values = mutableListOf<WakefulnessModel>()
val job = underTest.wakefulnessState.onEach(values::add).launchIn(this)
@@ -217,4 +221,63 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
job.cancel()
verify(wakefulnessLifecycle).removeObserver(captor.value)
}
+
+ @Test
+ fun isBouncerShowing() = runBlockingTest {
+ whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
+ var latest: Boolean? = null
+ val job = underTest.isBouncerShowing.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ val captor = argumentCaptor<KeyguardStateController.Callback>()
+ verify(keyguardStateController).addCallback(captor.capture())
+
+ whenever(keyguardStateController.isBouncerShowing).thenReturn(true)
+ captor.value.onBouncerShowingChanged()
+ assertThat(latest).isTrue()
+
+ whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
+ captor.value.onBouncerShowingChanged()
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun biometricUnlockState() = runBlockingTest {
+ val values = mutableListOf<BiometricUnlockModel>()
+ val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this)
+
+ val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>()
+ verify(biometricUnlockController).addBiometricModeListener(captor.capture())
+
+ captor.value.onModeChanged(BiometricUnlockController.MODE_NONE)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_SHOW_BOUNCER)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_ONLY_WAKE)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_UNLOCK_COLLAPSING)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_DISMISS_BOUNCER)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
+
+ assertThat(values)
+ .isEqualTo(
+ listOf(
+ // Initial value will be NONE, followed by onModeChanged() call
+ BiometricUnlockModel.NONE,
+ BiometricUnlockModel.NONE,
+ BiometricUnlockModel.WAKE_AND_UNLOCK,
+ BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING,
+ BiometricUnlockModel.SHOW_BOUNCER,
+ BiometricUnlockModel.ONLY_WAKE,
+ BiometricUnlockModel.UNLOCK_COLLAPSING,
+ BiometricUnlockModel.DISMISS_BOUNCER,
+ BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM,
+ )
+ )
+
+ job.cancel()
+ verify(biometricUnlockController).removeBiometricModeListener(captor.value)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 64913c7c28c2..27d5d0a98978 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -128,11 +128,15 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
assertThat(steps.size).isEqualTo(3)
assertThat(steps[0])
- .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME))
assertThat(steps[1])
- .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING))
+ .isEqualTo(
+ TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING, OWNER_NAME)
+ )
assertThat(steps[2])
- .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
+ .isEqualTo(
+ TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME)
+ )
job.cancel()
}
@@ -174,15 +178,22 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
}
private fun assertSteps(steps: List<TransitionStep>, fractions: List<BigDecimal>) {
- assertThat(steps[0]).isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
+ assertThat(steps[0])
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME))
fractions.forEachIndexed { index, fraction ->
assertThat(steps[index + 1])
.isEqualTo(
- TransitionStep(AOD, LOCKSCREEN, fraction.toFloat(), TransitionState.RUNNING)
+ TransitionStep(
+ AOD,
+ LOCKSCREEN,
+ fraction.toFloat(),
+ TransitionState.RUNNING,
+ OWNER_NAME
+ )
)
}
assertThat(steps[steps.size - 1])
- .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME))
assertThat(wtfHandler.failed).isFalse()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 7116cc101d3f..8b6603d79244 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -25,10 +25,14 @@ import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
@@ -37,6 +41,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.runBlockingTest
import org.junit.Before
import org.junit.Test
@@ -189,6 +195,8 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
/* startActivity= */ true,
),
)
+
+ private val IMMEDIATE = Dispatchers.Main.immediate
}
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@@ -213,10 +221,20 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
whenever(expandable.activityLaunchController()).thenReturn(animationController)
homeControls =
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
- ) {}
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
+ val quickAccessWallet =
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ )
+ val qrCodeScanner =
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
+ val quickAffordanceRepository =
+ KeyguardQuickAffordanceRepository(
+ scope = CoroutineScope(IMMEDIATE),
+ backgroundDispatcher = IMMEDIATE,
+ selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
+ )
underTest =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = KeyguardInteractor(repository = FakeKeyguardRepository()),
@@ -229,14 +247,8 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
),
KeyguardQuickAffordancePosition.BOTTOM_END to
listOf(
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
- ) {},
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
- ) {},
+ quickAccessWallet,
+ qrCodeScanner,
),
),
),
@@ -244,6 +256,11 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
keyguardStateController = keyguardStateController,
userTracker = userTracker,
activityStarter = activityStarter,
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
+ },
+ repository = { quickAffordanceRepository },
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index ae32ba6676be..33645354d9f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -22,22 +22,31 @@ import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runBlockingTest
import kotlinx.coroutines.yield
import org.junit.Before
@@ -47,6 +56,7 @@ import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
@@ -62,6 +72,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
private lateinit var qrCodeScanner: FakeKeyguardQuickAffordanceConfig
+ private lateinit var featureFlags: FakeFeatureFlags
@Before
fun setUp() {
@@ -71,20 +82,25 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
repository.setKeyguardShowing(true)
homeControls =
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
- ) {}
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
quickAccessWallet =
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
- ) {}
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ )
qrCodeScanner =
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
- ) {}
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
+
+ val quickAffordanceRepository =
+ KeyguardQuickAffordanceRepository(
+ scope = CoroutineScope(IMMEDIATE),
+ backgroundDispatcher = IMMEDIATE,
+ selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
+ )
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
+ }
underTest =
KeyguardQuickAffordanceInteractor(
@@ -107,6 +123,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
keyguardStateController = keyguardStateController,
userTracker = userTracker,
activityStarter = activityStarter,
+ featureFlags = featureFlags,
+ repository = { quickAffordanceRepository },
)
}
@@ -210,6 +228,270 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
job.cancel()
}
+ @Test
+ fun select() =
+ runBlocking(IMMEDIATE) {
+ featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
+ homeControls.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+ quickAccessWallet.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+ qrCodeScanner.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf<String, List<String>>(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+ )
+ )
+
+ var startConfig: KeyguardQuickAffordanceModel? = null
+ val job1 =
+ underTest
+ .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ .onEach { startConfig = it }
+ .launchIn(this)
+ var endConfig: KeyguardQuickAffordanceModel? = null
+ val job2 =
+ underTest
+ .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { endConfig = it }
+ .launchIn(this)
+
+ underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
+ yield()
+ yield()
+ assertThat(startConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Visible(
+ configKey =
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START +
+ "::${homeControls.key}",
+ icon = ICON,
+ activationState = ActivationState.NotSupported,
+ )
+ )
+ assertThat(endConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Hidden,
+ )
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+ listOf(homeControls.key),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+ )
+ )
+
+ underTest.select(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ quickAccessWallet.key
+ )
+ yield()
+ yield()
+ assertThat(startConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Visible(
+ configKey =
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START +
+ "::${quickAccessWallet.key}",
+ icon = ICON,
+ activationState = ActivationState.NotSupported,
+ )
+ )
+ assertThat(endConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Hidden,
+ )
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+ listOf(quickAccessWallet.key),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+ )
+ )
+
+ underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, qrCodeScanner.key)
+ yield()
+ yield()
+ assertThat(startConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Visible(
+ configKey =
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START +
+ "::${quickAccessWallet.key}",
+ icon = ICON,
+ activationState = ActivationState.NotSupported,
+ )
+ )
+ assertThat(endConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Visible(
+ configKey =
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END +
+ "::${qrCodeScanner.key}",
+ icon = ICON,
+ activationState = ActivationState.NotSupported,
+ )
+ )
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+ listOf(quickAccessWallet.key),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
+ listOf(qrCodeScanner.key),
+ )
+ )
+
+ job1.cancel()
+ job2.cancel()
+ }
+
+ @Test
+ fun `unselect - one`() =
+ runBlocking(IMMEDIATE) {
+ featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
+ homeControls.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+ quickAccessWallet.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+ qrCodeScanner.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+
+ var startConfig: KeyguardQuickAffordanceModel? = null
+ val job1 =
+ underTest
+ .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ .onEach { startConfig = it }
+ .launchIn(this)
+ var endConfig: KeyguardQuickAffordanceModel? = null
+ val job2 =
+ underTest
+ .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { endConfig = it }
+ .launchIn(this)
+ underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
+ yield()
+ yield()
+ underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, quickAccessWallet.key)
+ yield()
+ yield()
+
+ underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
+ yield()
+ yield()
+
+ assertThat(startConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Hidden,
+ )
+ assertThat(endConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Visible(
+ configKey =
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END +
+ "::${quickAccessWallet.key}",
+ icon = ICON,
+ activationState = ActivationState.NotSupported,
+ )
+ )
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
+ listOf(quickAccessWallet.key),
+ )
+ )
+
+ underTest.unselect(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ quickAccessWallet.key
+ )
+ yield()
+ yield()
+
+ assertThat(startConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Hidden,
+ )
+ assertThat(endConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Hidden,
+ )
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf<String, List<String>>(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+ )
+ )
+
+ job1.cancel()
+ job2.cancel()
+ }
+
+ @Test
+ fun `unselect - all`() =
+ runBlocking(IMMEDIATE) {
+ featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
+ homeControls.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+ quickAccessWallet.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+ qrCodeScanner.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+
+ underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
+ yield()
+ yield()
+ underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, quickAccessWallet.key)
+ yield()
+ yield()
+
+ underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, null)
+ yield()
+ yield()
+
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
+ listOf(quickAccessWallet.key),
+ )
+ )
+
+ underTest.unselect(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ null,
+ )
+ yield()
+ yield()
+
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf<String, List<String>>(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+ )
+ )
+ }
+
companion object {
private val ICON: Icon = mock {
whenever(this.contentDescription)
@@ -220,5 +502,6 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
)
}
private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+ private val IMMEDIATE = Dispatchers.Main.immediate
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 0424c28a1c24..6333b244fd38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -54,9 +54,11 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() {
@Test
fun `transition collectors receives only appropriate events`() =
runBlocking(IMMEDIATE) {
- var goneToAodSteps = mutableListOf<TransitionStep>()
+ var lockscreenToAodSteps = mutableListOf<TransitionStep>()
val job1 =
- underTest.goneToAodTransition.onEach { goneToAodSteps.add(it) }.launchIn(this)
+ underTest.lockscreenToAodTransition
+ .onEach { lockscreenToAodSteps.add(it) }
+ .launchIn(this)
var aodToLockscreenSteps = mutableListOf<TransitionStep>()
val job2 =
@@ -70,14 +72,14 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() {
steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
- steps.add(TransitionStep(GONE, AOD, 0f, STARTED))
- steps.add(TransitionStep(GONE, AOD, 0.1f, RUNNING))
- steps.add(TransitionStep(GONE, AOD, 0.2f, RUNNING))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0.1f, RUNNING))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0.2f, RUNNING))
steps.forEach { repository.sendTransitionStep(it) }
assertThat(aodToLockscreenSteps).isEqualTo(steps.subList(2, 5))
- assertThat(goneToAodSteps).isEqualTo(steps.subList(5, 8))
+ assertThat(lockscreenToAodSteps).isEqualTo(steps.subList(5, 8))
job1.cancel()
job2.cancel()
@@ -119,10 +121,8 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() {
fun keyguardStateTests() =
runBlocking(IMMEDIATE) {
var finishedSteps = mutableListOf<KeyguardState>()
- val job1 =
+ val job =
underTest.finishedKeyguardState.onEach { finishedSteps.add(it) }.launchIn(this)
- var startedSteps = mutableListOf<KeyguardState>()
- val job2 = underTest.startedKeyguardState.onEach { startedSteps.add(it) }.launchIn(this)
val steps = mutableListOf<TransitionStep>()
@@ -137,10 +137,60 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() {
steps.forEach { repository.sendTransitionStep(it) }
assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, AOD))
- assertThat(startedSteps).isEqualTo(listOf(LOCKSCREEN, AOD, GONE))
- job1.cancel()
- job2.cancel()
+ job.cancel()
+ }
+
+ @Test
+ fun finishedKeyguardTransitionStepTests() =
+ runBlocking(IMMEDIATE) {
+ var finishedSteps = mutableListOf<TransitionStep>()
+ val job =
+ underTest.finishedKeyguardTransitionStep
+ .onEach { finishedSteps.add(it) }
+ .launchIn(this)
+
+ val steps = mutableListOf<TransitionStep>()
+
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+ steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+ steps.forEach { repository.sendTransitionStep(it) }
+
+ assertThat(finishedSteps).isEqualTo(listOf(steps[2], steps[5]))
+
+ job.cancel()
+ }
+
+ @Test
+ fun startedKeyguardTransitionStepTests() =
+ runBlocking(IMMEDIATE) {
+ var startedSteps = mutableListOf<TransitionStep>()
+ val job =
+ underTest.startedKeyguardTransitionStep
+ .onEach { startedSteps.add(it) }
+ .launchIn(this)
+
+ val steps = mutableListOf<TransitionStep>()
+
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+ steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+ steps.forEach { repository.sendTransitionStep(it) }
+
+ assertThat(startedSteps).isEqualTo(listOf(steps[0], steps[3], steps[6]))
+
+ job.cancel()
}
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index f73d1ecf9373..78148c4d3d1b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -23,10 +23,14 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
@@ -41,6 +45,8 @@ import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlin.math.max
import kotlin.math.min
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.runBlockingTest
@@ -82,20 +88,13 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
.thenReturn(RETURNED_BURN_IN_OFFSET)
homeControlsQuickAffordanceConfig =
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
- ) {}
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
quickAccessWalletAffordanceConfig =
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
- ) {}
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ )
qrCodeScannerAffordanceConfig =
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
- ) {}
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
registry =
FakeKeyguardQuickAffordanceRegistry(
mapOf(
@@ -116,6 +115,18 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
whenever(userTracker.userHandle).thenReturn(mock())
whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
+ val quickAffordanceRepository =
+ KeyguardQuickAffordanceRepository(
+ scope = CoroutineScope(IMMEDIATE),
+ backgroundDispatcher = IMMEDIATE,
+ selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ configs =
+ setOf(
+ homeControlsQuickAffordanceConfig,
+ quickAccessWalletAffordanceConfig,
+ qrCodeScannerAffordanceConfig,
+ ),
+ )
underTest =
KeyguardBottomAreaViewModel(
keyguardInteractor = keyguardInteractor,
@@ -127,6 +138,11 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
keyguardStateController = keyguardStateController,
userTracker = userTracker,
activityStarter = activityStarter,
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
+ },
+ repository = { quickAffordanceRepository },
),
bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
burnInHelperWrapper = burnInHelperWrapper,
@@ -576,5 +592,6 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
companion object {
private const val DEFAULT_BURN_IN_OFFSET = 5
private const val RETURNED_BURN_IN_OFFSET = 3
+ private val IMMEDIATE = Dispatchers.Main.immediate
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index f96c39f007dd..aa1114b8736e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -34,6 +34,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
+import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
@@ -86,6 +87,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
private val mRemoteInputManager: NotificationRemoteInputManager = mock()
private val mEndLifetimeExtension: OnEndLifetimeExtensionCallback = mock()
private val mHeaderController: NodeController = mock()
+ private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider = mock()
private lateinit var mEntry: NotificationEntry
private lateinit var mGroupSummary: NotificationEntry
@@ -110,6 +112,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
mHeadsUpViewBinder,
mNotificationInterruptStateProvider,
mRemoteInputManager,
+ mLaunchFullScreenIntentProvider,
mHeaderController,
mExecutor)
mCoordinator.attach(mNotifPipeline)
@@ -242,6 +245,20 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
}
@Test
+ fun testOnEntryAdded_shouldFullScreen() {
+ setShouldFullScreen(mEntry)
+ mCollectionListener.onEntryAdded(mEntry)
+ verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
+ }
+
+ @Test
+ fun testOnEntryAdded_shouldNotFullScreen() {
+ setShouldFullScreen(mEntry, should = false)
+ mCollectionListener.onEntryAdded(mEntry)
+ verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+ }
+
+ @Test
fun testPromotesAddedHUN() {
// GIVEN the current entry should heads up
whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true)
@@ -794,6 +811,11 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
.thenReturn(should)
}
+ private fun setShouldFullScreen(entry: NotificationEntry, should: Boolean = true) {
+ whenever(mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .thenReturn(should)
+ }
+
private fun finishBind(entry: NotificationEntry) {
verify(mHeadsUpManager, never()).showNotification(entry)
withArgCaptor<BindCallback> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 6fa217415044..b17747ad2d92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -143,7 +143,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController,
mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
- mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener);
+ mBiometricUnlockController.addBiometricModeListener(mBiometricModeListener);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 9c56c2670c63..6fb68938b00d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -45,7 +45,7 @@ import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconStat
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
+import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import org.junit.Before;
@@ -80,7 +80,7 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
layout,
StatusBarLocation.HOME,
mock(StatusBarPipelineFlags.class),
- mock(WifiViewModel.class),
+ mock(WifiUiAdapter.class),
mock(MobileUiAdapter.class),
mMobileContextProvider,
mock(DarkIconDispatcher.class));
@@ -124,14 +124,14 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
LinearLayout group,
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
- WifiViewModel wifiViewModel,
+ WifiUiAdapter wifiUiAdapter,
MobileUiAdapter mobileUiAdapter,
MobileContextProvider contextProvider,
DarkIconDispatcher darkIconDispatcher) {
super(group,
location,
statusBarPipelineFlags,
- wifiViewModel,
+ wifiUiAdapter,
mobileUiAdapter,
contextProvider,
darkIconDispatcher);
@@ -172,7 +172,7 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
super(group,
StatusBarLocation.HOME,
mock(StatusBarPipelineFlags.class),
- mock(WifiViewModel.class),
+ mock(WifiUiAdapter.class),
mock(MobileUiAdapter.class),
contextProvider);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index c4098578197b..ce54d784520c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -67,8 +67,8 @@ import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -123,8 +123,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
@Mock
private ShadeControllerImpl mShadeController;
@Mock
- private NotifPipeline mNotifPipeline;
- @Mock
private NotificationVisibilityProvider mVisibilityProvider;
@Mock
private ActivityIntentHelper mActivityIntentHelper;
@@ -197,7 +195,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
getContext(),
mHandler,
mUiBgExecutor,
- mNotifPipeline,
mVisibilityProvider,
headsUpManager,
mActivityStarter,
@@ -222,7 +219,8 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
mock(NotificationPresenter.class),
mock(NotificationPanelViewController.class),
mActivityLaunchAnimator,
- notificationAnimationProvider
+ notificationAnimationProvider,
+ mock(LaunchFullScreenIntentProvider.class)
);
// set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg
@@ -384,11 +382,9 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
NotificationEntry entry = mock(NotificationEntry.class);
when(entry.getImportance()).thenReturn(NotificationManager.IMPORTANCE_HIGH);
when(entry.getSbn()).thenReturn(sbn);
- when(mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(eq(entry)))
- .thenReturn(true);
// WHEN
- mNotificationActivityStarter.handleFullScreenIntent(entry);
+ mNotificationActivityStarter.launchFullScreenIntent(entry);
// THEN display should try wake up for the full screen intent
verify(mCentralSurfaces).wakeUpForFullScreenIntent();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index c5841098010a..37457b308597 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -28,7 +28,6 @@ import com.android.systemui.lifecycle.InstantTaskExecutorRule
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
-import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -40,6 +39,7 @@ import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
@@ -70,7 +70,7 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
private lateinit var connectivityRepository: FakeConnectivityRepository
private lateinit var wifiRepository: FakeWifiRepository
private lateinit var interactor: WifiInteractor
- private lateinit var viewModel: WifiViewModel
+ private lateinit var viewModel: LocationBasedWifiViewModel
private lateinit var scope: CoroutineScope
private lateinit var airplaneModeViewModel: AirplaneModeViewModel
@@ -105,23 +105,19 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
scope,
statusBarPipelineFlags,
wifiConstants,
- )
+ ).home
}
@Test
fun constructAndBind_hasCorrectSlot() {
- val view = ModernStatusBarWifiView.constructAndBind(
- context, "slotName", viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, "slotName", viewModel)
assertThat(view.slot).isEqualTo("slotName")
}
@Test
fun getVisibleState_icon_returnsIcon() {
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
view.setVisibleState(STATE_ICON, /* animate= */ false)
@@ -130,9 +126,7 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
@Test
fun getVisibleState_dot_returnsDot() {
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
view.setVisibleState(STATE_DOT, /* animate= */ false)
@@ -141,9 +135,7 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
@Test
fun getVisibleState_hidden_returnsHidden() {
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
view.setVisibleState(STATE_HIDDEN, /* animate= */ false)
@@ -155,9 +147,7 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
@Test
fun setVisibleState_icon_iconShownDotHidden() {
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
view.setVisibleState(STATE_ICON, /* animate= */ false)
@@ -172,9 +162,7 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
@Test
fun setVisibleState_dot_iconHiddenDotShown() {
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
view.setVisibleState(STATE_DOT, /* animate= */ false)
@@ -189,9 +177,7 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
@Test
fun setVisibleState_hidden_iconAndDotHidden() {
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
view.setVisibleState(STATE_HIDDEN, /* animate= */ false)
@@ -211,9 +197,7 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2)
)
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
ViewUtils.attachView(view)
testableLooper.processAllMessages()
@@ -230,9 +214,7 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2)
)
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
ViewUtils.attachView(view)
testableLooper.processAllMessages()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
index 4f1fb02ecdcd..26df03f31b9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -25,6 +26,7 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.net.TetheringManager;
@@ -36,6 +38,7 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
@@ -96,6 +99,9 @@ public class HotspotControllerImplTest extends SysuiTestCase {
}).when(mWifiManager).registerSoftApCallback(any(Executor.class),
any(WifiManager.SoftApCallback.class));
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_show_wifi_tethering, true);
+
Handler handler = new Handler(mLooper.getLooper());
mController = new HotspotControllerImpl(mContext, handler, handler, mDumpManager);
@@ -176,4 +182,18 @@ public class HotspotControllerImplTest extends SysuiTestCase {
verify(mCallback1).onHotspotAvailabilityChanged(false);
}
+
+ @Test
+ public void testHotspotSupported_resource_false() {
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_show_wifi_tethering, false);
+
+ Handler handler = new Handler(mLooper.getLooper());
+
+ HotspotController controller =
+ new HotspotControllerImpl(mContext, handler, handler, mDumpManager);
+
+ verifyNoMoreInteractions(mTetheringManager);
+ assertFalse(controller.isHotspotSupported());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
index 120bf791c462..b485693af50f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
@@ -23,6 +23,8 @@ import android.os.UserHandle
import android.os.UserManager
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.user.data.repository.FakeUserRepository
@@ -55,6 +57,8 @@ class GuestUserInteractorTest : SysuiTestCase() {
@Mock private lateinit var dismissDialog: () -> Unit
@Mock private lateinit var selectUser: (Int) -> Unit
@Mock private lateinit var switchUser: (Int) -> Unit
+ @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
+ @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
private lateinit var underTest: GuestUserInteractor
@@ -87,10 +91,18 @@ class GuestUserInteractorTest : SysuiTestCase() {
repository = repository,
),
uiEventLogger = uiEventLogger,
+ resumeSessionReceiver = resumeSessionReceiver,
+ resetOrExitSessionReceiver = resetOrExitSessionReceiver,
)
}
@Test
+ fun `registers broadcast receivers`() {
+ verify(resumeSessionReceiver).register()
+ verify(resetOrExitSessionReceiver).register()
+ }
+
+ @Test
fun `onDeviceBootCompleted - allowed to add - create guest`() =
runBlocking(IMMEDIATE) {
setAllowedToAdd()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 1680c36cef87..58f55314c1b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -21,6 +21,8 @@ import android.app.ActivityManager
import android.app.admin.DevicePolicyManager
import android.os.UserManager
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
@@ -48,6 +50,8 @@ abstract class UserInteractorTest : SysuiTestCase() {
@Mock protected lateinit var devicePolicyManager: DevicePolicyManager
@Mock protected lateinit var uiEventLogger: UiEventLogger
@Mock protected lateinit var dialogShower: UserSwitchDialogController.DialogShower
+ @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
+ @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
protected lateinit var underTest: UserInteractor
@@ -107,6 +111,8 @@ abstract class UserInteractorTest : SysuiTestCase() {
devicePolicyManager = devicePolicyManager,
refreshUsersScheduler = refreshUsersScheduler,
uiEventLogger = uiEventLogger,
+ resumeSessionReceiver = resumeSessionReceiver,
+ resetOrExitSessionReceiver = resetOrExitSessionReceiver,
)
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index c12a868dbaed..116023aca655 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -23,6 +23,8 @@ import android.graphics.drawable.Drawable
import android.os.UserManager
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Text
import com.android.systemui.flags.FakeFeatureFlags
@@ -69,6 +71,8 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
@Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
@Mock private lateinit var uiEventLogger: UiEventLogger
+ @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
+ @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
private lateinit var underTest: UserSwitcherViewModel
@@ -104,6 +108,8 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
devicePolicyManager = devicePolicyManager,
refreshUsersScheduler = refreshUsersScheduler,
uiEventLogger = uiEventLogger,
+ resumeSessionReceiver = resumeSessionReceiver,
+ resetOrExitSessionReceiver = resetOrExitSessionReceiver,
)
underTest =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 11178db8afd2..627bd096143e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.data.repository
import com.android.systemui.common.shared.model.Position
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import kotlinx.coroutines.flow.Flow
@@ -52,6 +53,12 @@ class FakeKeyguardRepository : KeyguardRepository {
private val _wakefulnessState = MutableStateFlow(WakefulnessModel.ASLEEP)
override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState
+ private val _isBouncerShowing = MutableStateFlow(false)
+ override val isBouncerShowing: Flow<Boolean> = _isBouncerShowing
+
+ private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE)
+ override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState
+
override fun isKeyguardShowing(): Boolean {
return _isKeyguardShowing.value
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index 23c7a6139de8..2d6d29a50a74 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -62,7 +62,11 @@ public class FakeStatusBarIconController extends BaseLeakChecker<IconManager>
}
@Override
- public void setSignalIcon(String slot, WifiIconState state) {
+ public void setWifiIcon(String slot, WifiIconState state) {
+ }
+
+ @Override
+ public void setNewWifiIcon() {
}
@Override
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 252dcfc5eb9e..4430bb4b3292 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -26,6 +26,7 @@ import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -254,11 +255,13 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
private boolean mSafeMode;
private int mMaxWidgetBitmapMemory;
private boolean mIsProviderInfoPersisted;
+ private boolean mIsCombinedBroadcastEnabled;
AppWidgetServiceImpl(Context context) {
mContext = context;
}
+ @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG)
public void onStart() {
mPackageManager = AppGlobals.getPackageManager();
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
@@ -277,6 +280,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
mIsProviderInfoPersisted = !ActivityManager.isLowRamDeviceStatic()
&& DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.PERSISTS_WIDGET_PROVIDER_INFO, true);
+ mIsCombinedBroadcastEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.COMBINED_BROADCAST_ENABLED, true);
if (DEBUG_PROVIDER_INFO_CACHE && !mIsProviderInfoPersisted) {
Slog.d(TAG, "App widget provider info will not be persisted on this device");
}
@@ -1123,16 +1128,16 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
final int widgetCount = provider.widgets.size();
if (widgetCount == 1) {
- // Tell the provider that it's ready.
- sendEnableIntentLocked(provider);
+ // If we are binding the very first widget from a provider, we will send
+ // a combined broadcast or 2 separate broadcasts to tell the provider that
+ // it's ready, and we need them to provide the update now.
+ sendEnableAndUpdateIntentLocked(provider, new int[]{appWidgetId});
+ } else {
+ // For any widget other then the first one, we just send update intent
+ // as we normally would.
+ sendUpdateIntentLocked(provider, new int[]{appWidgetId});
}
- // Send an update now -- We need this update now, and just for this appWidgetId.
- // It's less critical when the next one happens, so when we schedule the next one,
- // we add updatePeriodMillis to its start time. That time will have some slop,
- // but that's okay.
- sendUpdateIntentLocked(provider, new int[] {appWidgetId});
-
// Schedule the future updates.
registerForBroadcastsLocked(provider, getWidgetIds(provider.widgets));
@@ -2361,6 +2366,22 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
cancelBroadcastsLocked(provider);
}
+ private void sendEnableAndUpdateIntentLocked(@NonNull Provider p, int[] appWidgetIds) {
+ final boolean canSendCombinedBroadcast = mIsCombinedBroadcastEnabled && p.info != null
+ && p.info.isExtendedFromAppWidgetProvider;
+ if (!canSendCombinedBroadcast) {
+ // If this function is called by mistake, send two separate broadcasts instead
+ sendEnableIntentLocked(p);
+ sendUpdateIntentLocked(p, appWidgetIds);
+ return;
+ }
+
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
+ intent.setComponent(p.id.componentName);
+ sendBroadcastAsUser(intent, p.id.getProfile());
+ }
+
private void sendEnableIntentLocked(Provider p) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED);
intent.setComponent(p.id.componentName);
@@ -2852,7 +2873,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
if (provider.widgets.size() > 0) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"appwidget init " + provider.id.componentName.getPackageName());
- sendEnableIntentLocked(provider);
provider.widgets.forEach(widget -> {
widget.trackingUpdate = true;
Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
@@ -2861,7 +2881,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
Log.i(TAG, "Widget update scheduled on unlock " + widget.toString());
});
int[] appWidgetIds = getWidgetIds(provider.widgets);
- sendUpdateIntentLocked(provider, appWidgetIds);
+ sendEnableAndUpdateIntentLocked(provider, appWidgetIds);
registerForBroadcastsLocked(provider, appWidgetIds);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index df5113b16f49..85d6f293852c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2886,10 +2886,13 @@ public class ActivityManagerService extends IActivityManager.Stub
|| event == Event.ACTIVITY_DESTROYED)) {
contentCaptureService.notifyActivityEvent(userId, activity, event);
}
- // TODO(b/201234353): Move the logic to client side.
- if (mVoiceInteractionManagerProvider != null && (event == Event.ACTIVITY_PAUSED
- || event == Event.ACTIVITY_RESUMED || event == Event.ACTIVITY_STOPPED)) {
- mVoiceInteractionManagerProvider.notifyActivityEventChanged();
+ // Currently we have move most of logic to the client side. When the activity lifecycle
+ // event changed, the client side will notify the VoiceInteractionManagerService. But
+ // when the application process died, the VoiceInteractionManagerService will miss the
+ // activity lifecycle event changed, so we still need ACTIVITY_DESTROYED event here to
+ // know if the activity has been destroyed.
+ if (mVoiceInteractionManagerProvider != null && event == Event.ACTIVITY_DESTROYED) {
+ mVoiceInteractionManagerProvider.notifyActivityDestroyed(appToken);
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index d2ba9c6dbfe4..53fcf3293525 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -41,6 +41,7 @@ import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.ActivityThread;
import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
@@ -152,6 +153,7 @@ import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorManager;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.System;
import android.service.notification.ZenModeConfig;
@@ -173,6 +175,7 @@ import android.widget.Toast;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.EventLogTags;
@@ -231,6 +234,7 @@ public class AudioService extends IAudioService.Stub
AudioSystemAdapter.OnVolRangeInitRequestListener {
private static final String TAG = "AS.AudioService";
+ private static final boolean CONFIG_DEFAULT_VAL = false;
private final AudioSystemAdapter mAudioSystem;
private final SystemServerAdapter mSystemServer;
@@ -981,6 +985,7 @@ public class AudioService extends IAudioService.Stub
* @param looper Looper to use for the service's message handler. If this is null, an
* {@link AudioSystemThread} is created as the messaging thread instead.
*/
+ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public AudioService(Context context, AudioSystemAdapter audioSystem,
SystemServerAdapter systemServer, SettingsAdapter settings, @Nullable Looper looper,
AppOpsManager appOps) {
@@ -1020,8 +1025,12 @@ public class AudioService extends IAudioService.Stub
mUseVolumeGroupAliases = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_handleVolumeAliasesUsingVolumeGroups);
- mNotifAliasRing = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_alias_ring_notif_stream_types);
+ mNotifAliasRing = !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
+
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+ ActivityThread.currentApplication().getMainExecutor(),
+ this::onDeviceConfigChange);
// Initialize volume
// Priority 1 - Android Property
@@ -1240,6 +1249,22 @@ public class AudioService extends IAudioService.Stub
}
/**
+ * Separating notification volume from ring is NOT of aliasing the corresponding streams
+ * @param properties
+ */
+ private void onDeviceConfigChange(DeviceConfig.Properties properties) {
+ Set<String> changeSet = properties.getKeyset();
+ if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
+ boolean newNotifAliasRing = !properties.getBoolean(
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL);
+ if (mNotifAliasRing != newNotifAliasRing) {
+ mNotifAliasRing = newNotifAliasRing;
+ updateStreamVolumeAlias(true, TAG);
+ }
+ }
+ }
+
+ /**
* Called by handling of MSG_INIT_STREAMS_VOLUMES
*/
private void onInitStreamsAndVolumes() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index ca4b7475f7dc..dbe971f32dc1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -100,9 +100,7 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession>
owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
isStrongBiometric, null /* taskStackListener */, lockoutCache,
allowBackgroundAuthentication,
- context.getResources().getBoolean(
- com.android.internal.R.bool.system_server_plays_face_haptics)
- /* shouldVibrate */,
+ false /* shouldVibrate */,
isKeyguardBypassEnabled);
setRequestId(requestId);
mUsageStats = usageStats;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 9baca98e4d2b..91eec7d19b3a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -74,7 +74,7 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
isStrongBiometric, null /* taskStackListener */,
- lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */,
+ lockoutTracker, allowBackgroundAuthentication, false /* shouldVibrate */,
isKeyguardBypassEnabled);
setRequestId(requestId);
mUsageStats = usageStats;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index a778b573f225..9aecf783a881 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -69,7 +69,6 @@ import java.util.function.Supplier;
class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
implements Udfps, LockoutConsumer, PowerPressHandler {
private static final String TAG = "FingerprintAuthenticationClient";
- private static final int MESSAGE_IGNORE_AUTH = 1;
private static final int MESSAGE_AUTH_SUCCESS = 2;
private static final int MESSAGE_FINGER_UP = 3;
@NonNull
@@ -136,7 +135,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
taskStackListener,
lockoutCache,
allowBackgroundAuthentication,
- true /* shouldVibrate */,
+ false /* shouldVibrate */,
false /* isKeyguardBypassEnabled */);
setRequestId(requestId);
mLockoutCache = lockoutCache;
@@ -235,12 +234,6 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
() -> {
long delay = 0;
if (authenticated && mSensorProps.isAnySidefpsType()) {
- if (mHandler.hasMessages(MESSAGE_IGNORE_AUTH)) {
- Slog.i(TAG, "(sideFPS) Ignoring auth due to recent power press");
- onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0,
- true);
- return;
- }
delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp;
if (mSideFpsLastAcquireStartTime != -1) {
@@ -497,16 +490,12 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
if (mSensorProps.isAnySidefpsType()) {
Slog.i(TAG, "(sideFPS): onPowerPressed");
mHandler.post(() -> {
- if (mHandler.hasMessages(MESSAGE_AUTH_SUCCESS)) {
- Slog.i(TAG, "(sideFPS): Ignoring auth in queue");
- mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
- // Do not call onError() as that will send an additional callback to coex.
- onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true);
- }
- mHandler.removeMessages(MESSAGE_IGNORE_AUTH);
- mHandler.postDelayed(() -> {
- }, MESSAGE_IGNORE_AUTH, mIgnoreAuthFor);
-
+ Slog.i(TAG, "(sideFPS): finishing auth");
+ // Ignore auths after a power has been detected
+ mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
+ // Do not call onError() as that will send an additional callback to coex.
+ onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true);
+ mSensorOverlays.hide(getSensorId());
});
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 7ed1a514f9bc..0d620fd3a9e4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -81,7 +81,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
isStrongBiometric, taskStackListener, lockoutTracker, allowBackgroundAuthentication,
- true /* shouldVibrate */, false /* isKeyguardBypassEnabled */);
+ false /* shouldVibrate */, false /* isKeyguardBypassEnabled */);
setRequestId(requestId);
mLockoutFrameworkImpl = lockoutTracker;
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 5589673973c3..e4aa5e574955 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -157,7 +157,7 @@ public final class DreamManagerService extends SystemService {
mDozeConfig = new AmbientDisplayConfiguration(mContext);
mUiEventLogger = new UiEventLoggerImpl();
mDreamUiEventLogger = new DreamUiEventLoggerImpl(
- mContext.getResources().getString(R.string.config_loggable_dream_prefix));
+ mContext.getResources().getStringArray(R.array.config_loggable_dream_prefixes));
AmbientDisplayConfiguration adc = new AmbientDisplayConfiguration(mContext);
mAmbientDisplayComponent = ComponentName.unflattenFromString(adc.ambientDisplayComponent());
mDreamsOnlyEnabledForSystemUser =
diff --git a/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java b/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java
index 26ca74a8d808..96ebcbb6a771 100644
--- a/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java
+++ b/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java
@@ -26,10 +26,10 @@ import com.android.internal.util.FrameworkStatsLog;
* @hide
*/
public class DreamUiEventLoggerImpl implements DreamUiEventLogger {
- final String mLoggableDreamPrefix;
+ private final String[] mLoggableDreamPrefixes;
- DreamUiEventLoggerImpl(String loggableDreamPrefix) {
- mLoggableDreamPrefix = loggableDreamPrefix;
+ DreamUiEventLoggerImpl(String[] loggableDreamPrefixes) {
+ mLoggableDreamPrefixes = loggableDreamPrefixes;
}
@Override
@@ -38,13 +38,20 @@ public class DreamUiEventLoggerImpl implements DreamUiEventLogger {
if (eventID <= 0) {
return;
}
- final boolean isFirstPartyDream =
- mLoggableDreamPrefix.isEmpty() ? false : dreamComponentName.startsWith(
- mLoggableDreamPrefix);
FrameworkStatsLog.write(FrameworkStatsLog.DREAM_UI_EVENT_REPORTED,
/* uid = 1 */ 0,
/* event_id = 2 */ eventID,
/* instance_id = 3 */ 0,
- /* dream_component_name = 4 */ isFirstPartyDream ? dreamComponentName : "other");
+ /* dream_component_name = 4 */
+ isFirstPartyDream(dreamComponentName) ? dreamComponentName : "other");
+ }
+
+ private boolean isFirstPartyDream(String dreamComponentName) {
+ for (int i = 0; i < mLoggableDreamPrefixes.length; ++i) {
+ if (dreamComponentName.startsWith(mLoggableDreamPrefixes[i])) {
+ return true;
+ }
+ }
+ return false;
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2eb2cf643c42..ccab96888e8f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -496,7 +496,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
/** The most recently given options. */
private ActivityOptions mPendingOptions;
/** Non-null if {@link #mPendingOptions} specifies the remote animation. */
- private RemoteAnimationAdapter mPendingRemoteAnimation;
+ RemoteAnimationAdapter mPendingRemoteAnimation;
private RemoteTransition mPendingRemoteTransition;
ActivityOptions returningOptions; // options that are coming back via convertToTranslucent
AppTimeTracker appTimeTracker; // set if we are tracking the time in this app/task/activity
@@ -812,7 +812,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
StartingData mStartingData;
WindowState mStartingWindow;
StartingSurfaceController.StartingSurface mStartingSurface;
- boolean startingDisplayed;
boolean startingMoved;
/** The last set {@link DropInputMode} for this activity surface. */
@@ -821,13 +820,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
/** Whether the input to this activity will be dropped during the current playing animation. */
private boolean mIsInputDroppedForAnimation;
- /**
- * If it is non-null, it requires all activities who have the same starting data to be drawn
- * to remove the starting window.
- * TODO(b/189385912): Remove starting window related fields after migrating them to task.
- */
- private StartingData mSharedStartingData;
-
boolean mHandleExitSplashScreen;
@TransferSplashScreenState
int mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
@@ -1200,14 +1192,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
pw.print(" firstWindowDrawn="); pw.print(firstWindowDrawn);
pw.print(" mIsExiting="); pw.println(mIsExiting);
}
- if (mSharedStartingData != null) {
- pw.println(prefix + "mSharedStartingData=" + mSharedStartingData);
- }
- if (mStartingWindow != null || mStartingSurface != null
- || startingDisplayed || startingMoved || mVisibleSetFromTransferredStartingWindow) {
+ if (mStartingWindow != null || mStartingData != null || mStartingSurface != null
+ || startingMoved || mVisibleSetFromTransferredStartingWindow) {
pw.print(prefix); pw.print("startingWindow="); pw.print(mStartingWindow);
pw.print(" startingSurface="); pw.print(mStartingSurface);
- pw.print(" startingDisplayed="); pw.print(startingDisplayed);
+ pw.print(" startingDisplayed="); pw.print(isStartingWindowDisplayed());
pw.print(" startingMoved="); pw.print(startingMoved);
pw.println(" mVisibleSetFromTransferredStartingWindow="
+ mVisibleSetFromTransferredStartingWindow);
@@ -2690,13 +2679,23 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
}
+ boolean isStartingWindowDisplayed() {
+ final StartingData data = mStartingData != null ? mStartingData : task != null
+ ? task.mSharedStartingData : null;
+ return data != null && data.mIsDisplayed;
+ }
+
/** Called when the starting window is added to this activity. */
void attachStartingWindow(@NonNull WindowState startingWindow) {
startingWindow.mStartingData = mStartingData;
mStartingWindow = startingWindow;
- // The snapshot type may have called associateStartingDataWithTask().
- if (mStartingData != null && mStartingData.mAssociatedTask != null) {
- attachStartingSurfaceToAssociatedTask();
+ if (mStartingData != null) {
+ if (mStartingData.mAssociatedTask != null) {
+ // The snapshot type may have called associateStartingDataWithTask().
+ attachStartingSurfaceToAssociatedTask();
+ } else if (isEmbedded()) {
+ associateStartingWindowWithTaskIfNeeded();
+ }
}
}
@@ -2711,11 +2710,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
/** Called when the starting window is not added yet but its data is known to fill the task. */
private void associateStartingDataWithTask() {
mStartingData.mAssociatedTask = task;
- task.forAllActivities(r -> {
- if (r.mVisibleRequested && !r.firstWindowDrawn) {
- r.mSharedStartingData = mStartingData;
- }
- });
+ task.mSharedStartingData = mStartingData;
}
/** Associates and attaches an added starting window to the current task. */
@@ -2746,10 +2741,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
void removeStartingWindowAnimation(boolean prepareAnimation) {
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
- if (mSharedStartingData != null) {
- mSharedStartingData.mAssociatedTask.forAllActivities(r -> {
- r.mSharedStartingData = null;
- });
+ if (task != null) {
+ task.mSharedStartingData = null;
}
if (mStartingWindow == null) {
if (mStartingData != null) {
@@ -2772,7 +2765,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mStartingData = null;
mStartingSurface = null;
mStartingWindow = null;
- startingDisplayed = false;
if (surface == null) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "startingWindow was set but "
+ "startingSurface==null, couldn't remove");
@@ -4257,7 +4249,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* @return {@code true} if starting window is in app's hierarchy.
*/
boolean hasStartingWindow() {
- if (startingDisplayed || mStartingData != null) {
+ if (mStartingData != null) {
return true;
}
for (int i = mChildren.size() - 1; i >= 0; i--) {
@@ -4355,10 +4347,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// Transfer the starting window over to the new token.
mStartingData = fromActivity.mStartingData;
- mSharedStartingData = fromActivity.mSharedStartingData;
mStartingSurface = fromActivity.mStartingSurface;
- startingDisplayed = fromActivity.startingDisplayed;
- fromActivity.startingDisplayed = false;
mStartingWindow = tStartingWindow;
reportedVisible = fromActivity.reportedVisible;
fromActivity.mStartingData = null;
@@ -4424,7 +4413,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
"Moving pending starting from %s to %s", fromActivity, this);
mStartingData = fromActivity.mStartingData;
- mSharedStartingData = fromActivity.mSharedStartingData;
fromActivity.mStartingData = null;
fromActivity.startingMoved = true;
scheduleAddStartingWindow();
@@ -6534,14 +6522,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// Remove starting window directly if is in a pure task. Otherwise if it is associated with
// a task (e.g. nested task fragment), then remove only if all visible windows in the task
// are drawn.
- final Task associatedTask =
- mSharedStartingData != null ? mSharedStartingData.mAssociatedTask : null;
+ final Task associatedTask = task.mSharedStartingData != null ? task : null;
if (associatedTask == null) {
removeStartingWindow();
- } else if (associatedTask.getActivity(r -> r.mVisibleRequested && !r.firstWindowDrawn
- // Don't block starting window removal if an Activity can't be a starting window
- // target.
- && r.mSharedStartingData != null) == null) {
+ } else if (associatedTask.getActivity(
+ r -> r.mVisibleRequested && !r.firstWindowDrawn) == null) {
// The last drawn activity may not be the one that owns the starting window.
final ActivityRecord r = associatedTask.topActivityContainsStartingWindow();
if (r != null) {
@@ -6756,7 +6741,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (mLastTransactionSequence != mWmService.mTransactionSequence) {
mLastTransactionSequence = mWmService.mTransactionSequence;
mNumDrawnWindows = 0;
- startingDisplayed = false;
// There is the main base application window, even if it is exiting, wait for it
mNumInterestingWindows = findMainWindow(false /* includeStartingApp */) != null ? 1 : 0;
@@ -6800,9 +6784,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
isInterestingAndDrawn = true;
}
}
- } else if (w.isDrawn()) {
+ } else if (mStartingData != null && w.isDrawn()) {
// The starting window for this container is drawn.
- startingDisplayed = true;
+ mStartingData.mIsDisplayed = true;
}
}
@@ -7550,7 +7534,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
ProtoLog.v(WM_DEBUG_ANIM, "Animation done in %s"
+ ": reportedVisible=%b okToDisplay=%b okToAnimate=%b startingDisplayed=%b",
- this, reportedVisible, okToDisplay(), okToAnimate(), startingDisplayed);
+ this, reportedVisible, okToDisplay(), okToAnimate(),
+ isStartingWindowDisplayed());
// clean up thumbnail window
if (mThumbnail != null) {
@@ -9649,7 +9634,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (mStartingWindow != null) {
mStartingWindow.writeIdentifierToProto(proto, STARTING_WINDOW);
}
- proto.write(STARTING_DISPLAYED, startingDisplayed);
+ proto.write(STARTING_DISPLAYED, isStartingWindowDisplayed());
proto.write(STARTING_MOVED, startingMoved);
proto.write(VISIBLE_SET_FROM_TRANSFERRED_STARTING_WINDOW,
mVisibleSetFromTransferredStartingWindow);
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 027d485d15a0..ed50c1e979ad 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2947,10 +2947,14 @@ class ActivityStarter {
}
}
- // Update the target's launch cookie to those specified in the options if set
+ // Update the target's launch cookie and pending remote animation to those specified in the
+ // options if set.
if (mStartActivity.mLaunchCookie != null) {
intentActivity.mLaunchCookie = mStartActivity.mLaunchCookie;
}
+ if (mStartActivity.mPendingRemoteAnimation != null) {
+ intentActivity.mPendingRemoteAnimation = mStartActivity.mPendingRemoteAnimation;
+ }
// Need to update mTargetRootTask because if task was moved out of it, the original root
// task may be destroyed.
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 9c95e31cc5f5..65fa98f7ab43 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -300,8 +300,8 @@ public class AppTransitionController {
layoutRedo = appTransition.goodToGo(transit, topOpeningApp);
handleNonAppWindowsInTransition(transit, flags);
appTransition.postAnimationCallback();
- appTransition.clear();
} finally {
+ appTransition.clear();
mService.mSurfaceAnimationRunner.continueStartingAnimations();
}
@@ -1229,11 +1229,11 @@ public class AppTransitionController {
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Check opening app=%s: allDrawn=%b startingDisplayed=%b "
+ "startingMoved=%b isRelaunching()=%b startingWindow=%s",
- activity, activity.allDrawn, activity.startingDisplayed,
+ activity, activity.allDrawn, activity.isStartingWindowDisplayed(),
activity.startingMoved, activity.isRelaunching(),
activity.mStartingWindow);
final boolean allDrawn = activity.allDrawn && !activity.isRelaunching();
- if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) {
+ if (!allDrawn && !activity.isStartingWindowDisplayed() && !activity.startingMoved) {
return false;
}
if (allDrawn) {
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index fbee343f09b5..300a894d15c9 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -38,6 +38,9 @@ public abstract class StartingData {
*/
Task mAssociatedTask;
+ /** Whether the starting window is drawn. */
+ boolean mIsDisplayed;
+
protected StartingData(WindowManagerService service, int typeParams) {
mService = service;
mTypeParams = typeParams;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index eba49bbc7301..66d7af9b4f6c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -358,6 +358,13 @@ class Task extends TaskFragment {
int mLockTaskUid = -1; // The uid of the application that called startLockTask().
+ /**
+ * If non-null, the starting window should cover the associated task. It is assigned when the
+ * parent activity of starting window is put in a partial area of the task. This field will be
+ * cleared when all visible activities in this task are drawn.
+ */
+ StartingData mSharedStartingData;
+
/** The process that had previously hosted the root activity of this task.
* Used to know that we should try harder to keep this process around, in case the
* user wants to return to it. */
@@ -3688,6 +3695,9 @@ class Task extends TaskFragment {
if (mRootProcess != null) {
pw.print(prefix); pw.print("mRootProcess="); pw.println(mRootProcess);
}
+ if (mSharedStartingData != null) {
+ pw.println(prefix + "mSharedStartingData=" + mSharedStartingData);
+ }
pw.print(prefix); pw.print("taskId=" + mTaskId);
pw.println(" rootTaskId=" + getRootTaskId());
pw.print(prefix); pw.println("hasChildPipActivity=" + (mChildPipActivity != null));
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index d17867673474..879c7e2d75dc 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1890,10 +1890,10 @@ class TaskFragment extends WindowContainer<WindowContainer> {
RemoteAnimationTarget createRemoteAnimationTarget(
RemoteAnimationController.RemoteAnimationRecord record) {
final ActivityRecord activity = record.getMode() == RemoteAnimationTarget.MODE_OPENING
- // There may be a trampoline activity without window on top of the existing task
- // which is moving to front. Exclude the finishing activity so the window of next
- // activity can be chosen to create the animation target.
- ? getTopNonFinishingActivity()
+ // There may be a launching (e.g. trampoline or embedded) activity without a window
+ // on top of the existing task which is moving to front. Exclude finishing activity
+ // so the window of next activity can be chosen to create the animation target.
+ ? getActivity(r -> !r.finishing && r.hasChild())
: getTopMostActivity();
return activity != null ? activity.createRemoteAnimationTarget(record) : null;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 744bf0aa42d9..d4c1abfa8d24 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1842,8 +1842,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
* @return {@code true} if one or more windows have been displayed, else false.
*/
boolean hasAppShownWindows() {
- return mActivityRecord != null
- && (mActivityRecord.firstWindowDrawn || mActivityRecord.startingDisplayed);
+ return mActivityRecord != null && (mActivityRecord.firstWindowDrawn
+ || mActivityRecord.isStartingWindowDisplayed());
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 6e16b5de1d85..a0ba8fda906f 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -374,13 +374,6 @@ class WindowStateAnimator {
}
void destroySurfaceLocked(SurfaceControl.Transaction t) {
- final ActivityRecord activity = mWin.mActivityRecord;
- if (activity != null) {
- if (mWin == activity.mStartingWindow) {
- activity.startingDisplayed = false;
- }
- }
-
if (mSurfaceController == null) {
return;
}
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
index 7610b7ca5ec3..b33e22fe6d1e 100644
--- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
@@ -164,7 +164,7 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase {
}
public void testRequestPinAppWidget() {
- ComponentName provider = new ComponentName(mTestContext, DummyAppWidget.class);
+ ComponentName provider = new ComponentName(mTestContext, TestAppWidgetProvider.class);
// Set up users.
when(mMockShortcutService.requestPinAppWidget(anyString(),
any(AppWidgetProviderInfo.class), eq(null), eq(null), anyInt()))
@@ -289,6 +289,16 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase {
assertEquals(4, updates.size());
}
+ public void testReceiveBroadcastBehavior_enableAndUpdate() {
+ TestAppWidgetProvider testAppWidgetProvider = new TestAppWidgetProvider();
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE);
+
+ testAppWidgetProvider.onReceive(mTestContext, intent);
+
+ assertTrue(testAppWidgetProvider.isBehaviorSuccess());
+ }
+
+
public void testUpdatesReceived_queueNotEmpty_multipleWidgetIdProvided() {
int widgetId = setupHostAndWidget();
int widgetId2 = bindNewWidget();
@@ -385,7 +395,7 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase {
}
private int bindNewWidget() {
- ComponentName provider = new ComponentName(mTestContext, DummyAppWidget.class);
+ ComponentName provider = new ComponentName(mTestContext, TestAppWidgetProvider.class);
int widgetId = mService.allocateAppWidgetId(mPkgName, HOST_ID);
assertTrue(mManager.bindAppWidgetIdIfAllowed(widgetId, provider));
assertEquals(provider, mManager.getAppWidgetInfo(widgetId).provider);
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/TestAppWidgetProvider.java b/services/tests/servicestests/src/com/android/server/appwidget/TestAppWidgetProvider.java
new file mode 100644
index 000000000000..6c11a6829c12
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appwidget/TestAppWidgetProvider.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 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.appwidget;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.Context;
+
+/**
+ * Placeholder widget for testing
+ */
+public class TestAppWidgetProvider extends AppWidgetProvider {
+ private boolean mEnabled;
+ private boolean mUpdated;
+
+ TestAppWidgetProvider() {
+ super();
+ mEnabled = false;
+ mUpdated = false;
+ }
+
+ public boolean isBehaviorSuccess() {
+ return mEnabled && mUpdated;
+ }
+
+ @Override
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager,
+ int[] appWidgetids) {
+ mUpdated = true;
+ }
+
+ @Override
+ public void onEnabled(Context context) {
+ mEnabled = true;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 606f4864643c..cd4af0a581f8 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -616,6 +616,20 @@ public class FingerprintAuthenticationClientTest {
verify(mCallback).onClientFinished(any(), eq(true));
}
+ @Test
+ public void sideFpsPowerPressCancelsIsntantly() throws Exception {
+ when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+
+ client.onPowerPressed();
+ mLooper.dispatchAll();
+
+ verify(mCallback, never()).onClientFinished(any(), eq(true));
+ verify(mCallback).onClientFinished(any(), eq(false));
+ }
+
private FingerprintAuthenticationClient createClient() throws RemoteException {
return createClient(100 /* version */, true /* allowBackgroundAuthentication */);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 462957a88a6c..8a0a4f7c82b7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2886,6 +2886,7 @@ public class ActivityRecordTests extends WindowTestsBase {
fragmentSetup.accept(taskFragment1, new Rect(0, 0, width / 2, height));
task.addChild(taskFragment1, POSITION_TOP);
assertEquals(task, activity1.mStartingData.mAssociatedTask);
+ assertEquals(activity1.mStartingData, task.mSharedStartingData);
final TaskFragment taskFragment2 = new TaskFragment(
mAtm, null /* fragmentToken */, false /* createdByOrganizer */);
@@ -2905,7 +2906,6 @@ public class ActivityRecordTests extends WindowTestsBase {
verify(activity1.getSyncTransaction()).reparent(eq(startingWindow.mSurfaceControl),
eq(task.mSurfaceControl));
- assertEquals(activity1.mStartingData, startingWindow.mStartingData);
assertEquals(task.mSurfaceControl, startingWindow.getAnimationLeashParent());
assertEquals(taskFragment1.getBounds(), activity1.getBounds());
// The activity was resized by task fragment, but starting window must still cover the task.
@@ -2916,6 +2916,7 @@ public class ActivityRecordTests extends WindowTestsBase {
activity1.onFirstWindowDrawn(activityWindow);
activity2.onFirstWindowDrawn(activityWindow);
assertNull(activity1.mStartingWindow);
+ assertNull(task.mSharedStartingData);
}
@Test
@@ -2991,10 +2992,10 @@ public class ActivityRecordTests extends WindowTestsBase {
final WindowManager.LayoutParams attrs =
new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING);
final TestWindowState startingWindow = createWindowState(attrs, activity);
- activity.startingDisplayed = true;
+ activity.mStartingData = mock(StartingData.class);
activity.addWindow(startingWindow);
assertTrue("Starting window should be present", activity.hasStartingWindow());
- activity.startingDisplayed = false;
+ activity.mStartingData = null;
assertTrue("Starting window should be present", activity.hasStartingWindow());
activity.removeChild(startingWindow);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 00be7ed5bc6c..496f6817bb08 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -98,6 +98,7 @@ import android.service.voice.IVoiceInteractionSession;
import android.util.Pair;
import android.util.Size;
import android.view.Gravity;
+import android.view.RemoteAnimationAdapter;
import android.window.TaskFragmentOrganizerToken;
import androidx.test.filters.SmallTest;
@@ -1315,6 +1316,32 @@ public class ActivityStarterTests extends WindowTestsBase {
}
@Test
+ public void testRemoteAnimation_appliesToExistingTask() {
+ final ActivityStarter starter = prepareStarter(0, false);
+
+ // Put an activity on default display as the top focused activity.
+ ActivityRecord r = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final Intent intent = new Intent();
+ intent.setComponent(ActivityBuilder.getDefaultComponent());
+ starter.setReason("testRemoteAnimation_newTask")
+ .setIntent(intent)
+ .execute();
+
+ assertNull(mRootWindowContainer.topRunningActivity().mPendingRemoteAnimation);
+
+ // Relaunch the activity with remote animation indicated in options.
+ final RemoteAnimationAdapter adaptor = mock(RemoteAnimationAdapter.class);
+ final ActivityOptions options = ActivityOptions.makeRemoteAnimation(adaptor);
+ starter.setReason("testRemoteAnimation_existingTask")
+ .setIntent(intent)
+ .setActivityOptions(options.toBundle())
+ .execute();
+
+ // Verify the remote animation is updated.
+ assertEquals(adaptor, mRootWindowContainer.topRunningActivity().mPendingRemoteAnimation);
+ }
+
+ @Test
public void testStartLaunchIntoPipActivity() {
final ActivityStarter starter = prepareStarter(0, false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 513791d2b8a5..0332c4b4597b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -1300,6 +1300,8 @@ public class AppTransitionControllerTest extends WindowTestsBase {
activity.allDrawn = true;
// Skip manipulate the SurfaceControl.
doNothing().when(activity).setDropInputMode(anyInt());
+ // Assume the activity contains a window.
+ doReturn(true).when(activity).hasChild();
// Make sure activity can create remote animation target.
doReturn(mock(RemoteAnimationTarget.class)).when(activity).createRemoteAnimationTarget(
any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index f61effa284ef..d55e53cca8a6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -321,7 +321,6 @@ public class AppTransitionTests extends WindowTestsBase {
final ActivityRecord activity2 = createActivityRecord(dc2);
activity1.allDrawn = true;
- activity1.startingDisplayed = true;
activity1.startingMoved = true;
// Simulate activity resume / finish flows to prepare app transition & set visibility,
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 352d8d115b6a..bc5c9ec7a29c 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -172,11 +172,11 @@ public class VoiceInteractionManagerService extends SystemService {
mAmInternal.setVoiceInteractionManagerProvider(
new ActivityManagerInternal.VoiceInteractionManagerProvider() {
@Override
- public void notifyActivityEventChanged() {
+ public void notifyActivityDestroyed(IBinder activityToken) {
if (DEBUG) {
- Slog.d(TAG, "call notifyActivityEventChanged");
+ Slog.d(TAG, "notifyActivityDestroyed activityToken=" + activityToken);
}
- mServiceStub.notifyActivityEventChanged();
+ mServiceStub.notifyActivityDestroyed(activityToken);
}
});
}
@@ -447,11 +447,12 @@ public class VoiceInteractionManagerService extends SystemService {
return mImpl.supportsLocalVoiceInteraction();
}
- void notifyActivityEventChanged() {
+ void notifyActivityDestroyed(@NonNull IBinder activityToken) {
synchronized (this) {
- if (mImpl == null) return;
+ if (mImpl == null || activityToken == null) return;
- Binder.withCleanCallingIdentity(() -> mImpl.notifyActivityEventChangedLocked());
+ Binder.withCleanCallingIdentity(
+ () -> mImpl.notifyActivityDestroyedLocked(activityToken));
}
}
@@ -1223,6 +1224,16 @@ public class VoiceInteractionManagerService extends SystemService {
}
}
+ @Override
+ public void notifyActivityEventChanged(@NonNull IBinder activityToken, int type) {
+ synchronized (this) {
+ if (mImpl == null || activityToken == null) {
+ return;
+ }
+ Binder.withCleanCallingIdentity(
+ () -> mImpl.notifyActivityEventChangedLocked(activityToken, type));
+ }
+ }
//----------------- Hotword Detection/Validation APIs --------------------------------//
@Override
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index b9793cafb6fe..fabab2524e83 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -520,9 +520,23 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
mActiveSession.stopListeningVisibleActivityChangedLocked();
}
- public void notifyActivityEventChangedLocked() {
+ public void notifyActivityDestroyedLocked(@NonNull IBinder activityToken) {
if (DEBUG) {
- Slog.d(TAG, "notifyActivityEventChangedLocked");
+ Slog.d(TAG, "notifyActivityDestroyedLocked activityToken=" + activityToken);
+ }
+ if (mActiveSession == null || !mActiveSession.mShown) {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyActivityDestroyedLocked not allowed on no session or"
+ + " hidden session");
+ }
+ return;
+ }
+ mActiveSession.notifyActivityDestroyedLocked(activityToken);
+ }
+
+ public void notifyActivityEventChangedLocked(@NonNull IBinder activityToken, int type) {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyActivityEventChangedLocked type=" + type);
}
if (mActiveSession == null || !mActiveSession.mShown) {
if (DEBUG) {
@@ -531,7 +545,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
}
return;
}
- mActiveSession.notifyActivityEventChangedLocked();
+ mActiveSession.notifyActivityEventChangedLocked(activityToken, type);
}
public void updateStateLocked(
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index ae9be8c09e60..b24337fd54c9 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -29,6 +29,7 @@ import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_DATA;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_TASK_ID;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
@@ -59,6 +60,7 @@ import android.service.voice.IVoiceInteractionSessionService;
import android.service.voice.VisibleActivityInfo;
import android.service.voice.VoiceInteractionService;
import android.service.voice.VoiceInteractionSession;
+import android.util.ArrayMap;
import android.util.Slog;
import android.view.IWindowManager;
@@ -128,7 +130,11 @@ final class VoiceInteractionSessionConnection implements ServiceConnection,
private boolean mListeningVisibleActivity;
private final ScheduledExecutorService mScheduledExecutorService =
Executors.newSingleThreadScheduledExecutor();
- private final List<VisibleActivityInfo> mVisibleActivityInfos = new ArrayList<>();
+ // Records the visible activity information the system has already called onVisible, without
+ // confirming the result of callback. When activity visible state is changed, we use this to
+ // determine to call onVisible or onInvisible to assistant application.
+ private final ArrayMap<IBinder, VisibleActivityInfo> mVisibleActivityInfoForToken =
+ new ArrayMap<>();
private final PowerManagerInternal mPowerManagerInternal;
private final LowPowerStandbyControllerInternal mLowPowerStandbyControllerInternal;
private final Runnable mRemoveFromLowPowerStandbyAllowlistRunnable =
@@ -530,7 +536,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection,
public void cancelLocked(boolean finishTask) {
mListeningVisibleActivity = false;
- mVisibleActivityInfos.clear();
+ mVisibleActivityInfoForToken.clear();
hideLocked();
mCanceled = true;
if (mBound) {
@@ -608,17 +614,24 @@ final class VoiceInteractionSessionConnection implements ServiceConnection,
if (DEBUG) {
Slog.d(TAG, "startListeningVisibleActivityChangedLocked");
}
+
+ if (!mShown || mCanceled || mSession == null) {
+ return;
+ }
+
mListeningVisibleActivity = true;
- mVisibleActivityInfos.clear();
+ mVisibleActivityInfoForToken.clear();
- mScheduledExecutorService.execute(() -> {
- if (DEBUG) {
- Slog.d(TAG, "call handleVisibleActivitiesLocked from enable listening");
- }
- synchronized (mLock) {
- handleVisibleActivitiesLocked();
- }
- });
+ // It should only need to report which activities are visible
+ final ArrayMap<IBinder, VisibleActivityInfo> newVisibleActivityInfos =
+ getTopVisibleActivityInfosLocked();
+
+ if (newVisibleActivityInfos == null || newVisibleActivityInfos.isEmpty()) {
+ return;
+ }
+ notifyVisibleActivitiesChangedLocked(newVisibleActivityInfos,
+ VisibleActivityInfo.TYPE_ACTIVITY_ADDED);
+ mVisibleActivityInfoForToken.putAll(newVisibleActivityInfos);
}
void stopListeningVisibleActivityChangedLocked() {
@@ -626,12 +639,13 @@ final class VoiceInteractionSessionConnection implements ServiceConnection,
Slog.d(TAG, "stopListeningVisibleActivityChangedLocked");
}
mListeningVisibleActivity = false;
- mVisibleActivityInfos.clear();
+ mVisibleActivityInfoForToken.clear();
}
- void notifyActivityEventChangedLocked() {
+ void notifyActivityEventChangedLocked(@NonNull IBinder activityToken, int type) {
if (DEBUG) {
- Slog.d(TAG, "notifyActivityEventChangedLocked");
+ Slog.d(TAG, "notifyActivityEventChangedLocked activityToken=" + activityToken
+ + ", type=" + type);
}
if (!mListeningVisibleActivity) {
if (DEBUG) {
@@ -640,99 +654,139 @@ final class VoiceInteractionSessionConnection implements ServiceConnection,
return;
}
mScheduledExecutorService.execute(() -> {
- if (DEBUG) {
- Slog.d(TAG, "call handleVisibleActivitiesLocked from activity event");
- }
synchronized (mLock) {
- handleVisibleActivitiesLocked();
+ handleVisibleActivitiesLocked(activityToken, type);
}
});
}
- private List<VisibleActivityInfo> getVisibleActivityInfosLocked() {
+ private ArrayMap<IBinder, VisibleActivityInfo> getTopVisibleActivityInfosLocked() {
if (DEBUG) {
- Slog.d(TAG, "getVisibleActivityInfosLocked");
+ Slog.d(TAG, "getTopVisibleActivityInfosLocked");
}
List<ActivityAssistInfo> allVisibleActivities =
LocalServices.getService(ActivityTaskManagerInternal.class)
.getTopVisibleActivities();
if (DEBUG) {
- Slog.d(TAG,
- "getVisibleActivityInfosLocked: allVisibleActivities=" + allVisibleActivities);
+ Slog.d(TAG, "getTopVisibleActivityInfosLocked: allVisibleActivities="
+ + allVisibleActivities);
}
- if (allVisibleActivities == null || allVisibleActivities.isEmpty()) {
+ if (allVisibleActivities.isEmpty()) {
Slog.w(TAG, "no visible activity");
return null;
}
final int count = allVisibleActivities.size();
- final List<VisibleActivityInfo> visibleActivityInfos = new ArrayList<>(count);
+ final ArrayMap<IBinder, VisibleActivityInfo> visibleActivityInfoArrayMap =
+ new ArrayMap<>(count);
for (int i = 0; i < count; i++) {
ActivityAssistInfo info = allVisibleActivities.get(i);
if (DEBUG) {
- Slog.d(TAG, " : activityToken=" + info.getActivityToken()
+ Slog.d(TAG, "ActivityAssistInfo : activityToken=" + info.getActivityToken()
+ ", assistToken=" + info.getAssistToken()
+ ", taskId=" + info.getTaskId());
}
- visibleActivityInfos.add(
+ visibleActivityInfoArrayMap.put(info.getActivityToken(),
new VisibleActivityInfo(info.getTaskId(), info.getAssistToken()));
}
- return visibleActivityInfos;
+ return visibleActivityInfoArrayMap;
}
- private void handleVisibleActivitiesLocked() {
+ // TODO(b/242359988): Split this method up
+ private void handleVisibleActivitiesLocked(@NonNull IBinder activityToken, int type) {
if (DEBUG) {
- Slog.d(TAG, "handleVisibleActivitiesLocked");
+ Slog.d(TAG, "handleVisibleActivitiesLocked activityToken=" + activityToken
+ + ", type=" + type);
}
- if (mSession == null) {
+
+ if (!mListeningVisibleActivity) {
+ if (DEBUG) {
+ Slog.d(TAG, "not enable listening visible activity");
+ }
return;
}
- if (!mShown || !mListeningVisibleActivity || mCanceled) {
+ if (!mShown || mCanceled || mSession == null) {
return;
}
- final List<VisibleActivityInfo> newVisibleActivityInfos = getVisibleActivityInfosLocked();
- if (newVisibleActivityInfos == null || newVisibleActivityInfos.isEmpty()) {
- notifyVisibleActivitiesChangedLocked(mVisibleActivityInfos,
- VisibleActivityInfo.TYPE_ACTIVITY_REMOVED);
- mVisibleActivityInfos.clear();
- return;
- }
- if (mVisibleActivityInfos.isEmpty()) {
- notifyVisibleActivitiesChangedLocked(newVisibleActivityInfos,
- VisibleActivityInfo.TYPE_ACTIVITY_ADDED);
- mVisibleActivityInfos.addAll(newVisibleActivityInfos);
+ // We use this local variable to determine to call onVisible or onInvisible.
+ boolean notifyOnVisible = false;
+ VisibleActivityInfo notifyVisibleActivityInfo = null;
+
+ if (type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_START
+ || type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_RESUME) {
+ // It seems that the onStart is unnecessary. But if we have it, the assistant
+ // application can request the directActions early. Even if we have the onStart,
+ // we still need the onResume because it is possible that the activity goes to
+ // onResume from onPause with invisible before the activity goes to onStop from
+ // onPause.
+
+ // Check if we have reported this activity as visible. If we have reported it as
+ // visible, do nothing.
+ if (mVisibleActivityInfoForToken.containsKey(activityToken)) {
+ return;
+ }
+
+ // Before reporting this activity as visible, we need to make sure the activity
+ // is really visible.
+ notifyVisibleActivityInfo = getVisibleActivityInfoFromTopVisibleActivity(
+ activityToken);
+ if (notifyVisibleActivityInfo == null) {
+ return;
+ }
+ notifyOnVisible = true;
+ } else if (type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE) {
+ // For the onPause stage, the Activity is not necessarily invisible now, so we need
+ // to check its state.
+ // Note: After syncing with Activity owner, before the onPause is called, the
+ // visibility state has been updated.
+ notifyVisibleActivityInfo = getVisibleActivityInfoFromTopVisibleActivity(
+ activityToken);
+ if (notifyVisibleActivityInfo != null) {
+ return;
+ }
+
+ // Also make sure we previously reported this Activity as visible.
+ notifyVisibleActivityInfo = mVisibleActivityInfoForToken.get(activityToken);
+ if (notifyVisibleActivityInfo == null) {
+ return;
+ }
+ } else if (type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_STOP) {
+ // For the onStop stage, the activity is in invisible state. We only need to consider if
+ // we have reported this activity as visible. If we have reported it as visible, we
+ // need to report it as invisible.
+ // Why we still need onStop? Because it is possible that the activity is in a visible
+ // state during onPause stage, when the activity enters onStop from onPause, we may
+ // need to notify onInvisible.
+ // Note: After syncing with Activity owner, before the onStop is called, the
+ // visibility state has been updated.
+ notifyVisibleActivityInfo = mVisibleActivityInfoForToken.get(activityToken);
+ if (notifyVisibleActivityInfo == null) {
+ return;
+ }
+ } else {
+ Slog.w(TAG, "notifyActivityEventChangedLocked unexpected type=" + type);
return;
}
- final List<VisibleActivityInfo> addedActivities = new ArrayList<>();
- final List<VisibleActivityInfo> removedActivities = new ArrayList<>();
-
- removedActivities.addAll(mVisibleActivityInfos);
- for (int i = 0; i < newVisibleActivityInfos.size(); i++) {
- final VisibleActivityInfo candidateVisibleActivityInfo = newVisibleActivityInfos.get(i);
- if (!removedActivities.isEmpty() && removedActivities.contains(
- candidateVisibleActivityInfo)) {
- removedActivities.remove(candidateVisibleActivityInfo);
- } else {
- addedActivities.add(candidateVisibleActivityInfo);
+ try {
+ mSession.notifyVisibleActivityInfoChanged(notifyVisibleActivityInfo,
+ notifyOnVisible ? VisibleActivityInfo.TYPE_ACTIVITY_ADDED
+ : VisibleActivityInfo.TYPE_ACTIVITY_REMOVED);
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.w(TAG, "handleVisibleActivitiesLocked RemoteException : " + e);
}
}
- if (!addedActivities.isEmpty()) {
- notifyVisibleActivitiesChangedLocked(addedActivities,
- VisibleActivityInfo.TYPE_ACTIVITY_ADDED);
- }
- if (!removedActivities.isEmpty()) {
- notifyVisibleActivitiesChangedLocked(removedActivities,
- VisibleActivityInfo.TYPE_ACTIVITY_REMOVED);
+ if (notifyOnVisible) {
+ mVisibleActivityInfoForToken.put(activityToken, notifyVisibleActivityInfo);
+ } else {
+ mVisibleActivityInfoForToken.remove(activityToken);
}
-
- mVisibleActivityInfos.clear();
- mVisibleActivityInfos.addAll(newVisibleActivityInfos);
}
private void notifyVisibleActivitiesChangedLocked(
- List<VisibleActivityInfo> visibleActivityInfos, int type) {
+ ArrayMap<IBinder, VisibleActivityInfo> visibleActivityInfos, int type) {
if (visibleActivityInfos == null || visibleActivityInfos.isEmpty()) {
return;
}
@@ -741,7 +795,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection,
}
try {
for (int i = 0; i < visibleActivityInfos.size(); i++) {
- mSession.notifyVisibleActivityInfoChanged(visibleActivityInfos.get(i), type);
+ mSession.notifyVisibleActivityInfoChanged(visibleActivityInfos.valueAt(i), type);
}
} catch (RemoteException e) {
if (DEBUG) {
@@ -754,6 +808,51 @@ final class VoiceInteractionSessionConnection implements ServiceConnection,
}
}
+ private VisibleActivityInfo getVisibleActivityInfoFromTopVisibleActivity(
+ @NonNull IBinder activityToken) {
+ final ArrayMap<IBinder, VisibleActivityInfo> visibleActivityInfos =
+ getTopVisibleActivityInfosLocked();
+ if (visibleActivityInfos == null) {
+ return null;
+ }
+ return visibleActivityInfos.get(activityToken);
+ }
+
+ void notifyActivityDestroyedLocked(@NonNull IBinder activityToken) {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyActivityDestroyedLocked activityToken=" + activityToken);
+ }
+ if (!mListeningVisibleActivity) {
+ if (DEBUG) {
+ Slog.d(TAG, "not enable listening visible activity");
+ }
+ return;
+ }
+ mScheduledExecutorService.execute(() -> {
+ synchronized (mLock) {
+ if (!mListeningVisibleActivity) {
+ return;
+ }
+ if (!mShown || mCanceled || mSession == null) {
+ return;
+ }
+
+ VisibleActivityInfo visibleActivityInfo = mVisibleActivityInfoForToken.remove(
+ activityToken);
+ if (visibleActivityInfo != null) {
+ try {
+ mSession.notifyVisibleActivityInfoChanged(visibleActivityInfo,
+ VisibleActivityInfo.TYPE_ACTIVITY_REMOVED);
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.w(TAG, "notifyVisibleActivityInfoChanged RemoteException : " + e);
+ }
+ }
+ }
+ }
+ });
+ }
+
private void removeFromLowPowerStandbyAllowlist() {
synchronized (mLock) {
if (mLowPowerStandbyAllowlisted) {