diff options
192 files changed, 4550 insertions, 2110 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index f779b4d96b45..b0b3b1f3cb5e 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -319,6 +319,8 @@ public class DeviceIdleController extends SystemService private SensorManager mSensorManager; private final boolean mUseMotionSensor; private Sensor mMotionSensor; + private final boolean mIsLocationPrefetchEnabled; + @Nullable private LocationRequest mLocationRequest; private Intent mIdleIntent; private Bundle mIdleIntentOptions; @@ -2460,6 +2462,11 @@ public class DeviceIdleController extends SystemService return null; } + boolean isLocationPrefetchEnabled() { + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_autoPowerModePrefetchLocation); + } + boolean useMotionSensor() { return mContext.getResources().getBoolean( com.android.internal.R.bool.config_autoPowerModeUseMotionSensor); @@ -2489,6 +2496,7 @@ public class DeviceIdleController extends SystemService mAppStateTracker = mInjector.getAppStateTracker(context, AppSchedulingModuleThread.get().getLooper()); LocalServices.addService(AppStateTracker.class, mAppStateTracker); + mIsLocationPrefetchEnabled = mInjector.isLocationPrefetchEnabled(); mUseMotionSensor = mInjector.useMotionSensor(); } @@ -2602,8 +2610,7 @@ public class DeviceIdleController extends SystemService mMotionSensor = mInjector.getMotionSensor(); } - if (getContext().getResources().getBoolean( - com.android.internal.R.bool.config_autoPowerModePrefetchLocation)) { + if (mIsLocationPrefetchEnabled) { mLocationRequest = new LocationRequest.Builder(/*intervalMillis=*/ 0) .setQuality(LocationRequest.QUALITY_HIGH_ACCURACY) .setMaxUpdates(1) @@ -3779,34 +3786,40 @@ public class DeviceIdleController extends SystemService case STATE_SENSING: cancelSensingTimeoutAlarmLocked(); moveToStateLocked(STATE_LOCATING, reason); - scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT); - LocationManager locationManager = mInjector.getLocationManager(); - if (locationManager != null - && locationManager.getProvider(LocationManager.FUSED_PROVIDER) != null) { - locationManager.requestLocationUpdates(LocationManager.FUSED_PROVIDER, - mLocationRequest, - AppSchedulingModuleThread.getExecutor(), - mGenericLocationListener); - mLocating = true; - } else { - mHasFusedLocation = false; - } - if (locationManager != null - && locationManager.getProvider(LocationManager.GPS_PROVIDER) != null) { - mHasGps = true; - locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 5, - mGpsLocationListener, mHandler.getLooper()); - mLocating = true; + if (mIsLocationPrefetchEnabled) { + scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT); + LocationManager locationManager = mInjector.getLocationManager(); + if (locationManager != null + && locationManager.getProvider(LocationManager.FUSED_PROVIDER) + != null) { + locationManager.requestLocationUpdates(LocationManager.FUSED_PROVIDER, + mLocationRequest, + AppSchedulingModuleThread.getExecutor(), + mGenericLocationListener); + mLocating = true; + } else { + mHasFusedLocation = false; + } + if (locationManager != null + && locationManager.getProvider(LocationManager.GPS_PROVIDER) != null) { + mHasGps = true; + locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, + 1000, 5, mGpsLocationListener, mHandler.getLooper()); + mLocating = true; + } else { + mHasGps = false; + } + // If we have a location provider, we're all set, the listeners will move state + // forward. + if (mLocating) { + break; + } + // Otherwise, we have to move from locating into idle maintenance. } else { - mHasGps = false; - } - // If we have a location provider, we're all set, the listeners will move state - // forward. - if (mLocating) { - break; + mLocating = false; } - // Otherwise, we have to move from locating into idle maintenance. + // We're not doing any locating work, so move on to the next state. case STATE_LOCATING: cancelAlarmLocked(); cancelLocatingLocked(); @@ -5303,15 +5316,19 @@ public class DeviceIdleController extends SystemService pw.print(" "); pw.print(mStationaryListeners.size()); pw.println(" stationary listeners registered"); } - pw.print(" mLocating="); pw.print(mLocating); - pw.print(" mHasGps="); pw.print(mHasGps); - pw.print(" mHasFused="); pw.print(mHasFusedLocation); - pw.print(" mLocated="); pw.println(mLocated); - if (mLastGenericLocation != null) { - pw.print(" mLastGenericLocation="); pw.println(mLastGenericLocation); - } - if (mLastGpsLocation != null) { - pw.print(" mLastGpsLocation="); pw.println(mLastGpsLocation); + if (mIsLocationPrefetchEnabled) { + pw.print(" mLocating="); pw.print(mLocating); + pw.print(" mHasGps="); pw.print(mHasGps); + pw.print(" mHasFused="); pw.print(mHasFusedLocation); + pw.print(" mLocated="); pw.println(mLocated); + if (mLastGenericLocation != null) { + pw.print(" mLastGenericLocation="); pw.println(mLastGenericLocation); + } + if (mLastGpsLocation != null) { + pw.print(" mLastGpsLocation="); pw.println(mLastGpsLocation); + } + } else { + pw.println(" Location prefetching disabled"); } pw.print(" mState="); pw.print(stateToString(mState)); pw.print(" mLightState="); diff --git a/config/dirty-image-objects b/config/dirty-image-objects index dfd091cc1aff..2584610e2848 100644 --- a/config/dirty-image-objects +++ b/config/dirty-image-objects @@ -28,359 +28,270 @@ # Then, grep for lines containing "Private dirty object" from the output. # This particular file was generated by dumping systemserver and systemui. # -Landroid/accounts/Account; -Landroid/accounts/OnAccountsUpdateListener; Landroid/animation/LayoutTransition; Landroid/app/ActivityManager; -Landroid/app/ActivityManager$OnUidImportanceListener; Landroid/app/ActivityTaskManager; Landroid/app/ActivityThread; -Landroid/app/admin/DevicePolicyManager; Landroid/app/AlarmManager; -Landroid/app/Application; Landroid/app/AppOpsManager; -Landroid/app/backup/BackupManager; Landroid/app/ContextImpl; -Landroid/app/INotificationManager; -Landroid/app/Notification$BigPictureStyle; -Landroid/app/Notification$BigTextStyle; -Landroid/app/Notification$InboxStyle; -Landroid/app/NotificationChannel; -Landroid/app/NotificationChannelGroup; +Landroid/app/Notification; Landroid/app/NotificationManager; -Landroid/app/PendingIntent; -Landroid/app/PendingIntent$OnFinished; +Landroid/app/PendingIntent$FinishedDispatcher; +Landroid/app/PropertyInvalidatedCache$NoPreloadHolder; Landroid/app/QueuedWork; Landroid/app/ResourcesManager; +Landroid/app/SystemServiceRegistry; Landroid/app/WallpaperManager; -Landroid/app/WindowConfiguration; -Landroid/bluetooth/BluetoothAdapter; -Landroid/bluetooth/BluetoothDevice; -Landroid/bluetooth/BluetoothProfile; -Landroid/bluetooth/IBluetoothA2dp; -Landroid/bluetooth/IBluetoothHeadsetPhone; -Landroid/bluetooth/IBluetoothHidDevice; -Landroid/bluetooth/IBluetoothHidHost; -Landroid/bluetooth/IBluetoothMap; -Landroid/bluetooth/IBluetoothPan; -Landroid/bluetooth/IBluetoothPbap; -Landroid/bluetooth/IBluetoothSap; -Landroid/content/ClipboardManager$OnPrimaryClipChangedListener; -Landroid/content/ComponentName; -Landroid/content/ContentProvider$PipeDataWriter; +Landroid/app/backup/BackupManager; +Landroid/compat/Compatibility; +Landroid/content/AsyncQueryHandler; +Landroid/content/ContentProviderClient; Landroid/content/ContentResolver; Landroid/content/Context; -Landroid/content/Intent; -Landroid/content/pm/PackageManager$OnPermissionsChangedListener; -Landroid/content/pm/VersionedPackage; -Landroid/content/res/Configuration; -Landroid/content/SharedPreferences$OnSharedPreferenceChangeListener; +Landroid/content/pm/PackageItemInfo; +Landroid/content/pm/UserPackage; +Landroid/content/res/ResourceTimer; Landroid/database/CursorWindow; Landroid/database/sqlite/SQLiteCompatibilityWalFlags; -Landroid/database/sqlite/SQLiteDatabase$CursorFactory; +Landroid/database/sqlite/SQLiteDebug$NoPreloadHolder; Landroid/database/sqlite/SQLiteGlobal; -Landroid/database/sqlite/SQLiteTransactionListener; Landroid/ddm/DdmHandleAppName; Landroid/graphics/Bitmap; Landroid/graphics/Canvas; -Landroid/graphics/drawable/AdaptiveIconDrawable; -Landroid/graphics/drawable/ColorDrawable; -Landroid/graphics/drawable/GradientDrawable; -Landroid/graphics/drawable/Icon; -Landroid/graphics/drawable/InsetDrawable; -Landroid/graphics/drawable/RippleDrawable; -Landroid/graphics/drawable/VectorDrawable$VGroup; -Landroid/graphics/ImageDecoder; -Landroid/graphics/Rect; +Landroid/graphics/Compatibility; +Landroid/graphics/HardwareRenderer; Landroid/graphics/TemporaryBuffer; -Landroid/hardware/biometrics/BiometricSourceType; -Landroid/hardware/display/ColorDisplayManager$ColorDisplayManagerInternal; -Landroid/hardware/display/DisplayManagerGlobal; -Landroid/hardware/display/NightDisplayListener$Callback; -Landroid/hardware/input/InputManager; -Landroid/hardware/input/InputManager$InputDeviceListener; +Landroid/graphics/Typeface; +Landroid/graphics/drawable/AdaptiveIconDrawable; Landroid/hardware/SensorPrivacyManager; Landroid/hardware/SystemSensorManager; -Landroid/icu/impl/OlsonTimeZone; -Landroid/icu/text/BreakIterator; +Landroid/hardware/devicestate/DeviceStateManagerGlobal; +Landroid/hardware/display/ColorDisplayManager$ColorDisplayManagerInternal; +Landroid/hardware/display/DisplayManagerGlobal; +Landroid/hardware/input/InputManagerGlobal; +Landroid/hardware/location/GeofenceHardwareImpl; +Landroid/icu/impl/number/range/StandardPluralRanges; Landroid/icu/text/Collator; -Landroid/icu/text/DateFormat$BooleanAttribute; -Landroid/icu/text/DateTimePatternGenerator$DTPGflags; -Landroid/icu/text/PluralRules$Operand; Landroid/icu/util/TimeZone; -Landroid/location/GpsStatus$Listener; -Landroid/location/LocationListener; +Landroid/location/LocationManager; Landroid/media/AudioManager; +Landroid/media/AudioPlaybackConfiguration; +Landroid/media/AudioSystem; +Landroid/media/MediaCodec; +Landroid/media/MediaCodecList; +Landroid/media/MediaFrameworkPlatformInitializer; +Landroid/media/MediaRouter2Manager; Landroid/media/MediaRouter; Landroid/media/PlayerBase; -Landroid/media/session/MediaSessionManager; -Landroid/net/apf/ApfCapabilities; -Landroid/net/ConnectivityManager; -Landroid/net/ConnectivityManager$OnNetworkActiveListener; -Landroid/net/ConnectivityThread$Singleton; -Landroid/net/IpConfiguration$IpAssignment; -Landroid/net/IpConfiguration$ProxySettings; -Landroid/net/IpPrefix; -Landroid/net/LinkAddress; -Landroid/net/LinkProperties; -Landroid/net/Network; -Landroid/net/NetworkCapabilities; -Landroid/net/NetworkInfo; -Landroid/net/NetworkInfo$State; -Landroid/net/NetworkRequest; -Landroid/net/NetworkRequest$Type; -Landroid/net/RouteInfo; -Landroid/net/StringNetworkSpecifier; -Landroid/net/TrafficStats; -Landroid/net/UidRange; -Landroid/net/Uri$HierarchicalUri; -Landroid/net/Uri$StringUri; -Landroid/net/wifi/WifiManager; -Landroid/net/wifi/WifiManager$SoftApCallback; -Landroid/os/AsyncResult; +Landroid/media/audiopolicy/AudioProductStrategy; +Landroid/media/audiopolicy/AudioVolumeGroup; +Landroid/nfc/NfcAdapter; +Landroid/nfc/NfcFrameworkInitializer; +Landroid/nfc/cardemulation/CardEmulation; Landroid/os/AsyncTask; +Landroid/os/BaseBundle; +Landroid/os/Binder; Landroid/os/BinderProxy; -Landroid/os/Bundle; -Landroid/os/DeadObjectException; Landroid/os/Environment; Landroid/os/FileObserver; Landroid/os/Handler; -Landroid/os/IDeviceIdleController; Landroid/os/LocaleList; Landroid/os/Looper; Landroid/os/Message; -Landroid/os/ParcelUuid; +Landroid/os/NullVibrator; +Landroid/os/Parcel; Landroid/os/Process; -Landroid/os/RecoverySystem; Landroid/os/ServiceManager; -Landroid/os/storage/StorageManager; Landroid/os/StrictMode; -Landroid/os/Trace; +Landroid/os/UEventObserver; +Landroid/os/UserManager; Landroid/os/WorkSource; -Landroid/os/WorkSource$WorkChain; +Landroid/os/storage/StorageManager; Landroid/permission/PermissionManager; +Landroid/provider/DeviceConfigInitializer; Landroid/provider/FontsContract; -Landroid/provider/Settings$SettingNotFoundException; +Landroid/provider/Settings; +Landroid/renderscript/RenderScript; Landroid/renderscript/RenderScriptCacheDir; -Landroid/security/IKeyChainService; -Landroid/security/keystore/AndroidKeyStoreProvider; +Landroid/security/keystore2/KeyStoreCryptoOperationUtils; Landroid/security/net/config/ApplicationConfig; Landroid/security/net/config/SystemCertificateSource$NoPreloadHolder; -Landroid/telecom/PhoneAccountHandle; +Landroid/security/net/config/UserCertificateSource$NoPreloadHolder; +Landroid/telecom/Log; +Landroid/telecom/TelecomManager; Landroid/telephony/AnomalyReporter; -Landroid/telephony/CellSignalStrengthCdma; -Landroid/telephony/CellSignalStrengthGsm; -Landroid/telephony/CellSignalStrengthLte; -Landroid/telephony/CellSignalStrengthNr; -Landroid/telephony/CellSignalStrengthTdscdma; -Landroid/telephony/CellSignalStrengthWcdma; -Landroid/telephony/DataSpecificRegistrationInfo; -Landroid/telephony/emergency/EmergencyNumber; -Landroid/telephony/ims/ImsMmTelManager$CapabilityCallback$CapabilityBinder; -Landroid/telephony/ims/ImsMmTelManager$RegistrationCallback$RegistrationBinder; -Landroid/telephony/ims/ImsReasonInfo; -Landroid/telephony/ims/ProvisioningManager$Callback$CallbackBinder; -Landroid/telephony/ModemActivityInfo; -Landroid/telephony/ModemInfo; -Landroid/telephony/NetworkRegistrationInfo; -Landroid/telephony/NetworkService; +Landroid/telephony/TelephonyFrameworkInitializer; +Landroid/telephony/TelephonyLocalConnection; Landroid/telephony/TelephonyManager; -Landroid/telephony/VoiceSpecificRegistrationInfo; +Landroid/telephony/TelephonyRegistryManager; +Landroid/text/DynamicLayout; +Landroid/text/TextUtils; Landroid/text/format/DateFormat; +Landroid/text/format/DateUtils; +Landroid/text/method/ArrowKeyMovementMethod; +Landroid/text/method/LinkMovementMethod; Landroid/text/method/SingleLineTransformationMethod; -Landroid/text/Selection$MemoryTextWatcher; -Landroid/text/SpanWatcher; -Landroid/text/style/AlignmentSpan; -Landroid/text/style/CharacterStyle; -Landroid/text/style/LeadingMarginSpan; -Landroid/text/style/LineBackgroundSpan; -Landroid/text/style/LineHeightSpan; -Landroid/text/style/MetricAffectingSpan; -Landroid/text/style/ReplacementSpan; -Landroid/text/style/SuggestionSpan; -Landroid/text/style/TabStopSpan; -Landroid/text/TextUtils; -Landroid/text/TextWatcher; -Landroid/transition/ChangeClipBounds; -Landroid/transition/ChangeImageTransform; -Landroid/transition/ChangeTransform; +Landroid/text/style/ClickableSpan; +Landroid/timezone/TelephonyLookup; +Landroid/timezone/TimeZoneFinder; Landroid/util/ArrayMap; Landroid/util/ArraySet; -Landroid/util/DisplayMetrics; Landroid/util/EventLog; -Landroid/util/Log; -Landroid/util/Patterns; -Landroid/view/AbsSavedState$1; -Landroid/view/accessibility/AccessibilityManager; -Landroid/view/accessibility/AccessibilityManager$AccessibilityServicesStateChangeListener; -Landroid/view/accessibility/AccessibilityManager$TouchExplorationStateChangeListener; -Landroid/view/accessibility/AccessibilityNodeIdManager; -Landroid/view/autofill/AutofillManager; -Landroid/view/autofill/Helper; +Landroid/util/NtpTrustedTime; Landroid/view/Choreographer; -Landroid/view/inputmethod/InputMethodManager; -Landroid/view/IWindowManager; +Landroid/view/CrossWindowBlurListeners; +Landroid/view/DisplayCutout; +Landroid/view/KeyEvent; +Landroid/view/MotionEvent; Landroid/view/PointerIcon; -Landroid/view/RemoteAnimationAdapter; -Landroid/view/ThreadedRenderer; +Landroid/view/RoundedCorners; +Landroid/view/SurfaceControl; Landroid/view/View; -Landroid/view/View$OnHoverListener; +Landroid/view/ViewGroup$TouchTarget; Landroid/view/ViewRootImpl; -Landroid/view/ViewStub; -Landroid/view/ViewStub$OnInflateListener; Landroid/view/ViewTreeObserver; -Landroid/view/WindowManager$LayoutParams; Landroid/view/WindowManagerGlobal; -Landroid/widget/ActionMenuPresenter$OverflowMenuButton; -Landroid/widget/ActionMenuView; -Landroid/widget/Button; -Landroid/widget/CheckBox; -Landroid/widget/FrameLayout; -Landroid/widget/ImageButton; +Landroid/view/accessibility/AccessibilityManager; +Landroid/view/accessibility/AccessibilityNodeIdManager; +Landroid/view/autofill/Helper; +Landroid/view/inputmethod/IInputMethodManagerGlobalInvoker; +Landroid/view/inputmethod/InputMethodManager; +Landroid/webkit/CookieSyncManager; +Landroid/webkit/WebView; +Landroid/webkit/WebViewFactory; +Landroid/webkit/WebViewZygote; +Landroid/widget/AbsListView; Landroid/widget/ImageView; Landroid/widget/LinearLayout; -Landroid/widget/RelativeLayout; -Landroid/widget/SeekBar; -Landroid/widget/Space; -Landroid/widget/TextView; -Landroid/widget/Toolbar; -[B -Lcom/android/ims/ImsManager; +Landroid/widget/Toast; +Landroid/window/SurfaceSyncGroup; +Lcom/android/i18n/timezone/TelephonyLookup; +Lcom/android/i18n/timezone/TimeZoneFinder; +Lcom/android/internal/config/appcloning/AppCloningDeviceConfigHelper; +Lcom/android/internal/content/om/OverlayConfig; +Lcom/android/internal/display/BrightnessSynchronizer; +Lcom/android/internal/infra/AndroidFuture; +Lcom/android/internal/inputmethod/ImeTracing; +Lcom/android/internal/inputmethod/InputMethodPrivilegedOperationsRegistry; +Lcom/android/internal/jank/InteractionJankMonitor$InstanceHolder; +Lcom/android/internal/jank/InteractionJankMonitor; Lcom/android/internal/logging/MetricsLogger; Lcom/android/internal/os/BackgroundThread; Lcom/android/internal/os/BinderInternal; -Lcom/android/internal/os/BinderInternal$BinderProxyLimitListener; +Lcom/android/internal/os/KernelCpuBpfTracking; Lcom/android/internal/os/RuntimeInit; Lcom/android/internal/os/SomeArgs; -Lcom/android/internal/policy/DecorView; -Lcom/android/internal/statusbar/IStatusBarService; -Lcom/android/internal/telephony/AppSmsManager; -Landroid/telephony/CallerInfoAsyncQuery$OnQueryCompleteListener; -Lcom/android/internal/telephony/CarrierActionAgent; -Lcom/android/internal/telephony/cat/CatService; -Lcom/android/internal/telephony/cat/IconLoader; -Lcom/android/internal/telephony/cat/RilMessageDecoder; -Lcom/android/internal/telephony/cdma/CdmaSubscriptionSourceManager; -Lcom/android/internal/telephony/cdma/EriManager; -Lcom/android/internal/telephony/CellularNetworkValidator; -Lcom/android/internal/telephony/CommandException; -Lcom/android/internal/telephony/dataconnection/DataConnection$DcActivatingState; -Lcom/android/internal/telephony/dataconnection/DataConnection$DcActiveState; -Lcom/android/internal/telephony/dataconnection/DataConnection$DcInactiveState; -Lcom/android/internal/telephony/dataconnection/DataEnabledSettings; -Lcom/android/internal/telephony/dataconnection/DcTracker; -Lcom/android/internal/telephony/euicc/EuiccCardController; -Lcom/android/internal/telephony/euicc/EuiccController; -Lcom/android/internal/telephony/GsmAlphabet; -Lcom/android/internal/telephony/GsmCdmaCallTracker; -Lcom/android/internal/telephony/GsmCdmaPhone; -Lcom/android/internal/telephony/IccPhoneBookInterfaceManager; -Lcom/android/internal/telephony/IccSmsInterfaceManager; -Lcom/android/internal/telephony/ims/ImsResolver; -Lcom/android/internal/telephony/imsphone/ImsExternalCallTracker; -Lcom/android/internal/telephony/imsphone/ImsPhone; -Lcom/android/internal/telephony/imsphone/ImsPhoneCallTracker; -Lcom/android/internal/telephony/ims/RcsMessageStoreController; +Lcom/android/internal/os/ZygoteInit; +Lcom/android/internal/policy/AttributeCache; +Lcom/android/internal/protolog/BaseProtoLogImpl; +Lcom/android/internal/protolog/ProtoLogImpl; +Lcom/android/internal/statusbar/NotificationVisibility; +Lcom/android/internal/telephony/CellBroadcastServiceManager; Lcom/android/internal/telephony/IntentBroadcaster; -Lcom/android/internal/telephony/ITelephonyRegistry$Stub$Proxy; -Lcom/android/internal/telephony/metrics/TelephonyMetrics; +Lcom/android/internal/telephony/MccTable; Lcom/android/internal/telephony/MultiSimSettingController; -Lcom/android/internal/telephony/nano/CarrierIdProto$CarrierAttribute; -Lcom/android/internal/telephony/nano/CarrierIdProto$CarrierId; -Lcom/android/internal/telephony/nano/TelephonyProto$RilDataCall; -Lcom/android/internal/telephony/nano/TelephonyProto$SmsSession$Event; -Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyCallSession$Event$RilCall; -Lcom/android/internal/telephony/NitzStateMachine; +Lcom/android/internal/telephony/PackageChangeReceiver; Lcom/android/internal/telephony/PhoneConfigurationManager; Lcom/android/internal/telephony/PhoneFactory; -Lcom/android/internal/telephony/PhoneSwitcher; Lcom/android/internal/telephony/ProxyController; -Lcom/android/internal/telephony/RadioConfig; -Lcom/android/internal/telephony/RIL; Lcom/android/internal/telephony/RILRequest; -Lcom/android/internal/telephony/RilWakelockInfo; -Lcom/android/internal/telephony/ServiceStateTracker; -Lcom/android/internal/telephony/SimActivationTracker; +Lcom/android/internal/telephony/RadioConfig; +Lcom/android/internal/telephony/RadioInterfaceCapabilityController; Lcom/android/internal/telephony/SmsApplication; Lcom/android/internal/telephony/SmsBroadcastUndelivered; -Lcom/android/internal/telephony/SmsStorageMonitor; -Lcom/android/internal/telephony/SmsUsageMonitor; -Lcom/android/internal/telephony/SubscriptionController; -Lcom/android/internal/telephony/SubscriptionInfoUpdater; +Lcom/android/internal/telephony/SomeArgs; Lcom/android/internal/telephony/TelephonyComponentFactory; Lcom/android/internal/telephony/TelephonyDevController; -Lcom/android/internal/telephony/TelephonyTester; -Lcom/android/internal/telephony/uicc/AdnRecordCache; -Lcom/android/internal/telephony/uicc/UiccCardApplication; +Lcom/android/internal/telephony/cat/CatService; +Lcom/android/internal/telephony/cdma/CdmaInboundSmsHandler; +Lcom/android/internal/telephony/cdma/CdmaSubscriptionSourceManager; +Lcom/android/internal/telephony/euicc/EuiccCardController; +Lcom/android/internal/telephony/euicc/EuiccController; +Lcom/android/internal/telephony/ims/ImsResolver; +Lcom/android/internal/telephony/metrics/TelephonyMetrics; +Lcom/android/internal/telephony/nano/PersistAtomsProto$CarrierIdMismatch; +Lcom/android/internal/telephony/nano/PersistAtomsProto$CellularDataServiceSwitch; +Lcom/android/internal/telephony/nano/PersistAtomsProto$CellularServiceState; +Lcom/android/internal/telephony/nano/PersistAtomsProto$DataCallSession; +Lcom/android/internal/telephony/nano/PersistAtomsProto$EmergencyNumbersInfo; +Lcom/android/internal/telephony/nano/PersistAtomsProto$GbaEvent; +Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsDedicatedBearerEvent; +Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsDedicatedBearerListenerEvent; +Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationFeatureTagStats; +Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationServiceDescStats; +Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationStats; +Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationTermination; +Lcom/android/internal/telephony/nano/PersistAtomsProto$IncomingSms; +Lcom/android/internal/telephony/nano/PersistAtomsProto$NetworkRequests; +Lcom/android/internal/telephony/nano/PersistAtomsProto$NetworkRequestsV2; +Lcom/android/internal/telephony/nano/PersistAtomsProto$OutgoingShortCodeSms; +Lcom/android/internal/telephony/nano/PersistAtomsProto$OutgoingSms; +Lcom/android/internal/telephony/nano/PersistAtomsProto$PresenceNotifyEvent; +Lcom/android/internal/telephony/nano/PersistAtomsProto$RcsAcsProvisioningStats; +Lcom/android/internal/telephony/nano/PersistAtomsProto$RcsClientProvisioningStats; +Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteController; +Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteIncomingDatagram; +Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteOutgoingDatagram; +Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteProvision; +Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteSession; +Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteSosMessageRecommender; +Lcom/android/internal/telephony/nano/PersistAtomsProto$SipDelegateStats; +Lcom/android/internal/telephony/nano/PersistAtomsProto$SipMessageResponse; +Lcom/android/internal/telephony/nano/PersistAtomsProto$SipTransportFeatureTagStats; +Lcom/android/internal/telephony/nano/PersistAtomsProto$SipTransportSession; +Lcom/android/internal/telephony/nano/PersistAtomsProto$UceEventStats; +Lcom/android/internal/telephony/nano/PersistAtomsProto$UnmeteredNetworks; +Lcom/android/internal/telephony/nano/PersistAtomsProto$VoiceCallRatUsage; +Lcom/android/internal/telephony/nano/PersistAtomsProto$VoiceCallSession; +Lcom/android/internal/telephony/nano/TelephonyProto$RilDataCall; +Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyCallSession$Event$RilCall; +Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyServiceState$NetworkRegistrationInfo; +Lcom/android/internal/telephony/satellite/PointingAppController; +Lcom/android/internal/telephony/satellite/SatelliteModemInterface; Lcom/android/internal/telephony/uicc/UiccController; -Lcom/android/internal/telephony/uicc/UiccProfile; Lcom/android/internal/telephony/uicc/UiccStateChangedLauncher; -Lcom/android/internal/telephony/uicc/UsimFileHandler; -Lcom/android/internal/telephony/uicc/VoiceMailConstants; -Lcom/android/internal/util/LatencyTracker; -Lcom/android/internal/util/StateMachine$SmHandler; -Lcom/android/okhttp/OkHttpClient; -Lcom/android/okhttp/okio/AsyncTimeout; -Lcom/android/okhttp/okio/SegmentPool; +Lcom/android/internal/util/ContrastColorUtil; +Lcom/android/internal/view/WindowManagerPolicyThread; +Lcom/android/org/bouncycastle/crypto/CryptoServicesRegistrar; Lcom/android/phone/ecc/nano/ProtobufEccData$CountryInfo; Lcom/android/phone/ecc/nano/ProtobufEccData$EccInfo; -Lcom/android/server/sip/SipWakeupTimer; -Lcom/android/server/SystemConfig; +Lcom/android/server/AppWidgetBackupBridge; Ldalvik/system/BaseDexClassLoader; Ldalvik/system/BlockGuard; Ldalvik/system/CloseGuard; Ldalvik/system/RuntimeHooks; Ldalvik/system/SocketTagger; -Ljava/io/BufferedReader; -Ljava/lang/AssertionError; -Ljava/lang/Boolean; -Ljava/lang/Byte; -Ljava/lang/Character; -Ljava/lang/CharSequence; -Ljava/lang/Class; -Ljava/lang/IllegalAccessException; -Ljava/lang/IllegalStateException; -Ljava/lang/NoSuchMethodException; -Ljava/lang/NullPointerException; -Ljava/lang/Object; -[Ljava/lang/Object; -Ljava/lang/ref/FinalizerReference; -Ljava/lang/Runnable; -Ljava/lang/SecurityException; -Ljava/lang/Short; -[Ljava/lang/String; +Ldalvik/system/VMRuntime; +Ldalvik/system/ZipPathValidator; +Ldalvik/system/ZygoteHooks; Ljava/lang/System; Ljava/lang/Thread; Ljava/lang/Throwable; -Ljava/lang/UnsatisfiedLinkError; -Ljava/net/Inet6Address; -Ljava/net/Socket; -Ljava/net/SocketException; +Ljava/lang/ref/FinalizerReference; +Ljava/lang/ref/ReferenceQueue; +Ljava/net/ResponseCache; Ljava/nio/Bits; Ljava/nio/charset/Charset; -Ljava/security/interfaces/RSAPrivateKey; Ljava/security/Provider; Ljava/util/Collections; -Ljava/util/concurrent/Executor; Ljava/util/GregorianCalendar; -Ljava/util/Locale; Ljava/util/Locale$NoImagePreloadHolder; +Ljava/util/Locale; Ljava/util/Scanner; -Ljava/util/Set; Ljava/util/TimeZone; +Ljava/util/concurrent/ForkJoinPool; +Ljava/util/concurrent/ThreadLocalRandom; +Ljavax/net/ServerSocketFactory; Ljavax/net/SocketFactory; -Ljavax/net/ssl/HttpsURLConnection; Ljavax/net/ssl/HttpsURLConnection$NoPreloadHolder; +Ljavax/net/ssl/HttpsURLConnection; Ljavax/net/ssl/SSLContext; -Ljavax/net/ssl/SSLSessionContext; +Ljavax/net/ssl/SSLServerSocketFactory; Ljavax/net/ssl/SSLSocketFactory; Llibcore/io/Libcore; -Llibcore/io/Memory; Llibcore/net/NetworkSecurityPolicy; -Llibcore/timezone/TimeZoneFinder; -Lorg/apache/http/params/HttpParams; Lsun/misc/Cleaner; -Lsun/nio/ch/FileChannelImpl; Lsun/nio/ch/FileChannelImpl$Unmapper; -Lsun/nio/fs/UnixChannelFactory; +Lsun/nio/ch/FileChannelImpl; Lsun/security/jca/Providers; diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 61d6787b4f85..2ae721648656 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -403,8 +403,11 @@ public class ActivityOptions extends ComponentOptions { private static final String KEY_SPLASH_SCREEN_STYLE = "android.activity.splashScreenStyle"; - /** See {@link #setTransientLaunch()}. */ - private static final String KEY_TRANSIENT_LAUNCH = "android.activity.transientLaunch"; + /** + * See {@link #setTransientLaunch()}. + * @hide + */ + public static final String KEY_TRANSIENT_LAUNCH = "android.activity.transientLaunch"; /** see {@link #makeLaunchIntoPip(PictureInPictureParams)}. */ private static final String KEY_LAUNCH_INTO_PIP_PARAMS = diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl index 36e57620c9dd..3c6ff2865d04 100644 --- a/core/java/android/app/ITaskStackListener.aidl +++ b/core/java/android/app/ITaskStackListener.aidl @@ -137,7 +137,7 @@ oneway interface ITaskStackListener { * activities inside it belong to a managed profile user, and that user has just * been locked. */ - void onTaskProfileLocked(in ActivityManager.RunningTaskInfo taskInfo); + void onTaskProfileLocked(in ActivityManager.RunningTaskInfo taskInfo, int userId); /** * Called when a task snapshot got updated. diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java index 774bc06e1adb..0290cee94dc3 100644 --- a/core/java/android/app/TaskStackListener.java +++ b/core/java/android/app/TaskStackListener.java @@ -154,8 +154,18 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub { } @Override + public void onTaskProfileLocked(RunningTaskInfo taskInfo, int userId) + throws RemoteException { + onTaskProfileLocked(taskInfo); + } + + /** + * @deprecated see {@link #onTaskProfileLocked(RunningTaskInfo, int)} + */ + @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void onTaskProfileLocked(RunningTaskInfo taskInfo) throws RemoteException { + public void onTaskProfileLocked(RunningTaskInfo taskInfo) + throws RemoteException { } @Override diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java index 09d2db89a043..9ebb0585afd0 100644 --- a/core/java/android/credentials/ui/RequestInfo.java +++ b/core/java/android/credentials/ui/RequestInfo.java @@ -30,6 +30,8 @@ import com.android.internal.util.AnnotationValidations; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; /** * Contains information about the request that initiated this UX flow. @@ -64,6 +66,9 @@ public final class RequestInfo implements Parcelable { @Nullable private final CreateCredentialRequest mCreateCredentialRequest; + @NonNull + private final List<String> mDefaultProviderIds; + @Nullable private final GetCredentialRequest mGetCredentialRequest; @@ -83,7 +88,8 @@ public final class RequestInfo implements Parcelable { @NonNull String appPackageName) { return new RequestInfo( token, TYPE_CREATE, appPackageName, createCredentialRequest, null, - /*hasPermissionToOverrideDefault=*/ false); + /*hasPermissionToOverrideDefault=*/ false, + /*defaultProviderIds=*/ new ArrayList<>()); } /** @@ -94,10 +100,11 @@ public final class RequestInfo implements Parcelable { @NonNull public static RequestInfo newCreateRequestInfo( @NonNull IBinder token, @NonNull CreateCredentialRequest createCredentialRequest, - @NonNull String appPackageName, boolean hasPermissionToOverrideDefault) { + @NonNull String appPackageName, boolean hasPermissionToOverrideDefault, + @NonNull List<String> defaultProviderIds) { return new RequestInfo( token, TYPE_CREATE, appPackageName, createCredentialRequest, null, - hasPermissionToOverrideDefault); + hasPermissionToOverrideDefault, defaultProviderIds); } /** Creates new {@code RequestInfo} for a get-credential flow. */ @@ -107,7 +114,8 @@ public final class RequestInfo implements Parcelable { @NonNull String appPackageName) { return new RequestInfo( token, TYPE_GET, appPackageName, null, getCredentialRequest, - /*hasPermissionToOverrideDefault=*/ false); + /*hasPermissionToOverrideDefault=*/ false, + /*defaultProviderIds=*/ new ArrayList<>()); } @@ -149,6 +157,20 @@ public final class RequestInfo implements Parcelable { } /** + * Returns default provider identifier (flattened component name) configured from the user + * settings. + * + * Will only be possibly non-empty for the create use case. Not meaningful for the sign-in use + * case. + * + * @hide + */ + @NonNull + public List<String> getDefaultProviderIds() { + return mDefaultProviderIds; + } + + /** * Returns the non-null GetCredentialRequest when the type of the request is {@link * #TYPE_GET}, or null otherwise. */ @@ -161,13 +183,15 @@ public final class RequestInfo implements Parcelable { @NonNull String appPackageName, @Nullable CreateCredentialRequest createCredentialRequest, @Nullable GetCredentialRequest getCredentialRequest, - boolean hasPermissionToOverrideDefault) { + boolean hasPermissionToOverrideDefault, + @NonNull List<String> defaultProviderIds) { mToken = token; mType = type; mAppPackageName = appPackageName; mCreateCredentialRequest = createCredentialRequest; mGetCredentialRequest = getCredentialRequest; mHasPermissionToOverrideDefault = hasPermissionToOverrideDefault; + mDefaultProviderIds = defaultProviderIds == null ? new ArrayList<>() : defaultProviderIds; } private RequestInfo(@NonNull Parcel in) { @@ -188,6 +212,7 @@ public final class RequestInfo implements Parcelable { mCreateCredentialRequest = createCredentialRequest; mGetCredentialRequest = getCredentialRequest; mHasPermissionToOverrideDefault = in.readBoolean(); + mDefaultProviderIds = in.createStringArrayList(); } @Override @@ -198,6 +223,7 @@ public final class RequestInfo implements Parcelable { dest.writeTypedObject(mCreateCredentialRequest, flags); dest.writeTypedObject(mGetCredentialRequest, flags); dest.writeBoolean(mHasPermissionToOverrideDefault); + dest.writeStringList(mDefaultProviderIds); } @Override diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 79e7574647b3..4a46beb670de 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4677,16 +4677,22 @@ public final class Settings { "display_color_mode_vendor_hint"; /** - * Whether or not the peak refresh rate should be forced. 0=no, 1=yes + * The user selected min refresh rate in frames per second. + * + * If this isn't set, 0 will be used. * @hide */ - public static final String FORCE_PEAK_REFRESH_RATE = "force_peak_refresh_rate"; + @Readable + public static final String MIN_REFRESH_RATE = "min_refresh_rate"; /** - * Whether or not the peak refresh rate should be used for some content. 0=no, 1=yes + * The user selected peak refresh rate in frames per second. + * + * If this isn't set, the system falls back to a device specific default. * @hide */ - public static final String SMOOTH_DISPLAY = "smooth_display"; + @Readable + public static final String PEAK_REFRESH_RATE = "peak_refresh_rate"; /** * The amount of time in milliseconds before the device goes to sleep or begins diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java index 1a1df6f5f989..751c675b28f4 100644 --- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java +++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java @@ -75,12 +75,13 @@ public final class CredentialProviderInfoFactory { /** * Constructs an information instance of the credential provider. * - * @param context the context object + * @param context the context object * @param serviceComponent the serviceComponent of the provider service - * @param userId the android userId for which the current process is running + * @param userId the android userId for which the current process is running * @param isSystemProvider whether this provider is a system provider * @throws PackageManager.NameNotFoundException If provider service is not found - * @throws SecurityException If provider does not require the relevant permission + * @throws SecurityException If provider does not require the relevant + * permission */ public static CredentialProviderInfo create( @NonNull Context context, @@ -99,13 +100,15 @@ public final class CredentialProviderInfoFactory { /** * Constructs an information instance of the credential provider. * - * @param context the context object - * @param serviceInfo the service info for the provider app. This must be retrieved from the - * {@code PackageManager} - * @param isSystemProvider whether the provider app is a system provider + * @param context the context object + * @param serviceInfo the service info for the provider app. This must + * be retrieved from the + * {@code PackageManager} + * @param isSystemProvider whether the provider app is a system provider * @param disableSystemAppVerificationForTests whether to disable system app permission - * verification so that tests can install system providers - * @param isEnabled whether the user enabled this provider + * verification so that tests can install system + * providers + * @param isEnabled whether the user enabled this provider * @throws SecurityException If provider does not require the relevant permission */ public static CredentialProviderInfo create( @@ -374,7 +377,6 @@ public final class CredentialProviderInfoFactory { if (appInfo == null || serviceInfo == null) { continue; } - services.add(serviceInfo); } catch (SecurityException | PackageManager.NameNotFoundException e) { Slog.e(TAG, "Error getting info for " + serviceInfo, e); diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java index 53a5fd5c634d..cf2e6a639fce 100644 --- a/core/java/android/service/credentials/CredentialProviderService.java +++ b/core/java/android/service/credentials/CredentialProviderService.java @@ -18,7 +18,6 @@ package android.service.credentials; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; -import android.Manifest; import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.SdkConstant; @@ -35,7 +34,7 @@ import android.os.ICancellationSignal; import android.os.Looper; import android.os.OutcomeReceiver; import android.os.RemoteException; -import android.util.Log; +import android.util.Slog; import java.util.Objects; @@ -226,7 +225,7 @@ public abstract class CredentialProviderService extends Service { if (SERVICE_INTERFACE.equals(intent.getAction())) { return mInterface.asBinder(); } - Log.d(TAG, "Failed to bind with intent: " + intent); + Slog.w(TAG, "Failed to bind with intent: " + intent); return null; } @@ -252,11 +251,6 @@ public abstract class CredentialProviderService extends Service { GetCredentialException>() { @Override public void onResult(BeginGetCredentialResponse result) { - // If provider service does not possess the HYBRID permission, this - // check will throw an exception in the provider process. - if (result.getRemoteCredentialEntry() != null) { - enforceRemoteEntryPermission(); - } try { callback.onSuccess(result); } catch (RemoteException e) { @@ -274,15 +268,6 @@ public abstract class CredentialProviderService extends Service { } )); } - private void enforceRemoteEntryPermission() { - String permission = - Manifest.permission.PROVIDE_REMOTE_CREDENTIALS; - getApplicationContext().enforceCallingOrSelfPermission( - permission, - String.format("Provider must have %s, in order to set a " - + "remote entry", permission) - ); - } @Override public void onBeginCreateCredential(BeginCreateCredentialRequest request, @@ -305,11 +290,6 @@ public abstract class CredentialProviderService extends Service { BeginCreateCredentialResponse, CreateCredentialException>() { @Override public void onResult(BeginCreateCredentialResponse result) { - // If provider service does not possess the HYBRID permission, this - // check will throw an exception in the provider process. - if (result.getRemoteCreateEntry() != null) { - enforceRemoteEntryPermission(); - } try { callback.onSuccess(result); } catch (RemoteException e) { diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 6061a0f93312..7822ddeb73d8 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -1063,21 +1063,6 @@ public class VoiceInteractionService extends Service { mActiveVisualQueryDetector = null; } mActiveDetectors.remove(detector); - shutdownHotwordDetectionServiceIfRequiredLocked(); - } - } - - private void shutdownHotwordDetectionServiceIfRequiredLocked() { - for (HotwordDetector detector : mActiveDetectors) { - if (detector.isUsingSandboxedDetectionService()) { - return; - } - } - - try { - mSystemService.shutdownHotwordDetectionService(); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); } } diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java index 8f270f5c5eeb..62f3c909dd4f 100644 --- a/core/java/android/view/inputmethod/InputConnectionWrapper.java +++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java @@ -412,4 +412,32 @@ public class InputConnectionWrapper implements InputConnection { public boolean setImeConsumesInput(boolean imeConsumesInput) { return mTarget.setImeConsumesInput(imeConsumesInput); } + + /** + * Called by the system when it needs to take a snapshot of multiple text-related data in an + * atomic manner. + * + * <p><strong>Editor authors</strong>: Supporting this method is strongly encouraged. Atomically + * taken {@link TextSnapshot} is going to be really helpful for the system when optimizing IPCs + * in a safe and deterministic manner. Return {@code null} if an atomically taken + * {@link TextSnapshot} is unavailable. The system continues supporting such a scenario + * gracefully.</p> + * + * <p><strong>IME authors</strong>: Currently IMEs cannot call this method directly and always + * receive {@code null} as the result.</p> + * + * <p>Beware that there is a bug that this method was not overridden in + * {@link InputConnectionWrapper}, which ended up always returning {@code null} when gets + * called even if the wrapped {@link InputConnection} implements this method. The bug was + * fixed in {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}.</p> + * + * @return {@code null} if {@link TextSnapshot} is unavailable and/or this API is called from + * IMEs. Beware the bug in older devices mentioned above. + * @throws NullPointerException if the target is {@code null}. + */ + @Nullable + @Override + public TextSnapshot takeSnapshot() { + return mTarget.takeSnapshot(); + } } diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index 51382a4b265f..4d0132e46ba7 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -21,6 +21,8 @@ import android.annotation.Nullable; import android.app.Activity; import android.content.Context; import android.content.ContextWrapper; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; import android.os.Handler; import android.os.RemoteException; import android.os.SystemProperties; @@ -421,36 +423,45 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { return false; } - boolean requestsPredictiveBack; + boolean requestsPredictiveBack = false; // Check if the context is from an activity. while ((context instanceof ContextWrapper) && !(context instanceof Activity)) { context = ((ContextWrapper) context).getBaseContext(); } + boolean shouldCheckActivity = false; + if (context instanceof Activity) { final Activity activity = (Activity) context; - if (activity.getActivityInfo().hasOnBackInvokedCallbackEnabled()) { - requestsPredictiveBack = - activity.getActivityInfo().isOnBackInvokedCallbackEnabled(); + final ActivityInfo activityInfo = activity.getActivityInfo(); + if (activityInfo != null) { + if (activityInfo.hasOnBackInvokedCallbackEnabled()) { + shouldCheckActivity = true; + requestsPredictiveBack = activityInfo.isOnBackInvokedCallbackEnabled(); + + if (DEBUG) { + Log.d(TAG, TextUtils.formatSimple( + "Activity: %s isPredictiveBackEnabled=%s", + activity.getComponentName(), + requestsPredictiveBack)); + } + } } else { - requestsPredictiveBack = - context.getApplicationInfo().isOnBackInvokedCallbackEnabled(); + Log.w(TAG, "The ActivityInfo is null, so we cannot verify if this Activity" + + " has the 'android:enableOnBackInvokedCallback' attribute." + + " The application attribute will be used as a fallback."); } + } - if (DEBUG) { - Log.d(TAG, TextUtils.formatSimple("Activity: %s isPredictiveBackEnabled=%s", - activity.getComponentName(), - requestsPredictiveBack)); - } - } else { - requestsPredictiveBack = - context.getApplicationInfo().isOnBackInvokedCallbackEnabled(); + if (!shouldCheckActivity) { + final ApplicationInfo applicationInfo = context.getApplicationInfo(); + requestsPredictiveBack = applicationInfo.isOnBackInvokedCallbackEnabled(); if (DEBUG) { Log.d(TAG, TextUtils.formatSimple("App: %s requestsPredictiveBack=%s", - context.getApplicationInfo().packageName, + applicationInfo.packageName, requestsPredictiveBack)); } } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 499d38c31b59..50f393b53277 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -1725,16 +1725,17 @@ public class ResolverActivity extends Activity implements if (inWorkProfile) { ((TextView) findViewById(R.id.open_cross_profile)).setText( - devicePolicyResourcesManager.getString(MINIRESOLVER_OPEN_IN_WORK, - () -> getString(R.string.miniresolver_open_in_work, targetDisplayLabel), + devicePolicyResourcesManager.getString(MINIRESOLVER_OPEN_IN_PERSONAL, + () -> getString(R.string.miniresolver_open_in_personal, + targetDisplayLabel), targetDisplayLabel)); ((Button) findViewById(R.id.use_same_profile_browser)).setText( devicePolicyResourcesManager.getString(MINIRESOLVER_USE_WORK_BROWSER, () -> getString(R.string.miniresolver_use_work_browser))); } else { ((TextView) findViewById(R.id.open_cross_profile)).setText( - devicePolicyResourcesManager.getString(MINIRESOLVER_OPEN_IN_PERSONAL, - () -> getString(R.string.miniresolver_open_in_personal, + devicePolicyResourcesManager.getString(MINIRESOLVER_OPEN_IN_WORK, + () -> getString(R.string.miniresolver_open_in_work, targetDisplayLabel), targetDisplayLabel)); ((Button) findViewById(R.id.use_same_profile_browser)).setText( diff --git a/core/java/com/android/internal/display/RefreshRateSettingsUtils.java b/core/java/com/android/internal/display/RefreshRateSettingsUtils.java deleted file mode 100644 index 39d8380c7e95..000000000000 --- a/core/java/com/android/internal/display/RefreshRateSettingsUtils.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.display; - -import android.content.ContentResolver; -import android.content.Context; -import android.hardware.display.DisplayManager; -import android.provider.Settings; -import android.util.Log; -import android.view.Display; - -/** - * Constants and utility methods for refresh rate settings. - */ -public class RefreshRateSettingsUtils { - - private static final String TAG = "RefreshRateSettingsUtils"; - - public static final float DEFAULT_REFRESH_RATE = 60f; - - /** - * Find the highest refresh rate among all the modes of the default display. - * @param context The context - * @return The highest refresh rate - */ - public static float findHighestRefreshRateForDefaultDisplay(Context context) { - final DisplayManager dm = context.getSystemService(DisplayManager.class); - final Display display = dm.getDisplay(Display.DEFAULT_DISPLAY); - - if (display == null) { - Log.w(TAG, "No valid default display device"); - return DEFAULT_REFRESH_RATE; - } - - float maxRefreshRate = DEFAULT_REFRESH_RATE; - for (Display.Mode mode : display.getSupportedModes()) { - if (Math.round(mode.getRefreshRate()) > maxRefreshRate) { - maxRefreshRate = mode.getRefreshRate(); - } - } - return maxRefreshRate; - } - - /** - * Get the min refresh rate which is determined by - * {@link Settings.System.FORCE_PEAK_REFRESH_RATE}. - * @param context The context - * @return The min refresh rate - */ - public static float getMinRefreshRate(Context context) { - final ContentResolver cr = context.getContentResolver(); - int forcePeakRefreshRateSetting = Settings.System.getIntForUser(cr, - Settings.System.FORCE_PEAK_REFRESH_RATE, -1, cr.getUserId()); - return forcePeakRefreshRateSetting == 1 - ? findHighestRefreshRateForDefaultDisplay(context) - : 0; - } - - /** - * Get the peak refresh rate which is determined by {@link Settings.System.SMOOTH_DISPLAY}. - * @param context The context - * @param defaultPeakRefreshRate The refresh rate to return if the setting doesn't have a value - * @return The peak refresh rate - */ - public static float getPeakRefreshRate(Context context, float defaultPeakRefreshRate) { - final ContentResolver cr = context.getContentResolver(); - int smoothDisplaySetting = Settings.System.getIntForUser(cr, - Settings.System.SMOOTH_DISPLAY, -1, cr.getUserId()); - switch (smoothDisplaySetting) { - case 0: - return DEFAULT_REFRESH_RATE; - case 1: - return findHighestRefreshRateForDefaultDisplay(context); - default: - return defaultPeakRefreshRate; - } - } -} diff --git a/core/proto/android/companion/telecom.proto b/core/proto/android/companion/telecom.proto index 9ccadbf6eb2d..3a9e5eeb4877 100644 --- a/core/proto/android/companion/telecom.proto +++ b/core/proto/android/companion/telecom.proto @@ -20,9 +20,9 @@ package android.companion; option java_multiple_files = true; -// Next index: 2 +// Next index: 4 message Telecom { - // Next index: 5 + // Next index: 6 message Call { // UUID representing this call int64 id = 1; @@ -34,6 +34,8 @@ message Telecom { // Human-readable name of the app processing this call string app_name = 2; bytes app_icon = 3; + // Unique identifier for this app, such as a package name. + string app_identifier = 4; } Origin origin = 2; @@ -59,9 +61,11 @@ message Telecom { REJECT_AND_BLOCK = 9; IGNORE = 10; } - repeated Control controls_available = 4; + repeated Control controls = 4; } // The list of active calls. repeated Call calls = 1; + // The list of requested calls or call changes. + repeated Call requests = 2; } diff --git a/core/res/res/values-mcc310/config.xml b/core/res/res/values-mcc310/config.xml index df398f9aab32..76abceeb74b1 100644 --- a/core/res/res/values-mcc310/config.xml +++ b/core/res/res/values-mcc310/config.xml @@ -22,4 +22,7 @@ <!-- Whether safe headphone volume is enabled or not (country specific). --> <bool name="config_safe_media_volume_enabled">false</bool> + <!-- Whether safe headphone sound dosage warning is enabled or not (country specific). --> + <bool name="config_safe_sound_dosage_mcc_enabled">false</bool> + </resources> diff --git a/core/res/res/values-mcc311/config.xml b/core/res/res/values-mcc311/config.xml index df398f9aab32..6e0b678f94d9 100644 --- a/core/res/res/values-mcc311/config.xml +++ b/core/res/res/values-mcc311/config.xml @@ -22,4 +22,7 @@ <!-- Whether safe headphone volume is enabled or not (country specific). --> <bool name="config_safe_media_volume_enabled">false</bool> + <!-- Whether safe headphone sound dosage warning is enabled or not (country specific). --> + <bool name="config_safe_sound_dosage_enabled">false</bool> + </resources> diff --git a/core/res/res/values-mcc312/config.xml b/core/res/res/values-mcc312/config.xml index df398f9aab32..6e0b678f94d9 100644 --- a/core/res/res/values-mcc312/config.xml +++ b/core/res/res/values-mcc312/config.xml @@ -22,4 +22,7 @@ <!-- Whether safe headphone volume is enabled or not (country specific). --> <bool name="config_safe_media_volume_enabled">false</bool> + <!-- Whether safe headphone sound dosage warning is enabled or not (country specific). --> + <bool name="config_safe_sound_dosage_enabled">false</bool> + </resources> diff --git a/core/res/res/values-mcc313/config.xml b/core/res/res/values-mcc313/config.xml index df398f9aab32..6e0b678f94d9 100644 --- a/core/res/res/values-mcc313/config.xml +++ b/core/res/res/values-mcc313/config.xml @@ -22,4 +22,7 @@ <!-- Whether safe headphone volume is enabled or not (country specific). --> <bool name="config_safe_media_volume_enabled">false</bool> + <!-- Whether safe headphone sound dosage warning is enabled or not (country specific). --> + <bool name="config_safe_sound_dosage_enabled">false</bool> + </resources> diff --git a/core/res/res/values-mcc314/config.xml b/core/res/res/values-mcc314/config.xml index df398f9aab32..6e0b678f94d9 100644 --- a/core/res/res/values-mcc314/config.xml +++ b/core/res/res/values-mcc314/config.xml @@ -22,4 +22,7 @@ <!-- Whether safe headphone volume is enabled or not (country specific). --> <bool name="config_safe_media_volume_enabled">false</bool> + <!-- Whether safe headphone sound dosage warning is enabled or not (country specific). --> + <bool name="config_safe_sound_dosage_enabled">false</bool> + </resources> diff --git a/core/res/res/values-mcc315/config.xml b/core/res/res/values-mcc315/config.xml index df398f9aab32..6e0b678f94d9 100644 --- a/core/res/res/values-mcc315/config.xml +++ b/core/res/res/values-mcc315/config.xml @@ -22,4 +22,7 @@ <!-- Whether safe headphone volume is enabled or not (country specific). --> <bool name="config_safe_media_volume_enabled">false</bool> + <!-- Whether safe headphone sound dosage warning is enabled or not (country specific). --> + <bool name="config_safe_sound_dosage_enabled">false</bool> + </resources> diff --git a/core/res/res/values-mcc316/config.xml b/core/res/res/values-mcc316/config.xml index df398f9aab32..6e0b678f94d9 100644 --- a/core/res/res/values-mcc316/config.xml +++ b/core/res/res/values-mcc316/config.xml @@ -22,4 +22,7 @@ <!-- Whether safe headphone volume is enabled or not (country specific). --> <bool name="config_safe_media_volume_enabled">false</bool> + <!-- Whether safe headphone sound dosage warning is enabled or not (country specific). --> + <bool name="config_safe_sound_dosage_enabled">false</bool> + </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 12dad7eb1faa..7bc3ab80da99 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2105,9 +2105,6 @@ <!-- The default volume for the ring stream --> <integer name="config_audio_ring_vol_default">5</integer> - <!-- Enable sound dose computation and warnings --> - <bool name="config_audio_csd_enabled_default">true</bool> - <!-- The default value for whether head tracking for spatial audio is enabled for a newly connected audio device --> <bool name="config_spatial_audio_head_tracking_enabled_default">false</bool> @@ -2939,6 +2936,9 @@ <!-- Whether safe headphone volume is enabled or not (country specific). --> <bool name="config_safe_media_volume_enabled">true</bool> + <!-- Whether safe headphone sound dosage warning is enabled or not (country specific). --> + <bool name="config_safe_sound_dosage_enabled">true</bool> + <!-- Whether safe headphone volume warning dialog is disabled on Vol+ (operator specific). --> <bool name="config_safe_media_disable_on_volume_up">true</bool> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index bc0af12e9569..ad864b13b16d 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -96,12 +96,12 @@ <!-- Width of the navigation bar when it is placed vertically on the screen in car mode --> <dimen name="navigation_bar_width_car_mode">96dp</dimen> <!-- Height of notification icons in the status bar --> - <dimen name="status_bar_icon_size">22dip</dimen> + <dimen name="status_bar_icon_size">18sp</dimen> <!-- Desired size of system icons in status bar. --> - <dimen name="status_bar_system_icon_size">15dp</dimen> + <dimen name="status_bar_system_icon_size">15sp</dimen> <!-- Intrinsic size of most system icons in status bar. This is the default value that is used if a Drawable reports an intrinsic size of 0. --> - <dimen name="status_bar_system_icon_intrinsic_size">17dp</dimen> + <dimen name="status_bar_system_icon_intrinsic_size">17sp</dimen> <!-- Size of the giant number (unread count) in the notifications --> <dimen name="status_bar_content_number_size">48sp</dimen> <!-- Margin at the edge of the screen to ignore touch events for in the windowshade. --> @@ -330,7 +330,7 @@ <dimen name="notification_icon_circle_start">16dp</dimen> <!-- size (width and height) of the icon in the notification header --> - <dimen name="notification_header_icon_size_ambient">18dp</dimen> + <dimen name="notification_header_icon_size_ambient">18sp</dimen> <!-- The margin before the start of the app name in the header. --> <dimen name="notification_header_app_name_margin_start">3dp</dimen> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index bb10f7ae7c93..e25425d6cb18 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -283,7 +283,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_audio_csd_enabled_default" /> <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" /> @@ -349,6 +348,7 @@ <java-symbol type="bool" name="config_useDevInputEventForAudioJack" /> <java-symbol type="bool" name="config_safe_media_volume_enabled" /> <java-symbol type="bool" name="config_safe_media_disable_on_volume_up" /> + <java-symbol type="bool" name="config_safe_sound_dosage_enabled" /> <java-symbol type="bool" name="config_camera_sound_forced" /> <java-symbol type="bool" name="config_dontPreferApn" /> <java-symbol type="bool" name="config_restartRadioAfterProvisioning" /> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 596f3515dda5..7c2759af156f 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -1183,6 +1183,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/AppTransitionController.java" }, + "-1005167552": { + "message": "Playing #%d in parallel on track #%d", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/TransitionController.java" + }, "-1003678883": { "message": "Cleaning splash screen token=%s", "level": "VERBOSE", @@ -1447,6 +1453,12 @@ "group": "WM_DEBUG_TASKS", "at": "com\/android\/server\/wm\/RootWindowContainer.java" }, + "-774908272": { + "message": "Marking #%d animation as SYNC.", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/TransitionController.java" + }, "-771177730": { "message": "Removing focused app token:%s displayId=%d", "level": "VERBOSE", @@ -1495,12 +1507,6 @@ "group": "WM_DEBUG_CONFIGURATION", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, - "-741766551": { - "message": "Content Recording: Ignoring session on invalid virtual display", - "level": "VERBOSE", - "group": "WM_DEBUG_CONTENT_RECORDING", - "at": "com\/android\/server\/wm\/ContentRecordingController.java" - }, "-732715767": { "message": "Unable to retrieve window container to start recording for display %d", "level": "VERBOSE", @@ -2017,6 +2023,12 @@ "group": "WM_DEBUG_WALLPAPER", "at": "com\/android\/server\/wm\/WallpaperController.java" }, + "-266707683": { + "message": "Moving #%d from collecting to waiting.", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN", + "at": "com\/android\/server\/wm\/TransitionController.java" + }, "-262984451": { "message": "Relaunch failed %s", "level": "INFO", @@ -2089,6 +2101,12 @@ "group": "WM_DEBUG_WINDOW_MOVEMENT", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-186693085": { + "message": "Starting a Recents transition which can be parallel.", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-182877285": { "message": "Wallpaper layer changed: assigning layers + relayout", "level": "VERBOSE", diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index d3f395846894..24fd86b45df4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -297,7 +297,7 @@ public class Bubble implements BubbleViewProvider { public BubbleInfo asBubbleBarBubble() { return new BubbleInfo(getKey(), getFlags(), - getShortcutInfo().getId(), + getShortcutId(), getIcon(), getUser().getIdentifier(), getPackageName()); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java index 0f9260c9deaa..9abf0f678179 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java @@ -38,7 +38,7 @@ public interface TaskStackListenerCallback { default void onTaskStackChanged() { } - default void onTaskProfileLocked(RunningTaskInfo taskInfo) { } + default void onTaskProfileLocked(RunningTaskInfo taskInfo, int userId) { } default void onTaskDisplayChanged(int taskId, int newDisplayId) { } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java index e2106e478bb3..d8859bac471f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java @@ -150,8 +150,8 @@ public class TaskStackListenerImpl extends TaskStackListener implements Handler. } @Override - public void onTaskProfileLocked(ActivityManager.RunningTaskInfo taskInfo) { - mMainHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, taskInfo).sendToTarget(); + public void onTaskProfileLocked(ActivityManager.RunningTaskInfo taskInfo, int userId) { + mMainHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, userId, 0, taskInfo).sendToTarget(); } @Override @@ -348,8 +348,9 @@ public class TaskStackListenerImpl extends TaskStackListener implements Handler. case ON_TASK_PROFILE_LOCKED: { final ActivityManager.RunningTaskInfo info = (ActivityManager.RunningTaskInfo) msg.obj; + final int userId = msg.arg1; for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onTaskProfileLocked(info); + mTaskStackListeners.get(i).onTaskProfileLocked(info, userId); } break; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java index b0dea7231a1e..d27d05b207a6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java @@ -35,6 +35,7 @@ public class BubbleInfo implements Parcelable { private String mKey; // Same key as the Notification private int mFlags; // Flags from BubbleMetadata + @Nullable private String mShortcutId; private int mUserId; private String mPackageName; @@ -46,7 +47,7 @@ public class BubbleInfo implements Parcelable { @Nullable private Icon mIcon; - public BubbleInfo(String key, int flags, String shortcutId, @Nullable Icon icon, + public BubbleInfo(String key, int flags, @Nullable String shortcutId, @Nullable Icon icon, int userId, String packageName) { mKey = key; mFlags = flags; @@ -69,10 +70,12 @@ public class BubbleInfo implements Parcelable { return mKey; } + @Nullable public String getShortcutId() { return mShortcutId; } + @Nullable public Icon getIcon() { return mIcon; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java index 86ea72582a52..b9d2be280efb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java @@ -264,10 +264,10 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll /** * Show apps on desktop */ - void showDesktopApps() { + void showDesktopApps(int displayId) { // Bring apps to front, ignoring their visibility status to always ensure they are on top. WindowContainerTransaction wct = new WindowContainerTransaction(); - bringDesktopAppsToFront(wct); + bringDesktopAppsToFront(displayId, wct); if (!wct.isEmpty()) { if (Transitions.ENABLE_SHELL_TRANSITIONS) { @@ -280,12 +280,12 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll } /** Get number of tasks that are marked as visible */ - int getVisibleTaskCount() { - return mDesktopModeTaskRepository.getVisibleTaskCount(); + int getVisibleTaskCount(int displayId) { + return mDesktopModeTaskRepository.getVisibleTaskCount(displayId); } - private void bringDesktopAppsToFront(WindowContainerTransaction wct) { - final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks(); + private void bringDesktopAppsToFront(int displayId, WindowContainerTransaction wct) { + final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks(displayId); ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size()); final List<RunningTaskInfo> taskInfos = new ArrayList<>(); @@ -386,6 +386,7 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request) { + RunningTaskInfo triggerTask = request.getTriggerTask(); // Only do anything if we are in desktop mode and opening/moving-to-front a task/app in // freeform if (!DesktopModeStatus.isActive(mContext)) { @@ -399,16 +400,15 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll WindowManager.transitTypeToString(request.getType())); return null; } - if (request.getTriggerTask() == null - || request.getTriggerTask().getWindowingMode() != WINDOWING_MODE_FREEFORM) { + if (triggerTask == null || triggerTask.getWindowingMode() != WINDOWING_MODE_FREEFORM) { ProtoLog.d(WM_SHELL_DESKTOP_MODE, "skip shell transition request: not freeform task"); return null; } ProtoLog.d(WM_SHELL_DESKTOP_MODE, "handle shell transition request: %s", request); WindowContainerTransaction wct = new WindowContainerTransaction(); - bringDesktopAppsToFront(wct); - wct.reorder(request.getTriggerTask().token, true /* onTop */); + bringDesktopAppsToFront(triggerTask.displayId, wct); + wct.reorder(triggerTask.token, true /* onTop */); return wct; } @@ -493,16 +493,17 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll mController = null; } - public void showDesktopApps() { + @Override + public void showDesktopApps(int displayId) { executeRemoteCallWithTaskPermission(mController, "showDesktopApps", - DesktopModeController::showDesktopApps); + controller -> controller.showDesktopApps(displayId)); } @Override - public int getVisibleTaskCount() throws RemoteException { + public int getVisibleTaskCount(int displayId) throws RemoteException { int[] result = new int[1]; executeRemoteCallWithTaskPermission(mController, "getVisibleTaskCount", - controller -> result[0] = controller.getVisibleTaskCount(), + controller -> result[0] = controller.getVisibleTaskCount(displayId), true /* blocking */ ); return result[0]; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt index 12f8ea23ac8f..00cc57f0b99c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt @@ -20,6 +20,8 @@ import android.graphics.Region import android.util.ArrayMap import android.util.ArraySet import android.util.SparseArray +import androidx.core.util.forEach +import androidx.core.util.keyIterator import androidx.core.util.valueIterator import java.util.concurrent.Executor import java.util.function.Consumer @@ -29,14 +31,18 @@ import java.util.function.Consumer */ class DesktopModeTaskRepository { - /** - * Set of task ids that are marked as active in desktop mode. - * Active tasks in desktop mode are freeform tasks that are visible or have been visible after - * desktop mode was activated. - * Task gets removed from this list when it vanishes. Or when desktop mode is turned off. - */ - private val activeTasks = ArraySet<Int>() - private val visibleTasks = ArraySet<Int>() + /** Task data that is tracked per display */ + private data class DisplayData( + /** + * Set of task ids that are marked as active in desktop mode. Active tasks in desktop mode + * are freeform tasks that are visible or have been visible after desktop mode was + * activated. Task gets removed from this list when it vanishes. Or when desktop mode is + * turned off. + */ + val activeTasks: ArraySet<Int> = ArraySet(), + val visibleTasks: ArraySet<Int> = ArraySet(), + ) + // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0). private val freeformTasksInZOrder = mutableListOf<Int>() private val activeTasksListeners = ArraySet<ActiveTasksListener>() @@ -47,9 +53,22 @@ class DesktopModeTaskRepository { private var desktopGestureExclusionListener: Consumer<Region>? = null private var desktopGestureExclusionExecutor: Executor? = null - /** - * Add a [ActiveTasksListener] to be notified of updates to active tasks in the repository. - */ + private val displayData = + object : SparseArray<DisplayData>() { + /** + * Get the [DisplayData] associated with this [displayId] + * + * Creates a new instance if one does not exist + */ + fun getOrCreate(displayId: Int): DisplayData { + if (!contains(displayId)) { + put(displayId, DisplayData()) + } + return get(displayId) + } + } + + /** Add a [ActiveTasksListener] to be notified of updates to active tasks in the repository. */ fun addActiveTaskListener(activeTasksListener: ActiveTasksListener) { activeTasksListeners.add(activeTasksListener) } @@ -57,10 +76,17 @@ class DesktopModeTaskRepository { /** * Add a [VisibleTasksListener] to be notified when freeform tasks are visible or not. */ - fun addVisibleTasksListener(visibleTasksListener: VisibleTasksListener, executor: Executor) { - visibleTasksListeners.put(visibleTasksListener, executor) - executor.execute( - Runnable { visibleTasksListener.onVisibilityChanged(visibleTasks.size > 0) }) + fun addVisibleTasksListener( + visibleTasksListener: VisibleTasksListener, + executor: Executor + ) { + visibleTasksListeners[visibleTasksListener] = executor + displayData.keyIterator().forEach { displayId -> + val visibleTasks = getVisibleTaskCount(displayId) + executor.execute { + visibleTasksListener.onVisibilityChanged(displayId, visibleTasks > 0) + } + } } /** @@ -100,14 +126,21 @@ class DesktopModeTaskRepository { } /** - * Mark a task with given [taskId] as active. + * Mark a task with given [taskId] as active on given [displayId] * - * @return `true` if the task was not active + * @return `true` if the task was not active on given [displayId] */ - fun addActiveTask(taskId: Int): Boolean { - val added = activeTasks.add(taskId) + fun addActiveTask(displayId: Int, taskId: Int): Boolean { + // Check if task is active on another display, if so, remove it + displayData.forEach { id, data -> + if (id != displayId && data.activeTasks.remove(taskId)) { + activeTasksListeners.onEach { it.onActiveTasksChanged(id) } + } + } + + val added = displayData.getOrCreate(displayId).activeTasks.add(taskId) if (added) { - activeTasksListeners.onEach { it.onActiveTasksChanged() } + activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) } } return added } @@ -118,65 +151,93 @@ class DesktopModeTaskRepository { * @return `true` if the task was active */ fun removeActiveTask(taskId: Int): Boolean { - val removed = activeTasks.remove(taskId) - if (removed) { - activeTasksListeners.onEach { it.onActiveTasksChanged() } + var result = false + displayData.forEach { displayId, data -> + if (data.activeTasks.remove(taskId)) { + activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) } + result = true + } } - return removed + return result } /** * Check if a task with the given [taskId] was marked as an active task */ fun isActiveTask(taskId: Int): Boolean { - return activeTasks.contains(taskId) + return displayData.valueIterator().asSequence().any { data -> + data.activeTasks.contains(taskId) + } } /** * Whether a task is visible. */ fun isVisibleTask(taskId: Int): Boolean { - return visibleTasks.contains(taskId) + return displayData.valueIterator().asSequence().any { data -> + data.visibleTasks.contains(taskId) + } } /** - * Get a set of the active tasks + * Get a set of the active tasks for given [displayId] */ - fun getActiveTasks(): ArraySet<Int> { - return ArraySet(activeTasks) + fun getActiveTasks(displayId: Int): ArraySet<Int> { + return ArraySet(displayData[displayId]?.activeTasks) } /** * Get a list of freeform tasks, ordered from top-bottom (top at index 0). */ + // TODO(b/278084491): pass in display id fun getFreeformTasksInZOrder(): List<Int> { return freeformTasksInZOrder } /** * Updates whether a freeform task with this id is visible or not and notifies listeners. + * + * If the task was visible on a different display with a different displayId, it is removed from + * the set of visible tasks on that display. Listeners will be notified. */ - fun updateVisibleFreeformTasks(taskId: Int, visible: Boolean) { - val prevCount: Int = visibleTasks.size + fun updateVisibleFreeformTasks(displayId: Int, taskId: Int, visible: Boolean) { if (visible) { - visibleTasks.add(taskId) + // Task is visible. Check if we need to remove it from any other display. + val otherDisplays = displayData.keyIterator().asSequence().filter { it != displayId } + for (otherDisplayId in otherDisplays) { + if (displayData[otherDisplayId].visibleTasks.remove(taskId)) { + // Task removed from other display, check if we should notify listeners + if (displayData[otherDisplayId].visibleTasks.isEmpty()) { + notifyVisibleTaskListeners(otherDisplayId, hasVisibleFreeformTasks = false) + } + } + } + } + + val prevCount = getVisibleTaskCount(displayId) + if (visible) { + displayData.getOrCreate(displayId).visibleTasks.add(taskId) } else { - visibleTasks.remove(taskId) + displayData[displayId]?.visibleTasks?.remove(taskId) } - if (prevCount == 0 && visibleTasks.size == 1 || - prevCount > 0 && visibleTasks.size == 0) { - for ((listener, executor) in visibleTasksListeners) { - executor.execute( - Runnable { listener.onVisibilityChanged(visibleTasks.size > 0) }) - } + val newCount = getVisibleTaskCount(displayId) + // Check if count changed and if there was no tasks or this is the first task + if (prevCount != newCount && (prevCount == 0 || newCount == 0)) { + notifyVisibleTaskListeners(displayId, newCount > 0) + } + } + + private fun notifyVisibleTaskListeners(displayId: Int, hasVisibleFreeformTasks: Boolean) { + visibleTasksListeners.forEach { (listener, executor) -> + executor.execute { listener.onVisibilityChanged(displayId, hasVisibleFreeformTasks) } } } /** - * Get number of tasks that are marked as visible + * Get number of tasks that are marked as visible on given [displayId] */ - fun getVisibleTaskCount(): Int { - return visibleTasks.size + fun getVisibleTaskCount(displayId: Int): Int { + return displayData[displayId]?.visibleTasks?.size ?: 0 } /** @@ -226,7 +287,7 @@ class DesktopModeTaskRepository { * Called when the active tasks change in desktop mode. */ @JvmDefault - fun onActiveTasksChanged() {} + fun onActiveTasksChanged(displayId: Int) {} } /** @@ -237,6 +298,6 @@ class DesktopModeTaskRepository { * Called when the desktop starts or stops showing freeform tasks. */ @JvmDefault - fun onVisibilityChanged(hasVisibleFreeformTasks: Boolean) {} + fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {} } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 0d5602365578..c814fe575e81 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -97,10 +97,11 @@ class DesktopTasksController( } /** Show all tasks, that are part of the desktop, on top of launcher */ - fun showDesktopApps() { + fun showDesktopApps(displayId: Int) { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "showDesktopApps") val wct = WindowContainerTransaction() - bringDesktopAppsToFront(wct) + // TODO(b/278084491): pass in display id + bringDesktopAppsToFront(displayId, wct) // Execute transaction if there are pending operations if (!wct.isEmpty) { @@ -114,8 +115,8 @@ class DesktopTasksController( } /** Get number of tasks that are marked as visible */ - fun getVisibleTaskCount(): Int { - return desktopModeTaskRepository.getVisibleTaskCount() + fun getVisibleTaskCount(displayId: Int): Int { + return desktopModeTaskRepository.getVisibleTaskCount(displayId) } /** Move a task with given `taskId` to desktop */ @@ -129,7 +130,7 @@ class DesktopTasksController( val wct = WindowContainerTransaction() // Bring other apps to front first - bringDesktopAppsToFront(wct) + bringDesktopAppsToFront(task.displayId, wct) addMoveToDesktopChanges(wct, task.token) if (Transitions.ENABLE_SHELL_TRANSITIONS) { transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) @@ -165,7 +166,7 @@ class DesktopTasksController( freeformBounds: Rect ) { val wct = WindowContainerTransaction() - bringDesktopAppsToFront(wct) + bringDesktopAppsToFront(taskInfo.displayId, wct) addMoveToDesktopChanges(wct, taskInfo.getToken()) wct.setBounds(taskInfo.token, freeformBounds) @@ -244,9 +245,9 @@ class DesktopTasksController( ?: WINDOWING_MODE_UNDEFINED } - private fun bringDesktopAppsToFront(wct: WindowContainerTransaction) { + private fun bringDesktopAppsToFront(displayId: Int, wct: WindowContainerTransaction) { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront") - val activeTasks = desktopModeTaskRepository.getActiveTasks() + val activeTasks = desktopModeTaskRepository.getActiveTasks(displayId) // First move home to front and then other tasks on top of it moveHomeTaskToFront(wct) @@ -290,18 +291,17 @@ class DesktopTasksController( request: TransitionRequestInfo ): WindowContainerTransaction? { // Check if we should skip handling this transition - val task: RunningTaskInfo? = request.triggerTask val shouldHandleRequest = when { // Only handle open or to front transitions request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> false // Only handle when it is a task transition - task == null -> false + request.triggerTask == null -> false // Only handle standard type tasks - task.activityType != ACTIVITY_TYPE_STANDARD -> false + request.triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> false // Only handle fullscreen or freeform tasks - task.windowingMode != WINDOWING_MODE_FULLSCREEN && - task.windowingMode != WINDOWING_MODE_FREEFORM -> false + request.triggerTask.windowingMode != WINDOWING_MODE_FULLSCREEN && + request.triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> false // Otherwise process it else -> true } @@ -310,10 +310,11 @@ class DesktopTasksController( return null } - val activeTasks = desktopModeTaskRepository.getActiveTasks() + val task: RunningTaskInfo = request.triggerTask + val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId) // Check if we should switch a fullscreen task to freeform - if (task?.windowingMode == WINDOWING_MODE_FULLSCREEN) { + if (task.windowingMode == WINDOWING_MODE_FULLSCREEN) { // If there are any visible desktop tasks, switch the task to freeform if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) { ProtoLog.d( @@ -329,7 +330,7 @@ class DesktopTasksController( } // CHeck if we should switch a freeform task to fullscreen - if (task?.windowingMode == WINDOWING_MODE_FREEFORM) { + if (task.windowingMode == WINDOWING_MODE_FREEFORM) { // If no visible desktop tasks, switch this task to freeform as the transition came // outside of this controller if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) { @@ -559,20 +560,19 @@ class DesktopTasksController( controller = null } - override fun showDesktopApps() { + override fun showDesktopApps(displayId: Int) { ExecutorUtils.executeRemoteCallWithTaskPermission( controller, - "showDesktopApps", - Consumer(DesktopTasksController::showDesktopApps) - ) + "showDesktopApps" + ) { c -> c.showDesktopApps(displayId) } } - override fun getVisibleTaskCount(): Int { + override fun getVisibleTaskCount(displayId: Int): Int { val result = IntArray(1) ExecutorUtils.executeRemoteCallWithTaskPermission( controller, "getVisibleTaskCount", - { controller -> result[0] = controller.getVisibleTaskCount() }, + { controller -> result[0] = controller.getVisibleTaskCount(displayId) }, true /* blocking */ ) return result[0] diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl index d0739e14675f..899d67267e69 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl @@ -21,9 +21,9 @@ package com.android.wm.shell.desktopmode; */ interface IDesktopMode { - /** Show apps on the desktop */ - void showDesktopApps(); + /** Show apps on the desktop on the given display */ + void showDesktopApps(int displayId); - /** Get count of visible desktop tasks */ - int getVisibleTaskCount(); + /** Get count of visible desktop tasks on the given display */ + int getVisibleTaskCount(int displayId); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index 48487bc4a3d6..22541bbd892a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -94,11 +94,12 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, mDesktopModeTaskRepository.ifPresent(repository -> { repository.addOrMoveFreeformTaskToTop(taskInfo.taskId); if (taskInfo.isVisible) { - if (repository.addActiveTask(taskInfo.taskId)) { + if (repository.addActiveTask(taskInfo.displayId, taskInfo.taskId)) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "Adding active freeform task: #%d", taskInfo.taskId); } - repository.updateVisibleFreeformTasks(taskInfo.taskId, true); + repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId, + true); } }); } @@ -117,7 +118,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "Removing active freeform task: #%d", taskInfo.taskId); } - repository.updateVisibleFreeformTasks(taskInfo.taskId, false); + repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId, false); }); } @@ -137,12 +138,13 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, if (DesktopModeStatus.isAnyEnabled()) { mDesktopModeTaskRepository.ifPresent(repository -> { if (taskInfo.isVisible) { - if (repository.addActiveTask(taskInfo.taskId)) { + if (repository.addActiveTask(taskInfo.displayId, taskInfo.taskId)) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "Adding active freeform task: #%d", taskInfo.taskId); } } - repository.updateVisibleFreeformTasks(taskInfo.taskId, taskInfo.isVisible); + repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId, + taskInfo.isVisible); }); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 566c130c7573..d3e7f9ca4227 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -72,7 +72,6 @@ import android.window.TaskOrganizer; import android.window.TaskSnapshot; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; -import android.window.WindowContainerTransactionCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; @@ -139,17 +138,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // the runnable to execute after WindowContainerTransactions is applied to finish resizing pip private Runnable mPipFinishResizeWCTRunnable; - private final WindowContainerTransactionCallback mPipFinishResizeWCTCallback = - new WindowContainerTransactionCallback() { - @Override - public void onTransactionReady(int id, SurfaceControl.Transaction t) { - t.apply(); - - // execute the runnable if non-null after WCT is applied to finish resizing pip - maybePerformFinishResizeCallback(); - } - }; - private void maybePerformFinishResizeCallback() { if (mPipFinishResizeWCTRunnable != null) { mPipFinishResizeWCTRunnable.run(); @@ -175,6 +163,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, final int direction = animator.getTransitionDirection(); if (mIsCancelled) { sendOnPipTransitionFinished(direction); + maybePerformFinishResizeCallback(); return; } final int animationType = animator.getAnimationType(); @@ -199,6 +188,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, || isRemovePipDirection(direction); if (mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP || isExitPipDirection) { + // execute the finish resize callback if needed after the transaction is committed + tx.addTransactionCommittedListener(mMainExecutor, + PipTaskOrganizer.this::maybePerformFinishResizeCallback); + // Finish resize as long as we're not exiting PIP, or, if we are, only if this is // the end of an exit PIP animation. // This is necessary in case there was a resize animation ongoing when exit PIP @@ -1614,12 +1607,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) { mSplitScreenOptional.ifPresent(splitScreenController -> splitScreenController.enterSplitScreen(mTaskInfo.taskId, wasPipTopLeft, wct)); - } else if (direction == TRANSITION_DIRECTION_LEAVE_PIP) { - // when leaving PiP we can call the callback without sync - maybePerformFinishResizeCallback(); - mTaskOrganizer.applyTransaction(wct); } else { - mTaskOrganizer.applySyncTransaction(wct, mPipFinishResizeWCTCallback); + mTaskOrganizer.applyTransaction(wct); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index b0bb14b49db6..f8e143575583 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -1060,13 +1060,22 @@ public class PipController implements PipTransitionController.PipTransitionCallb /** Save the state to restore to on re-entry. */ public void saveReentryState(Rect pipBounds) { float snapFraction = mPipBoundsAlgorithm.getSnapFraction(pipBounds); - if (mPipBoundsState.hasUserResizedPip()) { - final Rect reentryBounds = mTouchHandler.getUserResizeBounds(); - final Size reentrySize = new Size(reentryBounds.width(), reentryBounds.height()); - mPipBoundsState.saveReentryState(reentrySize, snapFraction); - } else { + + if (!mPipBoundsState.hasUserResizedPip()) { mPipBoundsState.saveReentryState(null /* bounds */, snapFraction); + return; } + + Size reentrySize = new Size(pipBounds.width(), pipBounds.height()); + + // TODO: b/279937014 Investigate why userResizeBounds are empty with shell transitions on + // fallback to using the userResizeBounds if userResizeBounds are not empty + if (!mTouchHandler.getUserResizeBounds().isEmpty()) { + Rect userResizeBounds = mTouchHandler.getUserResizeBounds(); + reentrySize = new Size(userResizeBounds.width(), userResizeBounds.height()); + } + + mPipBoundsState.saveReentryState(reentrySize, snapFraction); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index 956af709f156..281cae5e4ffa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -104,6 +104,7 @@ public class PipResizeGestureHandler { private boolean mAllowGesture; private boolean mIsAttached; private boolean mIsEnabled; + private boolean mEnableTouch; private boolean mEnablePinchResize; private boolean mEnableDragCornerResize; private boolean mIsSysUiStateValid; @@ -138,6 +139,7 @@ public class PipResizeGestureHandler { mPhonePipMenuController = menuActivityController; mPipUiEventLogger = pipUiEventLogger; mPinchResizingAlgorithm = new PipPinchResizingAlgorithm(); + mEnableTouch = true; mUpdateResizeBoundsCallback = (rect) -> { mUserResizeBounds.set(rect); @@ -248,6 +250,11 @@ public class PipResizeGestureHandler { return; } + if (!mEnableTouch) { + // No need to handle anything if touches are not enabled for resizing. + return; + } + // Don't allow resize when PiP is stashed. if (mPipBoundsState.isStashed()) { return; @@ -581,14 +588,13 @@ public class PipResizeGestureHandler { mLastResizeBounds, movementBounds); mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction); - // disable the pinch resizing until the final bounds are updated - final boolean prevEnablePinchResize = mEnablePinchResize; - mEnablePinchResize = false; + // disable the resizing until the final bounds are updated + mEnableTouch = false; mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds, PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback, () -> { // reset the pinch resizing to its default state - mEnablePinchResize = prevEnablePinchResize; + mEnableTouch = true; }); } else { mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index c5bfd8753994..5c9709c756f7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -245,7 +245,7 @@ public class RecentTasksController implements TaskStackListenerCallback, } @Override - public void onActiveTasksChanged() { + public void onActiveTasksChanged(int displayId) { notifyRecentTasksChanged(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java index 7991c529aa49..2ab4c751d399 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java @@ -82,6 +82,10 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { mGuard.open("release"); } + SurfaceControl getSurfaceControl() { + return mSurfaceControl; + } + /** * Sets the provided {@link TaskViewBase}, which is used to notify the client part about the * task related changes and getting the current bounds. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java index 81d69a4fa611..c7e534a2bcf5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java @@ -336,6 +336,19 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { tv.prepareOpenAnimation(taskIsNew, startTransaction, finishTransaction, chg.getTaskInfo(), chg.getLeash(), wct); changesHandled++; + } else if (chg.getMode() == TRANSIT_CHANGE) { + TaskViewTaskController tv = findTaskView(chg.getTaskInfo()); + if (tv == null) { + if (pending != null) { + Slog.w(TAG, "Found a non-TaskView task in a TaskView Transition. This " + + "shouldn't happen, so there may be a visual artifact: " + + chg.getTaskInfo().taskId); + } + continue; + } + startTransaction.reparent(chg.getLeash(), tv.getSurfaceControl()); + finishTransaction.reparent(chg.getLeash(), tv.getSurfaceControl()); + changesHandled++; } } if (stillNeedsMatchingLaunch) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java index 9d7c39f1c90e..ba364f8a6e59 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java @@ -73,7 +73,8 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler { if (mHandlerIds.containsKey(handler)) { handlerId = mHandlerIds.get(handler); } else { - handlerId = mHandlerIds.size(); + // + 1 to avoid 0 ids which can be confused with missing value when dumped to proto + handlerId = mHandlerIds.size() + 1; mHandlerIds.put(handler, handlerId); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java index ef8332f5e347..1d416c65851b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java @@ -86,9 +86,10 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback { public void onDragPositioningEnd(float x, float y) { PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y, mRepositionStartPoint); - if (mHasMoved && DragPositioningCallbackUtility.changeBounds(mCtrlType, mHasMoved, - mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta, - mDisplayController, mDesktopWindowDecoration)) { + if (mHasMoved) { + DragPositioningCallbackUtility.changeBounds(mCtrlType, mHasMoved, + mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta, + mDisplayController, mDesktopWindowDecoration); DragPositioningCallbackUtility.applyTaskBoundsChange( new WindowContainerTransaction(), mDesktopWindowDecoration, mRepositionTaskBounds, mTaskOrganizer); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java index de967bfa288b..afec1ee12341 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java @@ -33,6 +33,7 @@ import android.content.pm.ShortcutInfo; import android.content.res.Resources; import android.graphics.drawable.Icon; import android.os.Bundle; +import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -42,6 +43,7 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.R; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.bubbles.BubbleInfo; import org.junit.Before; import org.junit.Test; @@ -77,7 +79,7 @@ public class BubbleTest extends ShellTestCase { Intent target = new Intent(mContext, BubblesTestActivity.class); Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder( PendingIntent.getActivity(mContext, 0, target, PendingIntent.FLAG_MUTABLE), - Icon.createWithResource(mContext, R.drawable.bubble_ic_create_bubble)) + Icon.createWithResource(mContext, R.drawable.bubble_ic_create_bubble)) .build(); when(mSbn.getNotification()).thenReturn(mNotif); when(mNotif.getBubbleMetadata()).thenReturn(metadata); @@ -179,6 +181,34 @@ public class BubbleTest extends ShellTestCase { assertThat(bubble.isConversation()).isFalse(); } + @Test + public void testBubbleAsBubbleBarBubble_withShortcut() { + Bubble bubble = createBubbleWithShortcut(); + BubbleInfo bubbleInfo = bubble.asBubbleBarBubble(); + + assertThat(bubble.getShortcutInfo()).isNotNull(); + assertThat(bubbleInfo.getShortcutId()).isNotNull(); + assertThat(bubbleInfo.getShortcutId()).isEqualTo(bubble.getShortcutId()); + assertThat(bubbleInfo.getKey()).isEqualTo(bubble.getKey()); + assertThat(bubbleInfo.getUserId()).isEqualTo(bubble.getUser().getIdentifier()); + assertThat(bubbleInfo.getPackageName()).isEqualTo(bubble.getPackageName()); + } + + @Test + public void testBubbleAsBubbleBarBubble_withoutShortcut() { + Intent intent = new Intent(mContext, BubblesTestActivity.class); + intent.setPackage(mContext.getPackageName()); + Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1 /* userId */), + null /* icon */, mMainExecutor); + BubbleInfo bubbleInfo = bubble.asBubbleBarBubble(); + + assertThat(bubble.getShortcutInfo()).isNull(); + assertThat(bubbleInfo.getShortcutId()).isNull(); + assertThat(bubbleInfo.getKey()).isEqualTo(bubble.getKey()); + assertThat(bubbleInfo.getUserId()).isEqualTo(bubble.getUser().getIdentifier()); + assertThat(bubbleInfo.getPackageName()).isEqualTo(bubble.getPackageName()); + } + private Bubble createBubbleWithShortcut() { ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext) .setId("mockShortcutId") diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java index 1347e061eb45..60ee918ee545 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java @@ -112,9 +112,9 @@ public class TaskStackListenerImplTest extends ShellTestCase { @Test public void testOnTaskProfileLocked() { ActivityManager.RunningTaskInfo info = mock(ActivityManager.RunningTaskInfo.class); - mImpl.onTaskProfileLocked(info); - verify(mCallback).onTaskProfileLocked(eq(info)); - verify(mOtherCallback).onTaskProfileLocked(eq(info)); + mImpl.onTaskProfileLocked(info, 0); + verify(mCallback).onTaskProfileLocked(eq(info), eq(0)); + verify(mOtherCallback).onTaskProfileLocked(eq(info), eq(0)); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java index 63de74fa3b05..d6387ee5ae13 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java @@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS; +import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_NONE; @@ -82,6 +83,8 @@ import java.util.Arrays; @RunWith(AndroidTestingRunner.class) public class DesktopModeControllerTest extends ShellTestCase { + private static final int SECOND_DISPLAY = 2; + @Mock private ShellController mShellController; @Mock @@ -248,22 +251,22 @@ public class DesktopModeControllerTest extends ShellTestCase { public void testShowDesktopApps_allAppsInvisible_bringsToFront() { // Set up two active tasks on desktop, task2 is on top of task1. RunningTaskInfo freeformTask1 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(freeformTask1.taskId); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, freeformTask1.taskId); mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask1.taskId); mDesktopModeTaskRepository.updateVisibleFreeformTasks( - freeformTask1.taskId, false /* visible */); + DEFAULT_DISPLAY, freeformTask1.taskId, false /* visible */); RunningTaskInfo freeformTask2 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(freeformTask2.taskId); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, freeformTask2.taskId); mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask2.taskId); mDesktopModeTaskRepository.updateVisibleFreeformTasks( - freeformTask2.taskId, false /* visible */); + DEFAULT_DISPLAY, freeformTask2.taskId, false /* visible */); when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask1.taskId)).thenReturn( freeformTask1); when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask2.taskId)).thenReturn( freeformTask2); // Run show desktop apps logic - mController.showDesktopApps(); + mController.showDesktopApps(DEFAULT_DISPLAY); final WindowContainerTransaction wct = getBringAppsToFrontTransaction(); // Check wct has reorder calls @@ -283,17 +286,19 @@ public class DesktopModeControllerTest extends ShellTestCase { @Test public void testShowDesktopApps_appsAlreadyVisible_bringsToFront() { final RunningTaskInfo task1 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(task1.taskId); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId); mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId, + true /* visible */); when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1); final RunningTaskInfo task2 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(task2.taskId); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId); mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId, + true /* visible */); when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2); - mController.showDesktopApps(); + mController.showDesktopApps(DEFAULT_DISPLAY); final WindowContainerTransaction wct = getBringAppsToFrontTransaction(); // Check wct has reorder calls @@ -312,17 +317,19 @@ public class DesktopModeControllerTest extends ShellTestCase { @Test public void testShowDesktopApps_someAppsInvisible_reordersAll() { final RunningTaskInfo task1 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(task1.taskId); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId); mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, false /* visible */); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId, + false /* visible */); when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1); final RunningTaskInfo task2 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(task2.taskId); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId); mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId, + true /* visible */); when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2); - mController.showDesktopApps(); + mController.showDesktopApps(DEFAULT_DISPLAY); final WindowContainerTransaction wct = getBringAppsToFrontTransaction(); // Both tasks should be reordered to top, even if one was already visible. @@ -336,38 +343,87 @@ public class DesktopModeControllerTest extends ShellTestCase { } @Test + public void testShowDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay() { + RunningTaskInfo taskDefaultDisplay = createFreeformTask(DEFAULT_DISPLAY); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, taskDefaultDisplay.taskId); + mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskDefaultDisplay.taskId); + mDesktopModeTaskRepository.updateVisibleFreeformTasks( + DEFAULT_DISPLAY, taskDefaultDisplay.taskId, false /* visible */); + when(mShellTaskOrganizer.getRunningTaskInfo(taskDefaultDisplay.taskId)).thenReturn( + taskDefaultDisplay); + + RunningTaskInfo taskSecondDisplay = createFreeformTask(SECOND_DISPLAY); + mDesktopModeTaskRepository.addActiveTask(SECOND_DISPLAY, taskSecondDisplay.taskId); + mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskSecondDisplay.taskId); + mDesktopModeTaskRepository.updateVisibleFreeformTasks( + SECOND_DISPLAY, taskSecondDisplay.taskId, false /* visible */); + when(mShellTaskOrganizer.getRunningTaskInfo(taskSecondDisplay.taskId)).thenReturn( + taskSecondDisplay); + + mController.showDesktopApps(DEFAULT_DISPLAY); + + WindowContainerTransaction wct = getBringAppsToFrontTransaction(); + assertThat(wct.getHierarchyOps()).hasSize(1); + HierarchyOp op = wct.getHierarchyOps().get(0); + assertThat(op.getContainer()).isEqualTo(taskDefaultDisplay.token.asBinder()); + } + + @Test public void testGetVisibleTaskCount_noTasks_returnsZero() { - assertThat(mController.getVisibleTaskCount()).isEqualTo(0); + assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0); } @Test public void testGetVisibleTaskCount_twoTasks_bothVisible_returnsTwo() { RunningTaskInfo task1 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(task1.taskId); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId); mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId, + true /* visible */); RunningTaskInfo task2 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(task2.taskId); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId); mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId, + true /* visible */); - assertThat(mController.getVisibleTaskCount()).isEqualTo(2); + assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2); } @Test public void testGetVisibleTaskCount_twoTasks_oneVisible_returnsOne() { RunningTaskInfo task1 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(task1.taskId); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId); mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId, + true /* visible */); RunningTaskInfo task2 = createFreeformTask(); - mDesktopModeTaskRepository.addActiveTask(task2.taskId); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId); mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId); - mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, false /* visible */); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId, + false /* visible */); - assertThat(mController.getVisibleTaskCount()).isEqualTo(1); + assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1); + } + + @Test + public void testGetVisibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() { + RunningTaskInfo taskDefaultDisplay = createFreeformTask(); + mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, taskDefaultDisplay.taskId); + mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskDefaultDisplay.taskId); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, + taskDefaultDisplay.taskId, + true /* visible */); + + RunningTaskInfo taskSecondDisplay = createFreeformTask(); + mDesktopModeTaskRepository.addActiveTask(SECOND_DISPLAY, taskSecondDisplay.taskId); + mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskSecondDisplay.taskId); + mDesktopModeTaskRepository.updateVisibleFreeformTasks(SECOND_DISPLAY, + taskSecondDisplay.taskId, + true /* visible */); + + assertThat(mController.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt index 45cb3a062cc5..3bc2f0e8674e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt @@ -17,10 +17,12 @@ package com.android.wm.shell.desktopmode import android.testing.AndroidTestingRunner +import android.view.Display.DEFAULT_DISPLAY import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestShellExecutor import com.google.common.truth.Truth.assertThat +import junit.framework.Assert.fail import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -41,8 +43,8 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { val listener = TestListener() repo.addActiveTaskListener(listener) - repo.addActiveTask(1) - assertThat(listener.activeTaskChangedCalls).isEqualTo(1) + repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1) + assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(1) assertThat(repo.isActiveTask(1)).isTrue() } @@ -51,9 +53,9 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { val listener = TestListener() repo.addActiveTaskListener(listener) - repo.addActiveTask(1) - repo.addActiveTask(1) - assertThat(listener.activeTaskChangedCalls).isEqualTo(1) + repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1) + repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1) + assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(1) } @Test @@ -61,9 +63,22 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { val listener = TestListener() repo.addActiveTaskListener(listener) - repo.addActiveTask(1) - repo.addActiveTask(2) - assertThat(listener.activeTaskChangedCalls).isEqualTo(2) + repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1) + repo.addActiveTask(DEFAULT_DISPLAY, taskId = 2) + assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(2) + } + + @Test + fun addActiveTask_multipleDisplays_notifiesCorrectListener() { + val listener = TestListener() + repo.addActiveTaskListener(listener) + + repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1) + repo.addActiveTask(DEFAULT_DISPLAY, taskId = 2) + repo.addActiveTask(SECOND_DISPLAY, taskId = 3) + + assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(2) + assertThat(listener.activeChangesOnSecondaryDisplay).isEqualTo(1) } @Test @@ -71,10 +86,10 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { val listener = TestListener() repo.addActiveTaskListener(listener) - repo.addActiveTask(1) + repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1) repo.removeActiveTask(1) // Notify once for add and once for remove - assertThat(listener.activeTaskChangedCalls).isEqualTo(2) + assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(2) assertThat(repo.isActiveTask(1)).isFalse() } @@ -83,7 +98,17 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { val listener = TestListener() repo.addActiveTaskListener(listener) repo.removeActiveTask(99) - assertThat(listener.activeTaskChangedCalls).isEqualTo(0) + assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(0) + } + + @Test + fun remoteActiveTask_listenerForOtherDisplayNotNotified() { + val listener = TestListener() + repo.addActiveTaskListener(listener) + repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1) + repo.removeActiveTask(1) + assertThat(listener.activeChangesOnSecondaryDisplay).isEqualTo(0) + assertThat(repo.isActiveTask(1)).isFalse() } @Test @@ -93,14 +118,27 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { @Test fun addListener_notifiesVisibleFreeformTask() { - repo.updateVisibleFreeformTasks(1, true) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) val listener = TestVisibilityListener() val executor = TestShellExecutor() repo.addVisibleTasksListener(listener, executor) executor.flushAll() - assertThat(listener.hasVisibleFreeformTasks).isTrue() - assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(1) + assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue() + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1) + } + + @Test + fun addListener_tasksOnDifferentDisplay_doesNotNotify() { + repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true) + val listener = TestVisibilityListener() + val executor = TestShellExecutor() + repo.addVisibleTasksListener(listener, executor) + executor.flushAll() + + assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse() + // One call as adding listener notifies it + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(0) } @Test @@ -108,13 +146,61 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { val listener = TestVisibilityListener() val executor = TestShellExecutor() repo.addVisibleTasksListener(listener, executor) - repo.updateVisibleFreeformTasks(1, true) - repo.updateVisibleFreeformTasks(2, true) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true) + executor.flushAll() + + assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue() + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1) + } + + @Test + fun updateVisibleFreeformTasks_addVisibleTaskNotifiesListenerForThatDisplay() { + val listener = TestVisibilityListener() + val executor = TestShellExecutor() + repo.addVisibleTasksListener(listener, executor) + + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + executor.flushAll() + + assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue() + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1) + assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isFalse() + assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(0) + + repo.updateVisibleFreeformTasks(displayId = 1, taskId = 2, visible = true) + executor.flushAll() + + // Listener for secondary display is notified + assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isTrue() + assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1) + // No changes to listener for default display + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1) + } + + @Test + fun updateVisibleFreeformTasks_taskOnDefaultBecomesVisibleOnSecondDisplay_listenersNotified() { + val listener = TestVisibilityListener() + val executor = TestShellExecutor() + repo.addVisibleTasksListener(listener, executor) + + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) executor.flushAll() + assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue() + + // Mark task 1 visible on secondary display + repo.updateVisibleFreeformTasks(displayId = 1, taskId = 1, visible = true) + executor.flushAll() + + // Default display should have 2 calls + // 1 - visible task added + // 2 - visible task removed + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2) + assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse() - assertThat(listener.hasVisibleFreeformTasks).isTrue() - // Equal to 2 because adding the listener notifies the current state - assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(2) + // Secondary display should have 1 call for visible task added + assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1) + assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isTrue() } @Test @@ -122,52 +208,83 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { val listener = TestVisibilityListener() val executor = TestShellExecutor() repo.addVisibleTasksListener(listener, executor) - repo.updateVisibleFreeformTasks(1, true) - repo.updateVisibleFreeformTasks(2, true) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true) executor.flushAll() - assertThat(listener.hasVisibleFreeformTasks).isTrue() - repo.updateVisibleFreeformTasks(1, false) + assertThat(listener.hasVisibleTasksOnDefaultDisplay).isTrue() + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false) executor.flushAll() - // Equal to 2 because adding the listener notifies the current state - assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(2) + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1) - repo.updateVisibleFreeformTasks(2, false) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = false) executor.flushAll() - assertThat(listener.hasVisibleFreeformTasks).isFalse() - assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(3) + assertThat(listener.hasVisibleTasksOnDefaultDisplay).isFalse() + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2) } @Test fun getVisibleTaskCount() { // No tasks, count is 0 - assertThat(repo.getVisibleTaskCount()).isEqualTo(0) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) // New task increments count to 1 - repo.updateVisibleFreeformTasks(taskId = 1, visible = true) - assertThat(repo.getVisibleTaskCount()).isEqualTo(1) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) // Visibility update to same task does not increase count - repo.updateVisibleFreeformTasks(taskId = 1, visible = true) - assertThat(repo.getVisibleTaskCount()).isEqualTo(1) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) // Second task visible increments count - repo.updateVisibleFreeformTasks(taskId = 2, visible = true) - assertThat(repo.getVisibleTaskCount()).isEqualTo(2) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2) // Hiding a task decrements count - repo.updateVisibleFreeformTasks(taskId = 1, visible = false) - assertThat(repo.getVisibleTaskCount()).isEqualTo(1) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) // Hiding all tasks leaves count at 0 - repo.updateVisibleFreeformTasks(taskId = 2, visible = false) - assertThat(repo.getVisibleTaskCount()).isEqualTo(0) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = false) + assertThat(repo.getVisibleTaskCount(displayId = 9)).isEqualTo(0) // Hiding a not existing task, count remains at 0 - repo.updateVisibleFreeformTasks(taskId = 999, visible = false) - assertThat(repo.getVisibleTaskCount()).isEqualTo(0) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 999, visible = false) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) + } + + @Test + fun getVisibleTaskCount_multipleDisplays() { + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) + assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0) + + // New task on default display increments count for that display only + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) + assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0) + + // New task on secondary display, increments count for that display only + repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 2, visible = true) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) + assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1) + + // Marking task visible on another display, updates counts for both displays + repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) + assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2) + + // Marking task that is on secondary display, hidden on default display, does not affect + // secondary display + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) + assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2) + + // Hiding a task on that display, decrements count + repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = false) + assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) + assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1) } @Test @@ -197,19 +314,40 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { } class TestListener : DesktopModeTaskRepository.ActiveTasksListener { - var activeTaskChangedCalls = 0 - override fun onActiveTasksChanged() { - activeTaskChangedCalls++ + var activeChangesOnDefaultDisplay = 0 + var activeChangesOnSecondaryDisplay = 0 + override fun onActiveTasksChanged(displayId: Int) { + when (displayId) { + DEFAULT_DISPLAY -> activeChangesOnDefaultDisplay++ + SECOND_DISPLAY -> activeChangesOnSecondaryDisplay++ + else -> fail("Active task listener received unexpected display id: $displayId") + } } } class TestVisibilityListener : DesktopModeTaskRepository.VisibleTasksListener { - var hasVisibleFreeformTasks = false - var visibleFreeformTaskChangedCalls = 0 - - override fun onVisibilityChanged(hasVisibleTasks: Boolean) { - hasVisibleFreeformTasks = hasVisibleTasks - visibleFreeformTaskChangedCalls++ + var hasVisibleTasksOnDefaultDisplay = false + var hasVisibleTasksOnSecondaryDisplay = false + + var visibleChangesOnDefaultDisplay = 0 + var visibleChangesOnSecondaryDisplay = 0 + + override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) { + when (displayId) { + DEFAULT_DISPLAY -> { + hasVisibleTasksOnDefaultDisplay = hasVisibleFreeformTasks + visibleChangesOnDefaultDisplay++ + } + SECOND_DISPLAY -> { + hasVisibleTasksOnSecondaryDisplay = hasVisibleFreeformTasks + visibleChangesOnSecondaryDisplay++ + } + else -> fail("Visible task listener received unexpected display id: $displayId") + } } } + + companion object { + const val SECOND_DISPLAY = 1 + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index c9bd695ffb33..f506969f51df 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -25,6 +25,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.os.Binder import android.testing.AndroidTestingRunner +import android.view.Display.DEFAULT_DISPLAY import android.view.WindowManager import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_NONE @@ -84,10 +85,10 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler - lateinit var mockitoSession: StaticMockitoSession - lateinit var controller: DesktopTasksController - lateinit var shellInit: ShellInit - lateinit var desktopModeTaskRepository: DesktopModeTaskRepository + private lateinit var mockitoSession: StaticMockitoSession + private lateinit var controller: DesktopTasksController + private lateinit var shellInit: ShellInit + private lateinit var desktopModeTaskRepository: DesktopModeTaskRepository // Mock running tasks are registered here so we can get the list from mock shell task organizer private val runningTasks = mutableListOf<RunningTaskInfo>() @@ -155,7 +156,7 @@ class DesktopTasksControllerTest : ShellTestCase() { markTaskHidden(task1) markTaskHidden(task2) - controller.showDesktopApps() + controller.showDesktopApps(DEFAULT_DISPLAY) val wct = getLatestWct(expectTransition = TRANSIT_NONE) assertThat(wct.hierarchyOps).hasSize(3) @@ -173,7 +174,7 @@ class DesktopTasksControllerTest : ShellTestCase() { markTaskVisible(task1) markTaskVisible(task2) - controller.showDesktopApps() + controller.showDesktopApps(DEFAULT_DISPLAY) val wct = getLatestWct(expectTransition = TRANSIT_NONE) assertThat(wct.hierarchyOps).hasSize(3) @@ -191,7 +192,7 @@ class DesktopTasksControllerTest : ShellTestCase() { markTaskHidden(task1) markTaskVisible(task2) - controller.showDesktopApps() + controller.showDesktopApps(DEFAULT_DISPLAY) val wct = getLatestWct(expectTransition = TRANSIT_NONE) assertThat(wct.hierarchyOps).hasSize(3) @@ -205,7 +206,7 @@ class DesktopTasksControllerTest : ShellTestCase() { fun showDesktopApps_noActiveTasks_reorderHomeToTop() { val homeTask = setUpHomeTask() - controller.showDesktopApps() + controller.showDesktopApps(DEFAULT_DISPLAY) val wct = getLatestWct(expectTransition = TRANSIT_NONE) assertThat(wct.hierarchyOps).hasSize(1) @@ -213,8 +214,26 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay() { + val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY) + val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY) + setUpHomeTask(SECOND_DISPLAY) + val taskSecondDisplay = setUpFreeformTask(SECOND_DISPLAY) + markTaskHidden(taskDefaultDisplay) + markTaskHidden(taskSecondDisplay) + + controller.showDesktopApps(DEFAULT_DISPLAY) + + val wct = getLatestWct(expectTransition = TRANSIT_NONE) + assertThat(wct.hierarchyOps).hasSize(2) + // Expect order to be from bottom: home, task + wct.assertReorderAt(index = 0, homeTaskDefaultDisplay) + wct.assertReorderAt(index = 1, taskDefaultDisplay) + } + + @Test fun getVisibleTaskCount_noTasks_returnsZero() { - assertThat(controller.getVisibleTaskCount()).isEqualTo(0) + assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) } @Test @@ -222,7 +241,7 @@ class DesktopTasksControllerTest : ShellTestCase() { setUpHomeTask() setUpFreeformTask().also(::markTaskVisible) setUpFreeformTask().also(::markTaskVisible) - assertThat(controller.getVisibleTaskCount()).isEqualTo(2) + assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2) } @Test @@ -230,7 +249,15 @@ class DesktopTasksControllerTest : ShellTestCase() { setUpHomeTask() setUpFreeformTask().also(::markTaskVisible) setUpFreeformTask().also(::markTaskHidden) - assertThat(controller.getVisibleTaskCount()).isEqualTo(1) + assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) + } + + @Test + fun getVisibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() { + setUpHomeTask() + setUpFreeformTask(DEFAULT_DISPLAY).also(::markTaskVisible) + setUpFreeformTask(SECOND_DISPLAY).also(::markTaskVisible) + assertThat(controller.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1) } @Test @@ -258,6 +285,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveToDesktop(fullscreenTask) with(getLatestWct(expectTransition = TRANSIT_CHANGE)) { + // Operations should include home task, freeform task assertThat(hierarchyOps).hasSize(3) assertReorderSequence(homeTask, freeformTask, fullscreenTask) assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode) @@ -266,6 +294,28 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun moveToDesktop_onlyFreeformTasksFromCurrentDisplayBroughtToFront() { + setUpHomeTask(displayId = DEFAULT_DISPLAY) + val freeformTaskDefault = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val fullscreenTaskDefault = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) + markTaskHidden(freeformTaskDefault) + + val homeTaskSecond = setUpHomeTask(displayId = SECOND_DISPLAY) + val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY) + markTaskHidden(freeformTaskSecond) + + controller.moveToDesktop(fullscreenTaskDefault) + + with(getLatestWct(expectTransition = TRANSIT_CHANGE)) { + // Check that hierarchy operations do not include tasks from second display + assertThat(hierarchyOps.map { it.container }) + .doesNotContain(homeTaskSecond.token.asBinder()) + assertThat(hierarchyOps.map { it.container }) + .doesNotContain(freeformTaskSecond.token.asBinder()) + } + } + + @Test fun moveToFullscreen() { val task = setUpFreeformTask() controller.moveToFullscreen(task) @@ -281,6 +331,19 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun moveToFullscreen_secondDisplayTaskHasFreeform_secondDisplayNotAffected() { + val taskDefaultDisplay = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val taskSecondDisplay = setUpFreeformTask(displayId = SECOND_DISPLAY) + + controller.moveToFullscreen(taskDefaultDisplay) + + with(getLatestWct(expectTransition = TRANSIT_CHANGE)) { + assertThat(changes.keys).contains(taskDefaultDisplay.token.asBinder()) + assertThat(changes.keys).doesNotContain(taskSecondDisplay.token.asBinder()) + } + } + + @Test fun getTaskWindowingMode() { val fullscreenTask = setUpFullscreenTask() val freeformTask = setUpFreeformTask() @@ -324,6 +387,18 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun handleRequest_fullscreenTask_freeformTaskOnOtherDisplay_returnNull() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + + val fullscreenTaskDefaultDisplay = createFullscreenTask(displayId = DEFAULT_DISPLAY) + createFreeformTask(displayId = SECOND_DISPLAY) + + val result = + controller.handleRequest(Binder(), createTransition(fullscreenTaskDefaultDisplay)) + assertThat(result).isNull() + } + + @Test fun handleRequest_freeformTask_freeformVisible_returnNull() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -362,6 +437,18 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun handleRequest_freeformTask_freeformOnOtherDisplayOnly_returnSwitchToFullscreenWCT() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + + val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY) + createFreeformTask(displayId = SECOND_DISPLAY) + + val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay)) + assertThat(result?.changes?.get(taskDefaultDisplay.token.asBinder())?.windowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) + } + + @Test fun handleRequest_notOpenOrToFrontTransition_returnNull() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -400,35 +487,43 @@ class DesktopTasksControllerTest : ShellTestCase() { assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull() } - private fun setUpFreeformTask(): RunningTaskInfo { - val task = createFreeformTask() + private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { + val task = createFreeformTask(displayId) whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) - desktopModeTaskRepository.addActiveTask(task.taskId) + desktopModeTaskRepository.addActiveTask(displayId, task.taskId) desktopModeTaskRepository.addOrMoveFreeformTaskToTop(task.taskId) runningTasks.add(task) return task } - private fun setUpHomeTask(): RunningTaskInfo { - val task = createHomeTask() + private fun setUpHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { + val task = createHomeTask(displayId) whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) runningTasks.add(task) return task } - private fun setUpFullscreenTask(): RunningTaskInfo { - val task = createFullscreenTask() + private fun setUpFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { + val task = createFullscreenTask(displayId) whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) runningTasks.add(task) return task } private fun markTaskVisible(task: RunningTaskInfo) { - desktopModeTaskRepository.updateVisibleFreeformTasks(task.taskId, visible = true) + desktopModeTaskRepository.updateVisibleFreeformTasks( + task.displayId, + task.taskId, + visible = true + ) } private fun markTaskHidden(task: RunningTaskInfo) { - desktopModeTaskRepository.updateVisibleFreeformTasks(task.taskId, visible = false) + desktopModeTaskRepository.updateVisibleFreeformTasks( + task.displayId, + task.taskId, + visible = false + ) } private fun getLatestWct( @@ -457,6 +552,10 @@ class DesktopTasksControllerTest : ShellTestCase() { ): TransitionRequestInfo { return TransitionRequestInfo(type, task, null /* remoteTransition */) } + + companion object { + const val SECOND_DISPLAY = 2 + } } private fun WindowContainerTransaction.assertReorderAt(index: Int, task: RunningTaskInfo) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt index dc91d756842e..cf1ff3214d87 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt @@ -21,14 +21,17 @@ import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN +import android.view.Display.DEFAULT_DISPLAY import com.android.wm.shell.TestRunningTaskInfoBuilder class DesktopTestHelpers { companion object { /** Create a task that has windowing mode set to [WINDOWING_MODE_FREEFORM] */ @JvmStatic - fun createFreeformTask(): RunningTaskInfo { + @JvmOverloads + fun createFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { return TestRunningTaskInfoBuilder() + .setDisplayId(displayId) .setToken(MockToken().token()) .setActivityType(ACTIVITY_TYPE_STANDARD) .setWindowingMode(WINDOWING_MODE_FREEFORM) @@ -38,8 +41,10 @@ class DesktopTestHelpers { /** Create a task that has windowing mode set to [WINDOWING_MODE_FULLSCREEN] */ @JvmStatic - fun createFullscreenTask(): RunningTaskInfo { + @JvmOverloads + fun createFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { return TestRunningTaskInfoBuilder() + .setDisplayId(displayId) .setToken(MockToken().token()) .setActivityType(ACTIVITY_TYPE_STANDARD) .setWindowingMode(WINDOWING_MODE_FULLSCREEN) @@ -49,8 +54,10 @@ class DesktopTestHelpers { /** Create a new home task */ @JvmStatic - fun createHomeTask(): RunningTaskInfo { + @JvmOverloads + fun createHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { return TestRunningTaskInfoBuilder() + .setDisplayId(displayId) .setToken(MockToken().token()) .setActivityType(ACTIVITY_TYPE_HOME) .setWindowingMode(WINDOWING_MODE_FULLSCREEN) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index 6995d10dd78d..04f2c99783da 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -268,7 +268,7 @@ public class PipControllerTest extends ShellTestCase { } @Test - public void saveReentryState_userHasResized_savesSize() { + public void saveReentryState_nonEmptyUserResizeBounds_savesSize() { final Rect bounds = new Rect(0, 0, 10, 10); final Rect resizedBounds = new Rect(0, 0, 30, 30); when(mMockPipBoundsAlgorithm.getSnapFraction(bounds)).thenReturn(1.0f); @@ -281,6 +281,19 @@ public class PipControllerTest extends ShellTestCase { } @Test + public void saveReentryState_emptyUserResizeBounds_savesSize() { + final Rect bounds = new Rect(0, 0, 10, 10); + final Rect resizedBounds = new Rect(0, 0, 0, 0); + when(mMockPipBoundsAlgorithm.getSnapFraction(bounds)).thenReturn(1.0f); + when(mMockPipTouchHandler.getUserResizeBounds()).thenReturn(resizedBounds); + when(mMockPipBoundsState.hasUserResizedPip()).thenReturn(true); + + mPipController.saveReentryState(bounds); + + verify(mMockPipBoundsState).saveReentryState(new Size(10, 10), 1.0f); + } + + @Test public void onDisplayConfigurationChanged_inPip_movePip() { final int displayId = 1; final Rect bounds = new Rect(0, 0, 10, 10); diff --git a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml index 6bfcd82b0a4a..8c35da13e2c4 100644 --- a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml +++ b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml @@ -58,6 +58,7 @@ android:textSize="14sp" android:layout_marginTop="2dp" style="@style/TextAppearance" + android:focusable="true" android:textColor="?android:attr/textColorSecondary"/> </LinearLayout> diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml index 74072e9d4ec3..2502bbf7b40b 100644 --- a/packages/CompanionDeviceManager/res/values/strings.xml +++ b/packages/CompanionDeviceManager/res/values/strings.xml @@ -113,17 +113,11 @@ <!-- Back button for the helper consent dialog [CHAR LIMIT=30] --> <string name="consent_back">Back</string> - <!-- Action when permission list view is expanded CHAR LIMIT=30] --> - <string name="permission_expanded">Expanded</string> + <!-- Expand permission in the list CHAR LIMIT=30] --> + <string name="permission_expand">Expand <xliff:g id="permission_type" example="Notification">%1$s</xliff:g></string> - <!-- Expand action permission list CHAR LIMIT=30] --> - <string name="permission_expand">Expand</string> - - <!-- Action when permission list view is collapsed CHAR LIMIT=30] --> - <string name="permission_collapsed">Collapsed</string> - - <!-- Collapse action permission list CHAR LIMIT=30] --> - <string name="permission_collapse">Collapse</string> + <!-- Collapse permission int the list CHAR LIMIT=30] --> + <string name="permission_collapse">Collapse <xliff:g id="permission_type" example="Notification">%1$s</xliff:g></string> <!-- ================== System data transfer ==================== --> <!-- Title of the permission sync confirmation dialog. [CHAR LIMIT=NONE] --> diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java index b86ef649331a..f594bf270d29 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java @@ -124,7 +124,7 @@ class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.V } setAccessibility(view, viewType, - AccessibilityNodeInfo.ACTION_CLICK, R.string.permission_expand); + AccessibilityNodeInfo.ACTION_CLICK, R.string.permission_expand, 0); // Add expand buttons if the permissions are more than PERMISSION_SIZE in this list also // make the summary invisible by default. @@ -137,18 +137,16 @@ class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.V viewHolder.mExpandButton.setImageResource(R.drawable.btn_expand_less); viewHolder.mPermissionSummary.setVisibility(View.VISIBLE); viewHolder.mExpandButton.setTag(R.drawable.btn_expand_less); - view.setContentDescription(mContext.getString(R.string.permission_expanded)); setAccessibility(view, viewType, - AccessibilityNodeInfo.ACTION_CLICK, R.string.permission_collapse); - viewHolder.mPermissionSummary.setFocusable(true); + AccessibilityNodeInfo.ACTION_CLICK, + R.string.permission_collapse, R.string.permission_expand); } else { viewHolder.mExpandButton.setImageResource(R.drawable.btn_expand_more); viewHolder.mPermissionSummary.setVisibility(View.GONE); viewHolder.mExpandButton.setTag(R.drawable.btn_expand_more); - view.setContentDescription(mContext.getString(R.string.permission_collapsed)); setAccessibility(view, viewType, - AccessibilityNodeInfo.ACTION_CLICK, R.string.permission_expanded); - viewHolder.mPermissionSummary.setFocusable(false); + AccessibilityNodeInfo.ACTION_CLICK, + R.string.permission_expand, R.string.permission_collapse); } }); } else { @@ -200,14 +198,20 @@ class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.V } } - private void setAccessibility(View view, int viewType, int action, int resourceId) { - final String actionString = mContext.getString(resourceId); + private void setAccessibility(View view, int viewType, int action, int statusResourceId, + int actionResourceId) { final String permission = mContext.getString(sTitleMap.get(viewType)); + + if (actionResourceId != 0) { + view.announceForAccessibility( + getHtmlFromResources(mContext, actionResourceId, permission)); + } + view.setAccessibilityDelegate(new View.AccessibilityDelegate() { public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(host, info); info.addAction(new AccessibilityNodeInfo.AccessibilityAction(action, - actionString + permission)); + getHtmlFromResources(mContext, statusResourceId, permission))); } }); } diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml index a3b27529d857..340285760cda 100644 --- a/packages/CredentialManager/res/values/strings.xml +++ b/packages/CredentialManager/res/values/strings.xml @@ -86,6 +86,8 @@ <string name="use_provider_for_all_description">This password manager for <xliff:g id="username" example="becket@gmail.com">%1$s</xliff:g> will store your passwords and passkeys to help you easily sign in</string> <!-- This is a label for a button that sets this password manager as the default. [CHAR LIMIT=20] --> <string name="set_as_default">Set as default</string> + <!-- This is a button text to navigate the user to their system settings. [CHAR LIMIT=30] --> + <string name="settings">Settings</string> <!-- This is a label for a button that makes this password manager be used just in this specific case. [CHAR LIMIT=20] --> <string name="use_once">Use once</string> <!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are passwords and passkeys. [CHAR LIMIT=80] --> diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java index 01f92c4fa7b1..19b7e8546805 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java @@ -17,6 +17,7 @@ package com.android.settingslib.collapsingtoolbar; import android.app.ActionBar; +import android.content.pm.PackageManager; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -59,7 +60,8 @@ public class CollapsingToolbarBaseActivity extends FragmentActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (mCustomizeLayoutResId > 0 && !BuildCompatUtils.isAtLeastS()) { + // for backward compatibility on R devices or wearable devices due to small device size. + if (mCustomizeLayoutResId > 0 && (!BuildCompatUtils.isAtLeastS() || isWatch())) { super.setContentView(mCustomizeLayoutResId); return; } @@ -157,6 +159,14 @@ public class CollapsingToolbarBaseActivity extends FragmentActivity { return getToolbarDelegate().getAppBarLayout(); } + private boolean isWatch() { + PackageManager packageManager = getPackageManager(); + if (packageManager == null) { + return false; + } + return packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH); + } + private CollapsingToolbarDelegate getToolbarDelegate() { if (mToolbardelegate == null) { mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback()); diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java b/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java index 3c451127508d..dae48dbac0a4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java @@ -17,6 +17,7 @@ package com.android.settingslib.applications; import android.app.AppGlobals; +import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -94,6 +95,10 @@ public class DefaultAppInfo extends CandidateInfo { } + public @Nullable String getSummary() { + return this.summary; + } + @Override public Drawable loadIcon() { final IconDrawableFactory factory = IconDrawableFactory.newInstance(mContext); diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java index e4cc9f15aea1..6a5535d345db 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java @@ -100,6 +100,5 @@ public class SystemSettings { Settings.System.CAMERA_FLASH_NOTIFICATION, Settings.System.SCREEN_FLASH_NOTIFICATION, Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, - Settings.System.SMOOTH_DISPLAY }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index 4b720636c1d4..85623b26c589 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -226,6 +226,5 @@ public class SystemSettingsValidators { VALIDATORS.put(System.CAMERA_FLASH_NOTIFICATION, BOOLEAN_VALIDATOR); VALIDATORS.put(System.SCREEN_FLASH_NOTIFICATION, BOOLEAN_VALIDATOR); VALIDATORS.put(System.SCREEN_FLASH_NOTIFICATION_COLOR, ANY_INTEGER_VALIDATOR); - VALIDATORS.put(System.SMOOTH_DISPLAY, BOOLEAN_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index d1bd5e661072..46b45d12efc8 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -34,7 +34,6 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OV import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME; import static com.android.internal.accessibility.util.AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM; -import static com.android.internal.display.RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE; import static com.android.providers.settings.SettingsState.FALLBACK_FILE_SUFFIX; import static com.android.providers.settings.SettingsState.getTypeFromKey; import static com.android.providers.settings.SettingsState.getUserIdFromKey; @@ -5674,7 +5673,7 @@ public class SettingsProvider extends ContentProvider { providers.addAll(Arrays.asList(resources.getStringArray(resourceId))); } catch (Resources.NotFoundException e) { Slog.w(LOG_TAG, - "Get default array Cred Provider not found: " + e.toString()); + "Get default array Cred Provider not found: " + e.toString()); } try { final String storedValue = resources.getString(resourceId); @@ -5683,7 +5682,7 @@ public class SettingsProvider extends ContentProvider { } } catch (Resources.NotFoundException e) { Slog.w(LOG_TAG, - "Get default Cred Provider not found: " + e.toString()); + "Get default Cred Provider not found: " + e.toString()); } if (!providers.isEmpty()) { @@ -5732,8 +5731,8 @@ public class SettingsProvider extends ContentProvider { final Setting currentSetting = secureSettings .getSettingLocked(Settings.Secure.CREDENTIAL_SERVICE); if (currentSetting.isNull()) { - final int resourceId = com.android.internal.R.array - .config_defaultCredentialProviderService; + final int resourceId = + com.android.internal.R.array.config_defaultCredentialProviderService; final Resources resources = getContext().getResources(); // If the config has not be defined we might get an exception. final List<String> providers = new ArrayList<>(); @@ -5741,7 +5740,7 @@ public class SettingsProvider extends ContentProvider { providers.addAll(Arrays.asList(resources.getStringArray(resourceId))); } catch (Resources.NotFoundException e) { Slog.w(LOG_TAG, - "Get default array Cred Provider not found: " + e.toString()); + "Get default array Cred Provider not found: " + e.toString()); } if (!providers.isEmpty()) { @@ -5840,44 +5839,12 @@ public class SettingsProvider extends ContentProvider { currentVersion = 218; } - // v218: Convert Smooth Display and Force Peak Refresh Rate to a boolean if (currentVersion == 218) { - final String peakRefreshRateSettingName = "peak_refresh_rate"; - final String minRefreshRateSettingName = "min_refresh_rate"; - - final SettingsState systemSettings = getSystemSettingsLocked(userId); - final Setting peakRefreshRateSetting = - systemSettings.getSettingLocked(peakRefreshRateSettingName); - final Setting minRefreshRateSetting = - systemSettings.getSettingLocked(minRefreshRateSettingName); - - float peakRefreshRate = DEFAULT_REFRESH_RATE; - float minRefreshRate = 0; - try { - if (!peakRefreshRateSetting.isNull()) { - peakRefreshRate = Float.parseFloat(peakRefreshRateSetting.getValue()); - } - } catch (NumberFormatException e) { - // Do nothing. Overwrite with default value. - } - try { - if (!minRefreshRateSetting.isNull()) { - minRefreshRate = Float.parseFloat(minRefreshRateSetting.getValue()); - } - } catch (NumberFormatException e) { - // Do nothing. Overwrite with default value. - } - - systemSettings.deleteSettingLocked(peakRefreshRateSettingName); - systemSettings.deleteSettingLocked(minRefreshRateSettingName); - - systemSettings.insertSettingLocked(Settings.System.SMOOTH_DISPLAY, - peakRefreshRate > DEFAULT_REFRESH_RATE ? "1" : "0", /* tag= */ null, - /* makeDefault= */ false, SettingsState.SYSTEM_PACKAGE_NAME); - systemSettings.insertSettingLocked(Settings.System.FORCE_PEAK_REFRESH_RATE, - minRefreshRate > 0 ? "1" : "0", /* tag= */ null, - /* makeDefault= */ false, SettingsState.SYSTEM_PACKAGE_NAME); - + // Version 219: Removed + // TODO(b/211737588): Back up the Smooth Display setting + // Future upgrades to the `peak_refresh_rate` and `min_refresh_rate` settings + // should account for the database in a non-upgraded and upgraded (change id: + // Ib2cb2dd100f06f5452083b7606109a486e795a0e) state. currentVersion = 219; } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 36aa2ac74406..706666cbebab 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -97,7 +97,8 @@ public class SettingsBackupTest { Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug? Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only Settings.System.DESKTOP_MODE, // developer setting for internal prototyping - Settings.System.FORCE_PEAK_REFRESH_RATE, // depends on hardware capabilities + Settings.System.MIN_REFRESH_RATE, // depends on hardware capabilities + Settings.System.PEAK_REFRESH_RATE, // depends on hardware capabilities Settings.System.SCREEN_BRIGHTNESS_FLOAT, Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, Settings.System.MULTI_AUDIO_FOCUS_ENABLED // form-factor/OEM specific diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 6e55000dfe8a..c8eb4b41f003 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -992,6 +992,18 @@ android:excludeFromRecents="true" android:resizeableActivity="false" android:theme="@android:style/Theme.NoDisplay" /> + + <activity + android:name=".notetask.LaunchNotesRoleSettingsTrampolineActivity" + android:exported="true" + android:excludeFromRecents="true" + android:resizeableActivity="false" + android:theme="@android:style/Theme.NoDisplay" > + <intent-filter> + <action android:name="com.android.systemui.action.MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> <!-- endregion --> <!-- started from ControlsRequestReceiver --> diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS index 77ddc6e573b7..1ce347215954 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -55,6 +55,7 @@ lynhan@google.com madym@google.com mankoff@google.com mateuszc@google.com +mgalhardo@google.com michaelmikhil@google.com michschn@google.com mkephart@google.com diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java index 436145ec0692..3244eb43c8c4 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java @@ -131,7 +131,8 @@ public interface QS extends FragmentBase { /** * A rounded corner clipping that makes QS feel as if it were behind everything. */ - void setFancyClipping(int top, int bottom, int cornerRadius, boolean visible); + void setFancyClipping(int leftInset, int top, int rightInset, int bottom, int cornerRadius, + boolean visible, boolean fullWidth); /** * @return if quick settings is fully collapsed currently diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml index 29832a081612..934fa6f54286 100644 --- a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml +++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml @@ -30,7 +30,7 @@ <FrameLayout android:id="@+id/inout_container" - android:layout_height="17dp" + android:layout_height="@*android:dimen/status_bar_system_icon_intrinsic_size" android:layout_width="wrap_content" android:layout_gravity="center_vertical"> <ImageView @@ -39,24 +39,25 @@ android:layout_width="wrap_content" android:src="@drawable/ic_activity_down" android:visibility="gone" - android:paddingEnd="2dp" + android:paddingEnd="2sp" /> <ImageView android:id="@+id/mobile_out" android:layout_height="wrap_content" android:layout_width="wrap_content" android:src="@drawable/ic_activity_up" - android:paddingEnd="2dp" + android:paddingEnd="2sp" android:visibility="gone" /> </FrameLayout> <ImageView android:id="@+id/mobile_type" - android:layout_height="wrap_content" + android:layout_height="@dimen/status_bar_mobile_signal_size" android:layout_width="wrap_content" android:layout_gravity="center_vertical" - android:paddingStart="2.5dp" - android:paddingEnd="1dp" + android:adjustViewBounds="true" + android:paddingStart="2.5sp" + android:paddingEnd="1sp" android:visibility="gone" /> <Space android:id="@+id/mobile_roaming_space" @@ -70,14 +71,14 @@ android:layout_gravity="center_vertical"> <com.android.systemui.statusbar.AnimatedImageView android:id="@+id/mobile_signal" - android:layout_height="wrap_content" - android:layout_width="wrap_content" + android:layout_height="@dimen/status_bar_mobile_signal_size" + android:layout_width="@dimen/status_bar_mobile_signal_size" systemui:hasOverlappingRendering="false" /> <ImageView android:id="@+id/mobile_roaming" - android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_width="@dimen/status_bar_mobile_signal_size" + android:layout_height="@dimen/status_bar_mobile_signal_size" android:src="@drawable/stat_sys_roaming" android:contentDescription="@string/data_connection_roaming" android:visibility="gone" /> diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml index 4fc411eaea9d..4d289ebeb30b 100644 --- a/packages/SystemUI/res-keyguard/values/styles.xml +++ b/packages/SystemUI/res-keyguard/values/styles.xml @@ -22,6 +22,7 @@ <!-- Keyguard PIN pad styles --> <style name="Keyguard.TextView" parent="@android:style/Widget.DeviceDefault.TextView"> <item name="android:textSize">@dimen/kg_status_line_font_size</item> + <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> </style> <style name="Keyguard.TextView.EmergencyButton" parent="Theme.SystemUI"> <item name="android:textColor">?androidprv:attr/materialColorOnTertiaryFixed</item> diff --git a/packages/SystemUI/res/drawable/ic_note_task_shortcut_keyguard.xml b/packages/SystemUI/res/drawable/ic_note_task_shortcut_keyguard.xml index ee8d488343c4..a35504f9c202 100644 --- a/packages/SystemUI/res/drawable/ic_note_task_shortcut_keyguard.xml +++ b/packages/SystemUI/res/drawable/ic_note_task_shortcut_keyguard.xml @@ -16,16 +16,12 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" - android:viewportHeight="24" - android:viewportWidth="24"> + android:viewportWidth="24" + android:viewportHeight="24"> <path - android:fillAlpha="1" - android:fillColor="@android:color/white" - android:fillType="nonZero" - android:pathData="M17.6258,4.96L19.0358,6.37L7.4058,18.01L5.9958,16.6L17.6258,4.96ZM16.1358,3.62L4.1258,15.63L3.0158,19.83C2.9058,20.45 3.3858,21 3.9958,21C4.0558,21 4.1058,21 4.1658,20.99L8.3658,19.88L20.3758,7.86C20.7758,7.46 20.9958,6.93 20.9958,6.37C20.9958,5.81 20.7758,5.28 20.3758,4.88L19.1058,3.61C18.7158,3.22 18.1858,3 17.6258,3C17.0658,3 16.5358,3.22 16.1358,3.62Z" /> + android:pathData="M23.41,6L22,4.59C21.63,4.21 21.12,4 20.59,4C20.06,4 19.55,4.21 19.18,4.59L11.39,12.38L9.09,14.68L8.04,18.9C8.01,18.96 8,19.04 8,19.11C8,19.6 8.4,20 8.89,20C8.96,20 9.04,19.99 9.11,19.97L13.33,18.92L15.63,16.62L23.42,8.83C23.79,8.45 24,7.94 24,7.41C24,6.88 23.79,6.37 23.41,6ZM14.21,15.21L13.21,16.21L11.8,14.8L12.8,13.8L20.59,6L22,7.41L14.21,15.21Z" + android:fillColor="@android:color/white"/> <path - android:fillAlpha="1" - android:fillColor="@android:color/white" - android:fillType="nonZero" - android:pathData="M20.1936,15.3369C20.3748,16.3837 19.9151,17.5414 18.8846,18.7597C19.1546,18.872 19.4576,18.9452 19.7724,18.9867C20.0839,19.0278 20.3683,19.0325 20.5749,19.0266C20.6772,19.0236 20.7578,19.0181 20.8101,19.0138C20.8362,19.0116 20.855,19.0097 20.8657,19.0085L20.8754,19.0074L20.875,19.0075C21.4217,18.9385 21.9214,19.325 21.9918,19.8718C22.0624,20.4195 21.6756,20.9208 21.1279,20.9914L21,19.9996C21.1279,20.9914 21.1265,20.9916 21.1265,20.9916L21.1249,20.9918L21.1211,20.9923L21.1107,20.9935L21.0795,20.997C21.0542,20.9998 21.0199,21.0032 20.9775,21.0067C20.8929,21.0138 20.7753,21.0216 20.6323,21.0257C20.3481,21.0339 19.9533,21.0279 19.5109,20.9695C18.873,20.8854 18.0393,20.6793 17.3106,20.1662C16.9605,20.3559 16.5876,20.4952 16.2299,20.6003C15.5742,20.7927 14.8754,20.8968 14.2534,20.9534C13.6801,21.0055 13.4553,21.0037 13.1015,21.0008C13.0689,21.0005 13.0352,21.0002 13,21H12.8594C12.8214,21.0002 12.785,21.0006 12.7504,21.0009C12.6524,21.0019 12.5683,21.0027 12.5,21H12.0562C12.0277,21.0003 12.0054,21.0006 11.9926,21.001L11.9751,21H9L11,19H11.9795C11.9929,18.9997 12.0064,18.9997 12.0199,19H12.4117C12.4534,18.9996 12.4864,18.9995 12.5,19H12.9675C12.977,18.9999 12.9878,18.9999 13,19C13.0446,19.0003 13.0859,19.0007 13.1249,19.0011C13.4259,19.0038 13.591,19.0054 14.0723,18.9616C14.6201,18.9118 15.1795,18.8242 15.6665,18.6813C15.753,18.6559 15.8346,18.6295 15.9114,18.6022C15.0315,17.2981 14.7125,16.1044 15.015,15.0829C15.4095,13.7511 16.6784,13.2418 17.7026,13.2864C18.7262,13.3309 19.954,13.9529 20.1936,15.3369ZM16.9327,15.6508C16.873,15.8523 16.8651,16.3878 17.4697,17.334C18.2007,16.4284 18.2585,15.8839 18.2229,15.6781C18.1939,15.5108 18.0297,15.3025 17.6157,15.2845C17.2025,15.2665 16.9885,15.4626 16.9327,15.6508Z" /> + android:pathData="M6.688,20C2.047,20 0.333,18.65 0.333,16C0.333,13.61 2.439,12.474 5.713,12C6.792,11.844 7.344,11.397 7.344,10.927C7.344,9.625 4.679,9.705 3.833,9.667V7.667C3.833,7.667 6.792,7.667 8.208,8.729C8.932,9.272 9.333,9.979 9.333,11.05C9.333,12.52 8.281,13.677 5.713,13.885C4.017,14.023 2.333,14.52 2.333,16C2.333,17.33 4.013,18 7.333,18L6.688,20Z" + android:fillColor="@android:color/white"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget.xml b/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget.xml index 759018289562..860fc7d6b316 100644 --- a/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget.xml +++ b/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget.xml @@ -13,19 +13,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportHeight="24" - android:viewportWidth="24"> - <path - android:fillAlpha="1" - android:fillColor="#636C6F" - android:fillType="nonZero" - android:pathData="M17.6258,4.96L19.0358,6.37L7.4058,18.01L5.9958,16.6L17.6258,4.96ZM16.1358,3.62L4.1258,15.63L3.0158,19.83C2.9058,20.45 3.3858,21 3.9958,21C4.0558,21 4.1058,21 4.1658,20.99L8.3658,19.88L20.3758,7.86C20.7758,7.46 20.9958,6.93 20.9958,6.37C20.9958,5.81 20.7758,5.28 20.3758,4.88L19.1058,3.61C18.7158,3.22 18.1858,3 17.6258,3C17.0658,3 16.5358,3.22 16.1358,3.62Z" /> - <path - android:fillAlpha="1" - android:fillColor="#636C6F" - android:fillType="nonZero" - android:pathData="M20.1936,15.3369C20.3748,16.3837 19.9151,17.5414 18.8846,18.7597C19.1546,18.872 19.4576,18.9452 19.7724,18.9867C20.0839,19.0278 20.3683,19.0325 20.5749,19.0266C20.6772,19.0236 20.7578,19.0181 20.8101,19.0138C20.8362,19.0116 20.855,19.0097 20.8657,19.0085L20.8754,19.0074L20.875,19.0075C21.4217,18.9385 21.9214,19.325 21.9918,19.8718C22.0624,20.4195 21.6756,20.9208 21.1279,20.9914L21,19.9996C21.1279,20.9914 21.1265,20.9916 21.1265,20.9916L21.1249,20.9918L21.1211,20.9923L21.1107,20.9935L21.0795,20.997C21.0542,20.9998 21.0199,21.0032 20.9775,21.0067C20.8929,21.0138 20.7753,21.0216 20.6323,21.0257C20.3481,21.0339 19.9533,21.0279 19.5109,20.9695C18.873,20.8854 18.0393,20.6793 17.3106,20.1662C16.9605,20.3559 16.5876,20.4952 16.2299,20.6003C15.5742,20.7927 14.8754,20.8968 14.2534,20.9534C13.6801,21.0055 13.4553,21.0037 13.1015,21.0008C13.0689,21.0005 13.0352,21.0002 13,21H12.8594C12.8214,21.0002 12.785,21.0006 12.7504,21.0009C12.6524,21.0019 12.5683,21.0027 12.5,21H12.0562C12.0277,21.0003 12.0054,21.0006 11.9926,21.001L11.9751,21H9L11,19H11.9795C11.9929,18.9997 12.0064,18.9997 12.0199,19H12.4117C12.4534,18.9996 12.4864,18.9995 12.5,19H12.9675C12.977,18.9999 12.9878,18.9999 13,19C13.0446,19.0003 13.0859,19.0007 13.1249,19.0011C13.4259,19.0038 13.591,19.0054 14.0723,18.9616C14.6201,18.9118 15.1795,18.8242 15.6665,18.6813C15.753,18.6559 15.8346,18.6295 15.9114,18.6022C15.0315,17.2981 14.7125,16.1044 15.015,15.0829C15.4095,13.7511 16.6784,13.2418 17.7026,13.2864C18.7262,13.3309 19.954,13.9529 20.1936,15.3369ZM16.9327,15.6508C16.873,15.8523 16.8651,16.3878 17.4697,17.334C18.2007,16.4284 18.2585,15.8839 18.2229,15.6781C18.1939,15.5108 18.0297,15.3025 17.6157,15.2845C17.2025,15.2665 16.9885,15.4626 16.9327,15.6508Z" /> -</vector> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_note_task_shortcut_widget_background" /> + <foreground android:drawable="@drawable/ic_note_task_shortcut_widget_foreground" /> +</adaptive-icon> diff --git a/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget_background.xml b/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget_background.xml new file mode 100644 index 000000000000..9f98f07c8a58 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget_background.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + <path + android:pathData="M0,0h108v108h-108z" + android:fillColor="#0B57D0"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget_foreground.xml b/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget_foreground.xml new file mode 100644 index 000000000000..fcb3ef4bfdc5 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget_foreground.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + <path + android:pathData="M74.92,43L72.33,40.42C71.65,39.72 70.72,39.33 69.75,39.33C68.78,39.33 67.84,39.72 67.16,40.42L52.88,54.7L48.67,58.91L46.74,66.65C46.69,66.76 46.67,66.91 46.67,67.04C46.67,67.93 47.4,68.67 48.3,68.67C48.43,68.67 48.57,68.65 48.7,68.61L56.44,66.69L60.65,62.47L74.94,48.19C75.61,47.49 76,46.56 76,45.58C76,44.61 75.61,43.68 74.92,43ZM58.05,59.88L56.22,61.72L53.63,59.13L55.47,57.3L69.75,43L72.33,45.58L58.05,59.88Z" + android:fillColor="#ffffff"/> + <path + android:pathData="M44.26,68.67C35.75,68.67 32.61,66.19 32.61,61.33C32.61,56.95 36.47,54.87 42.47,54C44.45,53.71 45.46,52.89 45.46,52.03C45.46,49.65 40.58,49.79 39.03,49.72V46.06C39.03,46.06 44.45,46.06 47.05,48C48.37,49 49.11,50.3 49.11,52.26C49.11,54.95 47.18,57.07 42.47,57.46C39.36,57.71 36.28,58.62 36.28,61.33C36.28,63.77 39.36,65 45.44,65L44.26,68.67Z" + android:fillColor="#ffffff"/> +</vector> diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml index 441f963a855a..e989372adde3 100644 --- a/packages/SystemUI/res/layout/combined_qs_header.xml +++ b/packages/SystemUI/res/layout/combined_qs_header.xml @@ -126,8 +126,7 @@ <com.android.systemui.battery.BatteryMeterView android:id="@+id/batteryRemainingIcon" android:layout_width="wrap_content" - android:layout_height="@dimen/large_screen_shade_header_min_height" - app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height" + android:layout_height="0dp" app:layout_constrainedWidth="true" app:textAppearance="@style/TextAppearance.QS.Status" app:layout_constraintStart_toEndOf="@id/statusIcons" diff --git a/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml b/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml index 0ea0653ab89f..473ab08a1935 100644 --- a/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml +++ b/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml @@ -24,11 +24,11 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center_vertical" - android:layout_marginStart="2.5dp" + android:layout_marginStart="2.5sp" > <FrameLayout android:id="@+id/inout_container" - android:layout_height="17dp" + android:layout_height="@*android:dimen/status_bar_system_icon_intrinsic_size" android:layout_width="wrap_content" android:gravity="center_vertical" > <ImageView @@ -37,14 +37,14 @@ android:layout_width="wrap_content" android:src="@drawable/ic_activity_down" android:visibility="gone" - android:paddingEnd="2dp" + android:paddingEnd="2sp" /> <ImageView android:id="@+id/wifi_out" android:layout_height="wrap_content" android:layout_width="wrap_content" android:src="@drawable/ic_activity_up" - android:paddingEnd="2dp" + android:paddingEnd="2sp" android:visibility="gone" /> </FrameLayout> @@ -62,7 +62,7 @@ <View android:id="@+id/wifi_signal_spacer" android:layout_width="@dimen/status_bar_wifi_signal_spacer_width" - android:layout_height="4dp" + android:layout_height="4sp" android:visibility="gone" /> <!-- Looks like CarStatusBar uses this... --> @@ -75,7 +75,7 @@ <View android:id="@+id/wifi_airplane_spacer" android:layout_width="@dimen/status_bar_airplane_spacer_width" - android:layout_height="4dp" + android:layout_height="4sp" android:visibility="gone" /> </com.android.keyguard.AlphaOptimizedLinearLayout> diff --git a/packages/SystemUI/res/raw/biometricprompt_rear_landscape_base.json b/packages/SystemUI/res/raw/biometricprompt_rear_landscape_base.json new file mode 100644 index 000000000000..49c1c40f7f4f --- /dev/null +++ b/packages/SystemUI/res/raw/biometricprompt_rear_landscape_base.json @@ -0,0 +1 @@ +{"v":"5.8.1","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"BiometricPrompt_Rear_Landscape_Base_Foldable","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 18","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[169.478,169.749,0],"ix":2,"l":2},"a":{"a":0,"k":[-48.123,-30.19,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".grey400","cl":"grey400","parent":13,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"black circle matte","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey904","cl":"grey904","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,35.536,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-2.552,0.087],[0,0]],"o":[[0,0],[0,-3.287],[0,0],[0,0]],"v":[[-2.301,8.869],[-2.301,-3.772],[2.301,-9.806],[2.301,9.806]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"black circle matte 2","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue401","cl":"blue401","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,-27.655,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,3.286],[0,0],[-2.552,0.086],[0,0]],"o":[[0,0],[0,-3.286],[0,0],[-2.552,-0.086]],"v":[[-2.301,16.282],[-2.301,-16.281],[2.301,-22.313],[2.301,22.313]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"black circle matte 3","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-129,"s":[-67]},{"t":-29,"s":[0]}],"ix":10},"p":{"a":0,"k":[-75.352,41.307,0],"ix":2,"l":2},"a":{"a":0,"k":[94.648,211.307,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[6.72,-5.642],[0,0],[-9.394,-0.562],[-0.298,-0.038]],"o":[[-5.153,4.329],[3.882,-16.05],[0.31,0.019],[-0.044,0.75]],"v":[[0.863,12.222],[-8.931,14.755],[8.005,-15.108],[8.931,-15.021]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[81.486,130.081],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.459,6.045],[-5.153,4.329],[-0.044,0.75],[3.116,-24.664],[5.23,-22.052],[8.666,11.92],[-2.9,9.135]],"o":[[0,0],[6.72,-5.642],[12.723,1.335],[-2.369,18.762],[-13.993,-5.333],[2.255,-5.502],[1.843,-5.815]],"v":[[-9.99,-18.348],[-0.196,-20.881],[7.872,-48.124],[21.578,-9.331],[12.104,48.124],[-22.574,21.555],[-14.791,-0.206]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[82.545,163.184],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 8","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"black circle matte 4","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".grey903","cl":"grey903","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-18.345,-92.442,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[24.07,0],[0,0],[-8.27,0],[0,0]],"o":[[0,0],[0,8.269],[0,0],[-14.024,-17.379]],"v":[[-29.778,-14.252],[-29.778,-0.721],[-14.805,14.252],[29.778,14.252]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"black circle matte 5","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".grey902","cl":"grey902","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-15.947,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[154.053,139.81,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.3,0.367],[0,0],[-2.364,0.157],[0,0]],"o":[[0,0],[2.3,-0.367],[0,0],[-2.364,-0.157]],"v":[[-3.5,75.533],[-3.5,-75.533],[3.5,-76.312],[3.5,76.312]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[113.225,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,8.269],[0,0],[2.181,-0.187],[0,0],[-2.23,0],[0,42.252],[10.593,13.127],[0,0]],"o":[[0,0],[-2.23,0],[0,0],[2.181,0.187],[42.252,0],[0,-18.182],[0,0],[-8.27,0]],"v":[[-34.946,-62.973],[-34.946,-76.504],[-41.558,-76.201],[-41.558,76.201],[-34.946,76.504],[41.558,0],[24.61,-48],[-19.973,-48]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[156.824,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 5","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".black 2","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-48.123,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-129,"s":[0,0,100]},{"t":-79,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".grey700","cl":"grey700","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-56.481,-59.936,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.767,0],[0,0],[0,-3.767],[0,0],[-3.767,0],[0,0],[0,3.767],[0,0]],"o":[[0,0],[-3.767,0],[0,0],[0,3.767],[0,0],[3.767,0],[0,0],[0,-3.767]],"v":[[46.055,-14.479],[-46.056,-14.479],[-52.876,-7.659],[-52.876,7.658],[-46.056,14.479],[46.055,14.479],[52.876,7.658],[52.876,-7.659]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".grey901","cl":"grey901","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[16.485,2.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[50,50,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[4.184,0],[0,0],[0,0],[0,0],[0,-4.375]],"o":[[0,4.184],[0,0],[0,0],[0,0],[4.375,0],[0,0]],"v":[[114.116,92.129],[106.54,99.705],[7.788,99.705],[7.788,-99.704],[106.161,-99.704],[114.116,-91.749]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[5.707,0],[0,0],[1.894,-1.05],[0.886,0.346],[0,0],[2.166,0],[0,0],[0,-5.707],[0,0],[0,-1.46],[0,0],[-1.133,-0.038],[0,0],[0,-1.459],[0,0],[-1.133,-0.038],[0,0],[-5.708,0],[0,0],[-1.894,1.05],[-0.846,-0.289],[0,0],[-2.166,0],[0,0],[0,5.706],[0,0]],"o":[[0,0],[-2.166,0],[-0.883,0.354],[0,0],[-1.895,-1.05],[0,0],[-5.708,0],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[0,5.707],[0,0],[2.165,0],[0.833,-0.334],[0,0],[1.894,1.05],[0,0],[5.707,0],[0,0],[0,-5.707]],"v":[[106.16,-102.082],[8.455,-102.082],[2.265,-100.48],[-0.488,-100.468],[-0.519,-100.48],[-6.71,-102.082],[-104.116,-102.082],[-114.45,-91.748],[-114.45,-36.119],[-116.494,-33.44],[-116.494,-18.979],[-114.45,-16.3],[-114.45,-0.877],[-116.494,1.802],[-116.494,28.704],[-114.45,31.383],[-114.45,91.749],[-104.116,102.083],[-6.495,102.083],[-0.305,100.481],[2.294,100.425],[2.395,100.481],[9.872,102.083],[106.161,102.083],[116.494,91.75],[116.494,-91.748]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.529411792755,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file diff --git a/packages/SystemUI/res/raw/biometricprompt_rear_portrait_base.json b/packages/SystemUI/res/raw/biometricprompt_rear_portrait_base.json new file mode 100644 index 000000000000..9ea0d35e1de2 --- /dev/null +++ b/packages/SystemUI/res/raw/biometricprompt_rear_portrait_base.json @@ -0,0 +1 @@ +{"v":"5.8.1","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"BiometricPrompt_Rear_Portrait_Base_Foldable","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 18","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[169.478,169.749,0],"ix":2,"l":2},"a":{"a":0,"k":[-48.123,-30.19,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".grey400","cl":"grey400","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"black circle matte","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey904","cl":"grey904","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,35.536,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-2.552,0.087],[0,0]],"o":[[0,0],[0,-3.287],[0,0],[0,0]],"v":[[-2.301,8.869],[-2.301,-3.772],[2.301,-9.806],[2.301,9.806]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"black circle matte 2","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue401","cl":"blue401","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,-27.655,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,3.286],[0,0],[-2.552,0.086],[0,0]],"o":[[0,0],[0,-3.286],[0,0],[-2.552,-0.086]],"v":[[-2.301,16.282],[-2.301,-16.281],[2.301,-22.313],[2.301,22.313]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"black circle matte 3","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Finger 3","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-2,"ix":10},"p":{"a":0,"k":[260.134,83.782,0],"ix":2,"l":2},"a":{"a":0,"k":[302.634,38.782,0],"ix":1,"l":2},"s":{"a":0,"k":[178,178,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.262,5.076],[0,0],[-0.424,-7.095],[-0.028,-0.225]],"o":[[3.269,-3.892],[-12.123,2.932],[0.015,0.234],[0.567,-0.034]],"v":[[9.232,0.652],[11.145,-6.746],[-11.412,6.046],[-11.346,6.746]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[241.281,55.033],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 5","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[4.565,-1.102],[3.269,-3.892],[0.566,-0.033],[-18.63,2.353],[-16.656,3.951],[9.004,6.546],[6.9,-2.19]],"o":[[0,0],[-4.262,5.076],[1.008,9.61],[14.171,-1.79],[-4.028,-10.569],[-4.156,1.703],[-4.392,1.392]],"v":[[-13.858,-7.546],[-15.771,-0.148],[-36.349,5.946],[-7.047,16.299],[36.349,9.142],[16.281,-17.051],[-0.156,-11.172]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[266.285,55.833],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 4","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"black circle matte 4","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".grey903","cl":"grey903","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-18.345,-92.442,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[24.07,0],[0,0],[-8.27,0],[0,0]],"o":[[0,0],[0,8.269],[0,0],[-14.024,-17.379]],"v":[[-29.778,-14.252],[-29.778,-0.721],[-14.805,14.252],[29.778,14.252]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"black circle matte 5","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".grey902","cl":"grey902","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-15.947,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[154.053,139.81,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.3,0.367],[0,0],[-2.364,0.157],[0,0]],"o":[[0,0],[2.3,-0.367],[0,0],[-2.364,-0.157]],"v":[[-3.5,75.533],[-3.5,-75.533],[3.5,-76.312],[3.5,76.312]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[113.225,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,8.269],[0,0],[2.181,-0.187],[0,0],[-2.23,0],[0,42.252],[10.593,13.127],[0,0]],"o":[[0,0],[-2.23,0],[0,0],[2.181,0.187],[42.252,0],[0,-18.182],[0,0],[-8.27,0]],"v":[[-34.946,-62.973],[-34.946,-76.504],[-41.558,-76.201],[-41.558,76.201],[-34.946,76.504],[41.558,0],[24.61,-48],[-19.973,-48]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[156.824,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 5","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".black 2","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-48.123,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-129,"s":[0,0,100]},{"t":-79,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".grey700","cl":"grey700","parent":16,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-56.481,-59.936,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.767,0],[0,0],[0,-3.767],[0,0],[-3.767,0],[0,0],[0,3.767],[0,0]],"o":[[0,0],[-3.767,0],[0,0],[0,3.767],[0,0],[3.767,0],[0,0],[0,-3.767]],"v":[[46.055,-14.479],[-46.056,-14.479],[-52.876,-7.659],[-52.876,7.658],[-46.056,14.479],[46.055,14.479],[52.876,7.658],[52.876,-7.659]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".grey901","cl":"grey901","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[16.485,2.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[50,50,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[4.184,0],[0,0],[0,0],[0,0],[0,-4.375]],"o":[[0,4.184],[0,0],[0,0],[0,0],[4.375,0],[0,0]],"v":[[114.116,92.129],[106.54,99.705],[7.788,99.705],[7.788,-99.704],[106.161,-99.704],[114.116,-91.749]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[5.707,0],[0,0],[1.894,-1.05],[0.886,0.346],[0,0],[2.166,0],[0,0],[0,-5.707],[0,0],[0,-1.46],[0,0],[-1.133,-0.038],[0,0],[0,-1.459],[0,0],[-1.133,-0.038],[0,0],[-5.708,0],[0,0],[-1.894,1.05],[-0.846,-0.289],[0,0],[-2.166,0],[0,0],[0,5.706],[0,0]],"o":[[0,0],[-2.166,0],[-0.883,0.354],[0,0],[-1.895,-1.05],[0,0],[-5.708,0],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[0,5.707],[0,0],[2.165,0],[0.833,-0.334],[0,0],[1.894,1.05],[0,0],[5.707,0],[0,0],[0,-5.707]],"v":[[106.16,-102.082],[8.455,-102.082],[2.265,-100.48],[-0.488,-100.468],[-0.519,-100.48],[-6.71,-102.082],[-104.116,-102.082],[-114.45,-91.748],[-114.45,-36.119],[-116.494,-33.44],[-116.494,-18.979],[-114.45,-16.3],[-114.45,-0.877],[-116.494,1.802],[-116.494,28.704],[-114.45,31.383],[-114.45,91.749],[-104.116,102.083],[-6.495,102.083],[-0.305,100.481],[2.294,100.425],[2.395,100.481],[9.872,102.083],[106.161,102.083],[116.494,91.75],[116.494,-91.748]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.529411792755,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file diff --git a/packages/SystemUI/res/raw/biometricprompt_rear_portrait_reverse_base.json b/packages/SystemUI/res/raw/biometricprompt_rear_portrait_reverse_base.json new file mode 100644 index 000000000000..f2b259335034 --- /dev/null +++ b/packages/SystemUI/res/raw/biometricprompt_rear_portrait_reverse_base.json @@ -0,0 +1 @@ +{"v":"5.8.1","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"BiometricPrompt_Rear_Portrait_Reverse_Base_Foldable","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 18","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":270,"ix":10},"p":{"a":0,"k":[169.478,169.749,0],"ix":2,"l":2},"a":{"a":0,"k":[-48.123,-30.19,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".grey400","cl":"grey400","parent":13,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"black circle matte","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey904","cl":"grey904","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,35.536,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-2.552,0.087],[0,0]],"o":[[0,0],[0,-3.287],[0,0],[0,0]],"v":[[-2.301,8.869],[-2.301,-3.772],[2.301,-9.806],[2.301,9.806]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"black circle matte 2","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue401","cl":"blue401","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,-27.655,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,3.286],[0,0],[-2.552,0.086],[0,0]],"o":[[0,0],[0,-3.286],[0,0],[-2.552,-0.086]],"v":[[-2.301,16.282],[-2.301,-16.281],[2.301,-22.313],[2.301,22.313]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"black circle matte 3","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-75.352,41.307,0],"ix":2,"l":2},"a":{"a":0,"k":[94.648,211.307,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[6.72,-5.642],[0,0],[-9.394,-0.562],[-0.298,-0.038]],"o":[[-5.153,4.329],[3.882,-16.05],[0.31,0.019],[-0.044,0.75]],"v":[[0.863,12.222],[-8.931,14.755],[8.005,-15.108],[8.931,-15.021]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[81.486,130.081],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.459,6.045],[-5.153,4.329],[-0.044,0.75],[3.116,-24.664],[5.23,-22.052],[8.666,11.92],[-2.9,9.135]],"o":[[0,0],[6.72,-5.642],[12.723,1.335],[-2.369,18.762],[-13.993,-5.333],[2.255,-5.502],[1.843,-5.815]],"v":[[-9.99,-18.348],[-0.196,-20.881],[7.872,-48.124],[21.578,-9.331],[12.104,48.124],[-22.574,21.555],[-14.791,-0.206]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[82.545,163.184],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 8","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"black circle matte 4","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".grey903","cl":"grey903","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-18.345,-92.442,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[24.07,0],[0,0],[-8.27,0],[0,0]],"o":[[0,0],[0,8.269],[0,0],[-14.024,-17.379]],"v":[[-29.778,-14.252],[-29.778,-0.721],[-14.805,14.252],[29.778,14.252]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"black circle matte 5","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".grey902","cl":"grey902","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-15.947,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[154.053,139.81,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.3,0.367],[0,0],[-2.364,0.157],[0,0]],"o":[[0,0],[2.3,-0.367],[0,0],[-2.364,-0.157]],"v":[[-3.5,75.533],[-3.5,-75.533],[3.5,-76.312],[3.5,76.312]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[113.225,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,8.269],[0,0],[2.181,-0.187],[0,0],[-2.23,0],[0,42.252],[10.593,13.127],[0,0]],"o":[[0,0],[-2.23,0],[0,0],[2.181,0.187],[42.252,0],[0,-18.182],[0,0],[-8.27,0]],"v":[[-34.946,-62.973],[-34.946,-76.504],[-41.558,-76.201],[-41.558,76.201],[-34.946,76.504],[41.558,0],[24.61,-48],[-19.973,-48]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[156.824,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 5","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".black 2","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-48.123,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-129,"s":[0,0,100]},{"t":-79,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".grey700","cl":"grey700","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-56.481,-59.936,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.767,0],[0,0],[0,-3.767],[0,0],[-3.767,0],[0,0],[0,3.767],[0,0]],"o":[[0,0],[-3.767,0],[0,0],[0,3.767],[0,0],[3.767,0],[0,0],[0,-3.767]],"v":[[46.055,-14.479],[-46.056,-14.479],[-52.876,-7.659],[-52.876,7.658],[-46.056,14.479],[46.055,14.479],[52.876,7.658],[52.876,-7.659]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".grey901","cl":"grey901","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[16.485,2.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[50,50,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[4.184,0],[0,0],[0,0],[0,0],[0,-4.375]],"o":[[0,4.184],[0,0],[0,0],[0,0],[4.375,0],[0,0]],"v":[[114.116,92.129],[106.54,99.705],[7.788,99.705],[7.788,-99.704],[106.161,-99.704],[114.116,-91.749]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[5.707,0],[0,0],[1.894,-1.05],[0.886,0.346],[0,0],[2.166,0],[0,0],[0,-5.707],[0,0],[0,-1.46],[0,0],[-1.133,-0.038],[0,0],[0,-1.459],[0,0],[-1.133,-0.038],[0,0],[-5.708,0],[0,0],[-1.894,1.05],[-0.846,-0.289],[0,0],[-2.166,0],[0,0],[0,5.706],[0,0]],"o":[[0,0],[-2.166,0],[-0.883,0.354],[0,0],[-1.895,-1.05],[0,0],[-5.708,0],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[0,5.707],[0,0],[2.165,0],[0.833,-0.334],[0,0],[1.894,1.05],[0,0],[5.707,0],[0,0],[0,-5.707]],"v":[[106.16,-102.082],[8.455,-102.082],[2.265,-100.48],[-0.488,-100.468],[-0.519,-100.48],[-6.71,-102.082],[-104.116,-102.082],[-114.45,-91.748],[-114.45,-36.119],[-116.494,-33.44],[-116.494,-18.979],[-114.45,-16.3],[-114.45,-0.877],[-116.494,1.802],[-116.494,28.704],[-114.45,31.383],[-114.45,91.749],[-104.116,102.083],[-6.495,102.083],[-0.305,100.481],[2.294,100.425],[2.395,100.481],[9.872,102.083],[106.161,102.083],[116.494,91.75],[116.494,-91.748]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.529411792755,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml index 20864591ae5a..f40615eb46d0 100644 --- a/packages/SystemUI/res/values-sw720dp/dimens.xml +++ b/packages/SystemUI/res/values-sw720dp/dimens.xml @@ -17,7 +17,7 @@ --> <resources> <!-- gap on either side of status bar notification icons --> - <dimen name="status_bar_icon_padding">1dp</dimen> + <dimen name="status_bar_icon_padding">1sp</dimen> <dimen name="controls_header_horizontal_padding">28dp</dimen> <dimen name="controls_content_margin_horizontal">40dp</dimen> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 0aa880fe6d88..f5c4a4e4bb52 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -122,26 +122,26 @@ <dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen> <!-- Default horizontal drawable padding for status bar icons. --> - <dimen name="status_bar_horizontal_padding">2.5dp</dimen> + <dimen name="status_bar_horizontal_padding">2.5sp</dimen> <!-- Height of the battery icon in the status bar. --> - <dimen name="status_bar_battery_icon_height">13.0dp</dimen> + <dimen name="status_bar_battery_icon_height">13.0sp</dimen> <!-- Width of the battery icon in the status bar. The battery drawable assumes a 12x20 canvas, - so the width of the icon should be 13.0dp * (12.0 / 20.0) --> - <dimen name="status_bar_battery_icon_width">7.8dp</dimen> + so the width of the icon should be 13.0sp * (12.0 / 20.0) --> + <dimen name="status_bar_battery_icon_width">7.8sp</dimen> - <!-- The battery icon is 13dp tall, but the other system icons are 15dp tall (see + <!-- The battery icon is 13sp tall, but the other system icons are 15sp tall (see @*android:dimen/status_bar_system_icon_size) with some top and bottom padding embedded in - the drawables themselves. So, the battery icon may need an extra 1dp of spacing so that its + the drawables themselves. So, the battery icon may need an extra 1sp of spacing so that its bottom still aligns with the bottom of all the other system icons. See b/258672854. --> - <dimen name="status_bar_battery_extra_vertical_spacing">1dp</dimen> + <dimen name="status_bar_battery_extra_vertical_spacing">1sp</dimen> <!-- The font size for the clock in the status bar. --> <dimen name="status_bar_clock_size">14sp</dimen> <!-- The starting padding for the clock in the status bar. --> - <dimen name="status_bar_clock_starting_padding">7dp</dimen> + <dimen name="status_bar_clock_starting_padding">7sp</dimen> <!-- The end padding for the clock in the status bar. --> <dimen name="status_bar_clock_end_padding">0dp</dimen> @@ -153,16 +153,19 @@ <dimen name="status_bar_left_clock_end_padding">2dp</dimen> <!-- Spacing after the wifi signals that is present if there are any icons following it. --> - <dimen name="status_bar_wifi_signal_spacer_width">2.5dp</dimen> + <dimen name="status_bar_wifi_signal_spacer_width">2.5sp</dimen> <!-- Size of the view displaying the wifi signal icon in the status bar. --> - <dimen name="status_bar_wifi_signal_size">@*android:dimen/status_bar_system_icon_size</dimen> + <dimen name="status_bar_wifi_signal_size">13sp</dimen> + + <!-- Size of the view displaying the mobile signal icon in the status bar. --> + <dimen name="status_bar_mobile_signal_size">13sp</dimen> <!-- Spacing before the airplane mode icon if there are any icons preceding it. --> - <dimen name="status_bar_airplane_spacer_width">4dp</dimen> + <dimen name="status_bar_airplane_spacer_width">4sp</dimen> <!-- Spacing between system icons. --> - <dimen name="status_bar_system_icon_spacing">0dp</dimen> + <dimen name="status_bar_system_icon_spacing">2sp</dimen> <!-- The amount to scale each of the status bar icons by. A value of 1 means no scaling. --> <item name="status_bar_icon_scale_factor" format="float" type="dimen">1.0</item> @@ -310,7 +313,7 @@ <dimen name="snooze_snackbar_min_height">56dp</dimen> <!-- size at which Notification icons will be drawn in the status bar --> - <dimen name="status_bar_icon_drawing_size">15dp</dimen> + <dimen name="status_bar_icon_drawing_size">15sp</dimen> <!-- size at which Notification icons will be drawn on Ambient Display --> <dimen name="status_bar_icon_drawing_size_dark"> @@ -321,22 +324,22 @@ <item type="dimen" name="status_bar_icon_drawing_alpha">90%</item> <!-- gap on either side of status bar notification icons --> - <dimen name="status_bar_icon_padding">0dp</dimen> + <dimen name="status_bar_icon_padding">0sp</dimen> <!-- the padding on the start of the statusbar --> - <dimen name="status_bar_padding_start">8dp</dimen> + <dimen name="status_bar_padding_start">8sp</dimen> <!-- the padding on the end of the statusbar --> - <dimen name="status_bar_padding_end">8dp</dimen> + <dimen name="status_bar_padding_end">8sp</dimen> <!-- the padding on the top of the statusbar (usually 0) --> - <dimen name="status_bar_padding_top">0dp</dimen> + <dimen name="status_bar_padding_top">0sp</dimen> <!-- the radius of the overflow dot in the status bar --> - <dimen name="overflow_dot_radius">2dp</dimen> + <dimen name="overflow_dot_radius">2sp</dimen> <!-- the padding between dots in the icon overflow --> - <dimen name="overflow_icon_dot_padding">3dp</dimen> + <dimen name="overflow_icon_dot_padding">3sp</dimen> <!-- Dimensions related to screenshots --> @@ -617,8 +620,8 @@ <dimen name="qs_footer_icon_size">20dp</dimen> <dimen name="qs_header_row_min_height">48dp</dimen> - <dimen name="qs_header_non_clickable_element_height">24dp</dimen> - <dimen name="new_qs_header_non_clickable_element_height">24dp</dimen> + <dimen name="qs_header_non_clickable_element_height">24sp</dimen> + <dimen name="new_qs_header_non_clickable_element_height">24sp</dimen> <dimen name="qs_footer_padding">20dp</dimen> <dimen name="qs_security_footer_height">88dp</dimen> @@ -822,7 +825,7 @@ <!-- Padding between the mobile signal indicator and the start icon when the roaming icon is displayed in the upper left corner. --> - <dimen name="roaming_icon_start_padding">2dp</dimen> + <dimen name="roaming_icon_start_padding">2sp</dimen> <!-- Extra padding between the mobile data type icon and the strength indicator when the data type icon is wide for the tile in quick settings. --> @@ -1042,13 +1045,13 @@ <dimen name="display_cutout_margin_consumption">0px</dimen> <!-- Height of the Ongoing App Ops chip --> - <dimen name="ongoing_appops_chip_height">24dp</dimen> + <dimen name="ongoing_appops_chip_height">24sp</dimen> <!-- Side padding between background of Ongoing App Ops chip and content --> <dimen name="ongoing_appops_chip_side_padding">8dp</dimen> <!-- Margin between icons of Ongoing App Ops chip --> <dimen name="ongoing_appops_chip_icon_margin">4dp</dimen> <!-- Icon size of Ongoing App Ops chip --> - <dimen name="ongoing_appops_chip_icon_size">16dp</dimen> + <dimen name="ongoing_appops_chip_icon_size">16sp</dimen> <!-- Radius of Ongoing App Ops chip corners --> <dimen name="ongoing_appops_chip_bg_corner_radius">28dp</dimen> <!-- One or two privacy items --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 853930e174a7..a51afe905354 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1257,7 +1257,10 @@ <string name="monitoring_description_managed_profile_network_logging">Your admin has turned on network logging, which monitors traffic in your work profile but not in your personal profile.</string> <!-- Monitoring dialog: Description of an active VPN. [CHAR LIMIT=NONE]--> - <string name="monitoring_description_named_vpn">This device is connected to the internet through <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g>. Your network activity, including emails and browsing data, is visible to your IT admin.</string> + <string name="monitoring_description_named_vpn">This device is connected to the internet through <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g>. Your network activity, including emails and browsing data, is visible to the VPN provider.</string> + + <!-- Monitoring dialog: Description of an active VPN on a managed device. [CHAR LIMIT=NONE]--> + <string name="monitoring_description_managed_device_named_vpn">This device is connected to the internet through <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g>. Your network activity, including emails and browsing data, is visible to your IT admin.</string> <!-- Monitoring dialog: Description of two active VPNs. [CHAR LIMIT=NONE]--> <string name="monitoring_description_two_named_vpns">This device is connected to the internet through <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g> and <xliff:g id="vpn_app" example="Bar VPN App">%2$s</xliff:g>. Your network activity, including emails and browsing data, is visible to your IT admin.</string> @@ -3035,6 +3038,19 @@ --> <string name="keyguard_affordance_enablement_dialog_home_instruction_2">• At least one device is available</string> + <!--- + Requirement for the notes app to be available for the user to use. This is shown as part of a + bulleted list of requirements. When all requirements are met, the app can be accessed through a + shortcut button on the lock screen. [CHAR LIMIT=NONE] --> + <string name="keyguard_affordance_enablement_dialog_notes_app_instruction">Select a default notes app to use notetaking shortcut</string> + + <!--- + The action to make the lock screen shortcut for the notes app to be available for the user to + use. This is shown as the action button in the dialog listing the requirements. When all + requirements are met, the app can be accessed through a shortcut button on the lock screen. + [CHAR LIMIT=NONE] --> + <string name="keyguard_affordance_enablement_dialog_notes_app_action">Open settings</string> + <!-- Error message shown when a shortcut must be pressed and held to activate it, usually shown when the user tried to tap the shortcut or held it for too short a time. [CHAR LIMIT=32]. diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java index 362d7a9390d4..7cf3121957ca 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java @@ -68,7 +68,7 @@ public interface TaskStackChangeListener { onActivityLaunchOnSecondaryDisplayRerouted(); } - default void onTaskProfileLocked(RunningTaskInfo taskInfo) { } + default void onTaskProfileLocked(RunningTaskInfo taskInfo, int userId) { } default void onTaskCreated(int taskId, ComponentName componentName) { } default void onTaskRemoved(int taskId) { } default void onTaskMovedToFront(int taskId) { } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java index dd52cfbdc80f..c613afbda5b8 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java @@ -262,8 +262,8 @@ public class TaskStackChangeListeners { } @Override - public void onTaskProfileLocked(RunningTaskInfo taskInfo) { - mHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, taskInfo).sendToTarget(); + public void onTaskProfileLocked(RunningTaskInfo taskInfo, int userId) { + mHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, userId, 0, taskInfo).sendToTarget(); } @Override @@ -418,8 +418,9 @@ public class TaskStackChangeListeners { } case ON_TASK_PROFILE_LOCKED: { final RunningTaskInfo info = (RunningTaskInfo) msg.obj; + final int userId = msg.arg1; for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { - mTaskStackListeners.get(i).onTaskProfileLocked(info); + mTaskStackListeners.get(i).onTaskProfileLocked(info, userId); } break; } diff --git a/packages/SystemUI/src-debug/com/android/systemui/log/DebugLogger.kt b/packages/SystemUI/src-debug/com/android/systemui/log/DebugLogger.kt new file mode 100644 index 000000000000..af29b05a3fb1 --- /dev/null +++ b/packages/SystemUI/src-debug/com/android/systemui/log/DebugLogger.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log + +import android.os.Build +import android.util.Log +import android.util.Log.LOG_ID_MAIN + +/** + * A simplified debug logger built as a wrapper around Android's [Log]. Internal for development. + * + * The main advantages are: + * - Sensible defaults, automatically retrieving the class name from the call-site (i.e., tag); + * - The messages are purged from source on release builds (keep in mind they are visible on AOSP); + * - Lazily evaluate Strings for zero impact in production builds or when disabled; + * + * Usage example: + * ```kotlin + * // Logging a message: + * debugLog { "message" } + * + * // Logging an error: + * debugLog(error = exception) { "message" } + * + * // Logging the current stack trace, for debugging: + * debugLog(error = Throwable()) { "message" } + * ``` + */ +object DebugLogger { + + /** + * Log a debug message, with sensible defaults. + * + * For example: + * ```kotlin + * val one = 1 + * debugLog { "message#$one" } + * ``` + * + * The output will be: `D/NoteTaskController: message#1` + * + * Beware, the [debugLog] content is **REMOVED FROM SOURCE AND BINARY** in Release builds. + * + * @param enabled: whether or not the message should be logged. By default, it is + * [Build.IS_DEBUGGABLE]. + * @param priority: type of this log. By default, it is [Log.DEBUG]. + * @param tag: identifies the source of a log. By default, it is the receiver's simple name. + * @param error: a [Throwable] to log. + * @param message: a lazily evaluated message you wish to log. + */ + inline fun Any.debugLog( + enabled: Boolean = Build.IS_DEBUGGABLE, + priority: Int = Log.DEBUG, + tag: String = this::class.simpleName.orEmpty(), + error: Throwable? = null, + message: () -> String, + ) { + if (enabled) { + if (error == null) { + Log.println(priority, tag, message()) + } else { + Log.printlns(LOG_ID_MAIN, priority, tag, message(), error) + } + } + } +} diff --git a/packages/SystemUI/src-release/com/android/systemui/log/DebugLogger.kt b/packages/SystemUI/src-release/com/android/systemui/log/DebugLogger.kt new file mode 100644 index 000000000000..2764a1fdfe3d --- /dev/null +++ b/packages/SystemUI/src-release/com/android/systemui/log/DebugLogger.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log + +import android.os.Build +import android.util.Log + +/** An empty logger for release builds. */ +object DebugLogger { + + @JvmName("logcatMessage") + inline fun Any.debugLog( + enabled: Boolean = Build.IS_DEBUGGABLE, + priority: Int = Log.DEBUG, + tag: String = this::class.simpleName.orEmpty(), + error: Throwable? = null, + message: () -> String, + ) { + // no-op. + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt index 4b5c50ff7fa5..5499d2c07181 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt @@ -19,7 +19,6 @@ package com.android.systemui.biometrics import android.annotation.RawRes import android.content.Context import android.content.Context.FINGERPRINT_SERVICE -import android.content.res.Configuration import android.hardware.fingerprint.FingerprintManager import android.view.DisplayInfo import android.view.Surface @@ -35,21 +34,18 @@ import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP import com.android.systemui.biometrics.AuthBiometricView.STATE_IDLE import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION -import com.android.systemui.unfold.compat.ScreenSizeFoldProvider -import com.android.systemui.unfold.updates.FoldProvider /** Fingerprint only icon animator for BiometricPrompt. */ open class AuthBiometricFingerprintIconController( context: Context, iconView: LottieAnimationView, protected val iconViewOverlay: LottieAnimationView -) : AuthIconController(context, iconView), FoldProvider.FoldCallback { +) : AuthIconController(context, iconView) { - private var isDeviceFolded: Boolean = false private val isSideFps: Boolean private val isReverseDefaultRotation = context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation) - private val screenSizeFoldProvider: ScreenSizeFoldProvider = ScreenSizeFoldProvider(context) + var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1) set(value) { if (field == value) { @@ -77,20 +73,16 @@ open class AuthBiometricFingerprintIconController( if (isSideFps && getRotationFromDefault(displayInfo.rotation) == Surface.ROTATION_180) { iconView.rotation = 180f } - screenSizeFoldProvider.registerCallback(this, context.mainExecutor) - screenSizeFoldProvider.onConfigurationChange(context.resources.configuration) } private fun updateIconSideFps(@BiometricState lastState: Int, @BiometricState newState: Int) { val displayInfo = DisplayInfo() context.display?.getDisplayInfo(displayInfo) val rotation = getRotationFromDefault(displayInfo.rotation) - val iconAnimation = getSideFpsAnimationForTransition(rotation) val iconViewOverlayAnimation = getSideFpsOverlayAnimationForTransition(lastState, newState, rotation) ?: return if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) { - iconView.setAnimation(iconAnimation) iconViewOverlay.setAnimation(iconViewOverlayAnimation) } @@ -132,10 +124,6 @@ open class AuthBiometricFingerprintIconController( LottieColorUtils.applyDynamicColors(context, iconView) } - override fun onConfigurationChanged(newConfig: Configuration) { - screenSizeFoldProvider.onConfigurationChange(newConfig) - } - override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) { if (isSideFps) { updateIconSideFps(lastState, newState) @@ -230,25 +218,6 @@ open class AuthBiometricFingerprintIconController( if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation @RawRes - private fun getSideFpsAnimationForTransition(rotation: Int): Int = when (rotation) { - Surface.ROTATION_90 -> if (isDeviceFolded) { - R.raw.biometricprompt_folded_base_topleft - } else { - R.raw.biometricprompt_portrait_base_topleft - } - Surface.ROTATION_270 -> if (isDeviceFolded) { - R.raw.biometricprompt_folded_base_bottomright - } else { - R.raw.biometricprompt_portrait_base_bottomright - } - else -> if (isDeviceFolded) { - R.raw.biometricprompt_folded_base_default - } else { - R.raw.biometricprompt_landscape_base - } - } - - @RawRes private fun getSideFpsOverlayAnimationForTransition( @BiometricState oldState: Int, @BiometricState newState: Int, @@ -357,8 +326,4 @@ open class AuthBiometricFingerprintIconController( ) } } - - override fun onFoldUpdated(isFolded: Boolean) { - isDeviceFolded = isFolded - } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java index e04dd0680060..f330c34bfaa0 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java @@ -124,6 +124,7 @@ public abstract class AuthBiometricView extends LinearLayout { protected final int mTextColorHint; private AuthPanelController mPanelController; + private PromptInfo mPromptInfo; private boolean mRequireConfirmation; private int mUserId; @@ -266,11 +267,9 @@ public abstract class AuthBiometricView extends LinearLayout { /** Create the controller for managing the icons transitions during the prompt.*/ @NonNull protected abstract AuthIconController createIconController(); - void setPanelController(AuthPanelController panelController) { mPanelController = panelController; } - void setPromptInfo(PromptInfo promptInfo) { mPromptInfo = promptInfo; } @@ -463,9 +462,16 @@ public abstract class AuthBiometricView extends LinearLayout { return false; } + /** + * Updates mIconView animation on updates to fold state, device rotation, or rear display mode + * @param animation new asset to use for iconw + */ + public void updateIconViewAnimation(int animation) { + mIconView.setAnimation(animation); + } + public void updateState(@BiometricState int newState) { Log.v(TAG, "newState: " + newState); - mIconController.updateState(mState, newState); switch (newState) { @@ -625,7 +631,6 @@ public abstract class AuthBiometricView extends LinearLayout { public void restoreState(@Nullable Bundle savedState) { mSavedState = savedState; } - private void setTextOrHide(TextView view, CharSequence charSequence) { if (TextUtils.isEmpty(charSequence)) { view.setVisibility(View.GONE); @@ -658,7 +663,6 @@ public abstract class AuthBiometricView extends LinearLayout { @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - mIconController.onConfigurationChanged(newConfig); if (mSavedState != null) { updateState(mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_STATE)); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index aeebb010eb1e..b386bc9d050b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -67,6 +67,8 @@ import com.android.systemui.animation.Interpolators; import com.android.systemui.biometrics.AuthController.ScaleFactorProvider; import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor; import com.android.systemui.biometrics.ui.CredentialView; +import com.android.systemui.biometrics.ui.binder.AuthBiometricFingerprintViewBinder; +import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel; import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.keyguard.WakefulnessLifecycle; @@ -126,6 +128,8 @@ public class AuthContainerView extends LinearLayout // TODO: these should be migrated out once ready private final Provider<BiometricPromptCredentialInteractor> mBiometricPromptInteractor; + private final Provider<AuthBiometricFingerprintViewModel> + mAuthBiometricFingerprintViewModelProvider; private final Provider<CredentialViewModel> mCredentialViewModelProvider; @VisibleForTesting final BiometricCallback mBiometricCallback; @@ -241,12 +245,14 @@ public class AuthContainerView extends LinearLayout @NonNull LockPatternUtils lockPatternUtils, @NonNull InteractionJankMonitor jankMonitor, @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor, + @NonNull Provider<AuthBiometricFingerprintViewModel> + authBiometricFingerprintViewModelProvider, @NonNull Provider<CredentialViewModel> credentialViewModelProvider) { mConfig.mSensorIds = sensorIds; return new AuthContainerView(mConfig, fpProps, faceProps, wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils, jankMonitor, - biometricPromptInteractor, credentialViewModelProvider, - new Handler(Looper.getMainLooper()), bgExecutor); + biometricPromptInteractor, authBiometricFingerprintViewModelProvider, + credentialViewModelProvider, new Handler(Looper.getMainLooper()), bgExecutor); } } @@ -339,6 +345,8 @@ public class AuthContainerView extends LinearLayout @NonNull LockPatternUtils lockPatternUtils, @NonNull InteractionJankMonitor jankMonitor, @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor, + @NonNull Provider<AuthBiometricFingerprintViewModel> + authBiometricFingerprintViewModelProvider, @NonNull Provider<CredentialViewModel> credentialViewModelProvider, @NonNull Handler mainHandler, @NonNull @Background DelayableExecutor bgExecutor) { @@ -368,6 +376,7 @@ public class AuthContainerView extends LinearLayout mBackgroundExecutor = bgExecutor; mInteractionJankMonitor = jankMonitor; mBiometricPromptInteractor = biometricPromptInteractor; + mAuthBiometricFingerprintViewModelProvider = authBiometricFingerprintViewModelProvider; mCredentialViewModelProvider = credentialViewModelProvider; // Inflate biometric view only if necessary. @@ -386,6 +395,9 @@ public class AuthContainerView extends LinearLayout fingerprintAndFaceView.updateOverrideIconLayoutParamsSize(); fingerprintAndFaceView.setFaceClass3( faceProperties.sensorStrength == STRENGTH_STRONG); + final AuthBiometricFingerprintViewModel fpAndFaceViewModel = + mAuthBiometricFingerprintViewModelProvider.get(); + AuthBiometricFingerprintViewBinder.bind(fingerprintAndFaceView, fpAndFaceViewModel); mBiometricView = fingerprintAndFaceView; } else if (fpProperties != null) { final AuthBiometricFingerprintView fpView = @@ -394,6 +406,9 @@ public class AuthContainerView extends LinearLayout fpView.setSensorProperties(fpProperties); fpView.setScaleFactorProvider(config.mScaleProvider); fpView.updateOverrideIconLayoutParamsSize(); + final AuthBiometricFingerprintViewModel fpViewModel = + mAuthBiometricFingerprintViewModelProvider.get(); + AuthBiometricFingerprintViewBinder.bind(fpView, fpViewModel); mBiometricView = fpView; } else if (faceProperties != null) { mBiometricView = (AuthBiometricFaceView) layoutInflater.inflate( diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 6eb3c708e9b5..3579e8cc0218 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -73,6 +73,7 @@ import com.android.settingslib.udfps.UdfpsUtils; import com.android.systemui.CoreStartable; import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor; import com.android.systemui.biometrics.domain.interactor.LogContextInteractor; +import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel; import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; @@ -127,6 +128,9 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, // TODO: these should be migrated out once ready @NonNull private final Provider<BiometricPromptCredentialInteractor> mBiometricPromptInteractor; + + @NonNull private final Provider<AuthBiometricFingerprintViewModel> + mAuthBiometricFingerprintViewModelProvider; @NonNull private final Provider<CredentialViewModel> mCredentialViewModelProvider; @NonNull private final LogContextInteractor mLogContextInteractor; @@ -722,7 +726,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, } onDialogDismissed(reason); } - @Inject public AuthController(Context context, Execution execution, @@ -741,6 +744,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, @NonNull UdfpsLogger udfpsLogger, @NonNull LogContextInteractor logContextInteractor, @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor, + @NonNull Provider<AuthBiometricFingerprintViewModel> + authBiometricFingerprintViewModelProvider, @NonNull Provider<CredentialViewModel> credentialViewModelProvider, @NonNull InteractionJankMonitor jankMonitor, @Main Handler handler, @@ -771,6 +776,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, mLogContextInteractor = logContextInteractor; mBiometricPromptInteractor = biometricPromptInteractor; + mAuthBiometricFingerprintViewModelProvider = authBiometricFingerprintViewModelProvider; mCredentialViewModelProvider = credentialViewModelProvider; mOrientationListener = new BiometricDisplayListener( @@ -1299,7 +1305,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, .build(bgExecutor, sensorIds, mFpProps, mFaceProps, wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils, mInteractionJankMonitor, mBiometricPromptInteractor, - mCredentialViewModelProvider); + mAuthBiometricFingerprintViewModelProvider, mCredentialViewModelProvider); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt index d6ad4da04dbe..f5f46405660f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt @@ -18,7 +18,6 @@ package com.android.systemui.biometrics import android.annotation.DrawableRes import android.content.Context -import android.content.res.Configuration import android.graphics.drawable.Animatable2 import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.Drawable @@ -94,8 +93,6 @@ abstract class AuthIconController( /** Called during [onAnimationEnd] if the controller is not [deactivated]. */ open fun handleAnimationEnd(drawable: Drawable) {} - open fun onConfigurationChanged(newConfig: Configuration) {} - // TODO(b/251476085): Migrate this to an extension at the appropriate level? /** Load the given [rawResources] immediately so they are cached for use in the [context]. */ protected fun cacheLottieAssetsInContext(context: Context, vararg rawResources: Int) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt index 4319f01bed14..962140fa2cff 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt @@ -55,6 +55,7 @@ import com.airbnb.lottie.model.KeyPath import com.android.internal.annotations.VisibleForTesting import com.android.systemui.Dumpable import com.android.systemui.R +import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main @@ -84,6 +85,7 @@ constructor( private val activityTaskManager: ActivityTaskManager, overviewProxyService: OverviewProxyService, displayManager: DisplayManager, + private val displayStateInteractor: DisplayStateInteractor, @Main private val mainExecutor: DelayableExecutor, @Main private val handler: Handler, private val alternateBouncerInteractor: AlternateBouncerInteractor, @@ -203,14 +205,16 @@ constructor( request: SideFpsUiRequestSource, @BiometricOverlayConstants.ShowReason reason: Int = BiometricOverlayConstants.REASON_UNKNOWN ) { - requests.add(request) - mainExecutor.execute { - if (overlayView == null) { - traceSection("SideFpsController#show(request=${request.name}, reason=$reason") { - createOverlayForDisplay(reason) + if (!displayStateInteractor.isInRearDisplayMode.value) { + requests.add(request) + mainExecutor.execute { + if (overlayView == null) { + traceSection("SideFpsController#show(request=${request.name}, reason=$reason") { + createOverlayForDisplay(reason) + } + } else { + Log.v(TAG, "overlay already shown") } - } else { - Log.v(TAG, "overlay already shown") } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt index f0b9f670f1e0..c83166385ac6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt @@ -21,8 +21,12 @@ import com.android.systemui.biometrics.data.repository.FingerprintPropertyReposi import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepositoryImpl import com.android.systemui.biometrics.data.repository.PromptRepository import com.android.systemui.biometrics.data.repository.PromptRepositoryImpl +import com.android.systemui.biometrics.data.repository.RearDisplayStateRepository +import com.android.systemui.biometrics.data.repository.RearDisplayStateRepositoryImpl import com.android.systemui.biometrics.domain.interactor.CredentialInteractor import com.android.systemui.biometrics.domain.interactor.CredentialInteractorImpl +import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor +import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl import com.android.systemui.biometrics.domain.interactor.LogContextInteractor import com.android.systemui.biometrics.domain.interactor.LogContextInteractorImpl import com.android.systemui.dagger.SysUISingleton @@ -45,6 +49,9 @@ interface BiometricsModule { @SysUISingleton fun fingerprintRepository(impl: FingerprintPropertyRepositoryImpl): FingerprintPropertyRepository + @Binds + @SysUISingleton + fun rearDisplayStateRepository(impl: RearDisplayStateRepositoryImpl): RearDisplayStateRepository @Binds @SysUISingleton @@ -52,6 +59,10 @@ interface BiometricsModule { @Binds @SysUISingleton + fun providesDisplayStateInteractor(impl: DisplayStateInteractorImpl): DisplayStateInteractor + + @Binds + @SysUISingleton fun bindsLogContextInteractor(impl: LogContextInteractorImpl): LogContextInteractor companion object { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepository.kt new file mode 100644 index 000000000000..d17d961b5279 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepository.kt @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.data.repository + +import android.content.Context +import android.hardware.devicestate.DeviceStateManager +import com.android.internal.util.ArrayUtils +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Main +import java.util.concurrent.Executor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn + +/** Provide current rear display state. */ +interface RearDisplayStateRepository { + /** Provides the current rear display state. */ + val isInRearDisplayMode: StateFlow<Boolean> +} + +@SysUISingleton +class RearDisplayStateRepositoryImpl +@Inject +constructor( + @Application applicationScope: CoroutineScope, + @Application context: Context, + deviceStateManager: DeviceStateManager, + @Main mainExecutor: Executor +) : RearDisplayStateRepository { + override val isInRearDisplayMode: StateFlow<Boolean> = + conflatedCallbackFlow { + val sendRearDisplayStateUpdate = { state: Boolean -> + trySendWithFailureLogging( + state, + TAG, + "Error sending rear display state update to $state" + ) + } + + val callback = + DeviceStateManager.DeviceStateCallback { state -> + val isInRearDisplayMode = + ArrayUtils.contains( + context.resources.getIntArray( + com.android.internal.R.array.config_rearDisplayDeviceStates + ), + state + ) + sendRearDisplayStateUpdate(isInRearDisplayMode) + } + + sendRearDisplayStateUpdate(false) + deviceStateManager.registerCallback(mainExecutor, callback) + awaitClose { deviceStateManager.unregisterCallback(callback) } + } + .stateIn( + applicationScope, + started = SharingStarted.Eagerly, + initialValue = false, + ) + + companion object { + const val TAG = "RearDisplayStateRepositoryImpl" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt new file mode 100644 index 000000000000..c935aa290e21 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.domain.interactor + +import android.content.Context +import android.content.res.Configuration +import com.android.systemui.biometrics.data.repository.RearDisplayStateRepository +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.unfold.compat.ScreenSizeFoldProvider +import com.android.systemui.unfold.updates.FoldProvider +import java.util.concurrent.Executor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn + +/** Aggregates display state information. */ +interface DisplayStateInteractor { + + /** Whether the device is currently in rear display mode. */ + val isInRearDisplayMode: StateFlow<Boolean> + + /** Whether the device is currently folded. */ + val isFolded: Flow<Boolean> + + /** Called on configuration changes, used to keep the display state in sync */ + fun onConfigurationChanged(newConfig: Configuration) +} + +/** Encapsulates logic for interacting with the display state. */ +class DisplayStateInteractorImpl +@Inject +constructor( + @Application applicationScope: CoroutineScope, + @Application context: Context, + @Main mainExecutor: Executor, + rearDisplayStateRepository: RearDisplayStateRepository, +) : DisplayStateInteractor { + private var screenSizeFoldProvider: ScreenSizeFoldProvider = ScreenSizeFoldProvider(context) + + fun setScreenSizeFoldProvider(foldProvider: ScreenSizeFoldProvider) { + screenSizeFoldProvider = foldProvider + } + + override val isFolded: Flow<Boolean> = + conflatedCallbackFlow { + val sendFoldStateUpdate = { state: Boolean -> + trySendWithFailureLogging( + state, + TAG, + "Error sending fold state update to $state" + ) + } + + val callback = + object : FoldProvider.FoldCallback { + override fun onFoldUpdated(isFolded: Boolean) { + sendFoldStateUpdate(isFolded) + } + } + sendFoldStateUpdate(false) + screenSizeFoldProvider.registerCallback(callback, mainExecutor) + awaitClose { screenSizeFoldProvider.unregisterCallback(callback) } + } + .stateIn( + applicationScope, + started = SharingStarted.Eagerly, + initialValue = false, + ) + + override val isInRearDisplayMode: StateFlow<Boolean> = + rearDisplayStateRepository.isInRearDisplayMode + + override fun onConfigurationChanged(newConfig: Configuration) { + screenSizeFoldProvider.onConfigurationChange(newConfig) + } + + companion object { + private const val TAG = "DisplayStateInteractor" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintViewBinder.kt new file mode 100644 index 000000000000..e776ab44ee42 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintViewBinder.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.biometrics.ui.binder + +import android.view.Surface +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.biometrics.AuthBiometricFingerprintView +import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.launch + +object AuthBiometricFingerprintViewBinder { + + /** Binds a [AuthBiometricFingerprintView] to a [AuthBiometricFingerprintViewModel]. */ + @JvmStatic + fun bind(view: AuthBiometricFingerprintView, viewModel: AuthBiometricFingerprintViewModel) { + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.onConfigurationChanged(view.context.resources.configuration) + viewModel.setRotation(view.context.display?.orientation ?: Surface.ROTATION_0) + launch { + viewModel.iconAsset.collect { iconAsset -> + view.updateIconViewAnimation(iconAsset) + } + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/AuthBiometricFingerprintViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/AuthBiometricFingerprintViewModel.kt new file mode 100644 index 000000000000..617d80cee09d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/AuthBiometricFingerprintViewModel.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.biometrics.ui.viewmodel + +import android.annotation.RawRes +import android.content.res.Configuration +import android.view.Surface +import com.android.systemui.R +import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine + +/** Models UI of AuthBiometricFingerprintView to support rear display state changes. */ +class AuthBiometricFingerprintViewModel +@Inject +constructor(private val interactor: DisplayStateInteractor) { + /** Current device rotation. */ + private var rotation: Int = Surface.ROTATION_0 + + /** Current AuthBiometricFingerprintView asset. */ + val iconAsset: Flow<Int> = + combine(interactor.isFolded, interactor.isInRearDisplayMode) { + isFolded: Boolean, + isInRearDisplayMode: Boolean -> + getSideFpsAnimationAsset(isFolded, isInRearDisplayMode) + } + + @RawRes + private fun getSideFpsAnimationAsset( + isDeviceFolded: Boolean, + isInRearDisplayMode: Boolean, + ): Int = + when (rotation) { + Surface.ROTATION_90 -> + if (isInRearDisplayMode) { + R.raw.biometricprompt_rear_portrait_reverse_base + } else if (isDeviceFolded) { + R.raw.biometricprompt_folded_base_topleft + } else { + R.raw.biometricprompt_portrait_base_topleft + } + Surface.ROTATION_270 -> + if (isInRearDisplayMode) { + R.raw.biometricprompt_rear_portrait_base + } else if (isDeviceFolded) { + R.raw.biometricprompt_folded_base_bottomright + } else { + R.raw.biometricprompt_portrait_base_bottomright + } + else -> + if (isInRearDisplayMode) { + R.raw.biometricprompt_rear_landscape_base + } else if (isDeviceFolded) { + R.raw.biometricprompt_folded_base_default + } else { + R.raw.biometricprompt_landscape_base + } + } + + /** Called on configuration changes */ + fun onConfigurationChanged(newConfig: Configuration) { + interactor.onConfigurationChanged(newConfig) + } + + fun setRotation(newRotation: Int) { + rotation = newRotation + } +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 2b72b2582f1a..871e66c3f917 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -61,6 +61,9 @@ object Flags { // TODO(b/254512538): Tracking Bug val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply") + // TODO(b/279735475): Tracking Bug + @JvmField val NEW_LIGHT_BAR_LOGIC = unreleasedFlag(279735475, "new_light_bar_logic") + /** * This flag is server-controlled and should stay as [unreleasedFlag] since we never want to * enable it on release builds. @@ -203,11 +206,6 @@ object Flags { ) /** Whether to inflate the bouncer view on a background thread. */ - // TODO(b/272091103): Tracking Bug - @JvmField - val ASYNC_INFLATE_BOUNCER = releasedFlag(229, "async_inflate_bouncer") - - /** Whether to inflate the bouncer view on a background thread. */ // TODO(b/273341787): Tracking Bug @JvmField val PREVENT_BYPASS_KEYGUARD = releasedFlag(230, "prevent_bypass_keyguard") @@ -271,7 +269,7 @@ object Flags { ) @JvmField - val QS_PIPELINE_NEW_HOST = unreleasedFlag(504, "qs_pipeline_new_host", teamfood = false) + val QS_PIPELINE_NEW_HOST = unreleasedFlag(504, "qs_pipeline_new_host", teamfood = true) // TODO(b/278068252): Tracking Bug @JvmField @@ -388,7 +386,7 @@ object Flags { val MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE = releasedFlag(912, "media_ttt_dismiss_gesture") // TODO(b/266157412): Tracking Bug - val MEDIA_RETAIN_SESSIONS = releasedFlag(913, "media_retain_sessions") + val MEDIA_RETAIN_SESSIONS = unreleasedFlag(913, "media_retain_sessions") // TODO(b/266739309): Tracking Bug @JvmField @@ -398,10 +396,10 @@ object Flags { val MEDIA_RESUME_PROGRESS = releasedFlag(915, "media_resume_progress") // TODO(b/267166152) : Tracking Bug - val MEDIA_RETAIN_RECOMMENDATIONS = releasedFlag(916, "media_retain_recommendations") + val MEDIA_RETAIN_RECOMMENDATIONS = unreleasedFlag(916, "media_retain_recommendations") // TODO(b/270437894): Tracking Bug - val MEDIA_REMOTE_RESUME = releasedFlag(917, "media_remote_resume") + val MEDIA_REMOTE_RESUME = unreleasedFlag(917, "media_remote_resume") // 1000 - dock val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging") diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index d3b6fc237084..f64ed6078ddb 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -128,6 +128,7 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.phone.CentralSurfaces; +import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -240,6 +241,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private final ScreenshotHelper mScreenshotHelper; private final SysuiColorExtractor mSysuiColorExtractor; private final IStatusBarService mStatusBarService; + protected final LightBarController mLightBarController; protected final NotificationShadeWindowController mNotificationShadeWindowController; private final IWindowManager mIWindowManager; private final Executor mBackgroundExecutor; @@ -349,6 +351,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene MetricsLogger metricsLogger, SysuiColorExtractor colorExtractor, IStatusBarService statusBarService, + LightBarController lightBarController, NotificationShadeWindowController notificationShadeWindowController, IWindowManager iWindowManager, @Background Executor backgroundExecutor, @@ -381,6 +384,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mUiEventLogger = uiEventLogger; mSysuiColorExtractor = colorExtractor; mStatusBarService = statusBarService; + mLightBarController = lightBarController; mNotificationShadeWindowController = notificationShadeWindowController; mIWindowManager = iWindowManager; mBackgroundExecutor = backgroundExecutor; @@ -694,6 +698,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene ActionsDialogLite dialog = new ActionsDialogLite(mContext, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActionsLite, mAdapter, mOverflowAdapter, mSysuiColorExtractor, mStatusBarService, + mLightBarController, mNotificationShadeWindowController, this::onRefresh, mKeyguardShowing, mPowerAdapter, mUiEventLogger, mCentralSurfacesOptional, mKeyguardUpdateMonitor, mLockPatternUtils); @@ -2192,6 +2197,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene protected final SysuiColorExtractor mColorExtractor; private boolean mKeyguardShowing; protected float mScrimAlpha; + protected final LightBarController mLightBarController; protected final NotificationShadeWindowController mNotificationShadeWindowController; private ListPopupWindow mOverflowPopup; private Dialog mPowerOptionsDialog; @@ -2267,6 +2273,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene ActionsDialogLite(Context context, int themeRes, MyAdapter adapter, MyOverflowAdapter overflowAdapter, SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService, + LightBarController lightBarController, NotificationShadeWindowController notificationShadeWindowController, Runnable onRefreshCallback, boolean keyguardShowing, MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger, @@ -2282,6 +2289,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mPowerOptionsAdapter = powerAdapter; mColorExtractor = sysuiColorExtractor; mStatusBarService = statusBarService; + mLightBarController = lightBarController; mNotificationShadeWindowController = notificationShadeWindowController; mOnRefreshCallback = onRefreshCallback; mKeyguardShowing = keyguardShowing; @@ -2474,6 +2482,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @Override protected void start() { mGlobalActionsLayout.updateList(); + mLightBarController.setGlobalActionsVisible(true); if (mBackgroundDrawable instanceof ScrimDrawable) { mColorExtractor.addOnColorsChangedListener(this); @@ -2504,6 +2513,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @Override protected void stop() { + mLightBarController.setGlobalActionsVisible(false); mColorExtractor.removeOnColorsChangedListener(this); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java index b92499eb883e..b7ba2019777e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java @@ -56,11 +56,11 @@ public class WorkLockActivityController { tscl.registerTaskStackListener(mLockListener); } - private void startWorkChallengeInTask(ActivityManager.RunningTaskInfo info) { + private void startWorkChallengeInTask(ActivityManager.RunningTaskInfo info, int userId) { String packageName = info.baseActivity != null ? info.baseActivity.getPackageName() : ""; Intent intent = new Intent(KeyguardManager.ACTION_CONFIRM_DEVICE_CREDENTIAL_WITH_USER) .setComponent(new ComponentName(mContext, WorkLockActivity.class)) - .putExtra(Intent.EXTRA_USER_ID, info.userId) + .putExtra(Intent.EXTRA_USER_ID, userId) .putExtra(Intent.EXTRA_PACKAGE_NAME, packageName) .addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP); @@ -76,10 +76,11 @@ public class WorkLockActivityController { } else { // Starting the activity inside the task failed. We can't be sure why, so to be // safe just remove the whole task if it still exists. + Log.w(TAG, "Failed to start work lock activity, will remove task=" + info.taskId); try { mIatm.removeTask(info.taskId); } catch (RemoteException e) { - Log.w(TAG, "Failed to get description for task=" + info.taskId); + Log.e(TAG, "Failed to remove task=" + info.taskId); } } } @@ -112,8 +113,8 @@ public class WorkLockActivityController { private final TaskStackChangeListener mLockListener = new TaskStackChangeListener() { @Override - public void onTaskProfileLocked(ActivityManager.RunningTaskInfo info) { - startWorkChallengeInTask(info); + public void onTaskProfileLocked(ActivityManager.RunningTaskInfo info, int userId) { + startWorkChallengeInTask(info, userId); } }; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index a98a7d8f8639..951f0bd76a64 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -21,6 +21,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.graphics.Rect import android.hardware.display.DisplayManager import android.os.Bundle import android.os.IBinder @@ -33,6 +34,7 @@ import android.widget.FrameLayout import com.android.keyguard.ClockEventController import com.android.keyguard.KeyguardClockSwitch import com.android.systemui.R +import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main @@ -61,6 +63,7 @@ constructor( private val clockRegistry: ClockRegistry, private val broadcastDispatcher: BroadcastDispatcher, private val lockscreenSmartspaceController: LockscreenSmartspaceController, + private val udfpsOverlayInteractor: UdfpsOverlayInteractor, @Assisted bundle: Bundle, ) { @@ -112,7 +115,9 @@ constructor( setUpBottomArea(rootView) - setupSmartspace(rootView) + setUpSmartspace(rootView) + + setUpUdfps(rootView) setUpClock(rootView) @@ -169,49 +174,53 @@ constructor( /** * This sets up and shows a non-interactive smart space * - * The top padding is as follows: - * Status bar height + clock top margin + keyguard smart space top offset + * The top padding is as follows: Status bar height + clock top margin + keyguard smart space + * top offset * - * The start padding is as follows: - * Clock padding start + Below clock padding start + * The start padding is as follows: Clock padding start + Below clock padding start * - * The end padding is as follows: - * Below clock padding end + * The end padding is as follows: Below clock padding end */ - private fun setupSmartspace(parentView: ViewGroup) { - if (!lockscreenSmartspaceController.isEnabled() || - !lockscreenSmartspaceController.isDateWeatherDecoupled()) { + private fun setUpSmartspace(parentView: ViewGroup) { + if ( + !lockscreenSmartspaceController.isEnabled() || + !lockscreenSmartspaceController.isDateWeatherDecoupled() + ) { return } smartSpaceView = lockscreenSmartspaceController.buildAndConnectDateView(parentView) - val topPadding: Int = with(context.resources) { - getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard) + + val topPadding: Int = + with(context.resources) { + getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard) + getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) + getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) - } + } - val startPadding: Int = with(context.resources) { - getDimensionPixelSize(R.dimen.clock_padding_start) + + val startPadding: Int = + with(context.resources) { + getDimensionPixelSize(R.dimen.clock_padding_start) + getDimensionPixelSize(R.dimen.below_clock_padding_start) - } + } - val endPadding: Int = context.resources - .getDimensionPixelSize(R.dimen.below_clock_padding_end) + val endPadding: Int = + context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_end) smartSpaceView?.let { it.setPaddingRelative(startPadding, topPadding, endPadding, 0) it.isClickable = false parentView.addView( - it, - FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.WRAP_CONTENT, - ), + it, + FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.WRAP_CONTENT, + ), ) } + + smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f } private fun setUpBottomArea(parentView: ViewGroup) { @@ -234,6 +243,33 @@ constructor( ) } + private fun setUpUdfps(parentView: ViewGroup) { + val sensorBounds = udfpsOverlayInteractor.udfpsOverlayParams.value.sensorBounds + + // If sensorBounds are default rect, then there is no UDFPS + if (sensorBounds == Rect()) { + return + } + + // Place the UDFPS view in the proper sensor location + val fingerprintLayoutParams = + FrameLayout.LayoutParams(sensorBounds.width(), sensorBounds.height()) + fingerprintLayoutParams.setMarginsRelative( + sensorBounds.left, + sensorBounds.top, + sensorBounds.right, + sensorBounds.bottom + ) + val finger = + LayoutInflater.from(context) + .inflate( + R.layout.udfps_keyguard_view_internal, + parentView, + false, + ) as View + parentView.addView(finger, fingerprintLayoutParams) + } + private fun setUpClock(parentView: ViewGroup) { val clockChangeListener = object : ClockRegistry.ClockChangeListener { @@ -266,8 +302,6 @@ constructor( disposables.add(DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) }) onClockChanged(parentView) - - updateSmartspaceWithSetupClock() } private fun onClockChanged(parentView: ViewGroup) { @@ -281,33 +315,22 @@ constructor( ?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView)) clockView?.let { parentView.removeView(it) } - clockView = largeClock?.view?.apply { - if (shouldHighlightSelectedAffordance) { - alpha = DIM_ALPHA + clockView = + largeClock?.view?.apply { + if (shouldHighlightSelectedAffordance) { + alpha = DIM_ALPHA + } + parentView.addView(this) + visibility = View.VISIBLE } - parentView.addView(this) - visibility = View.VISIBLE - } } else { clockView?.visibility = View.GONE } - } - /** - * Updates smart space after clock is set up. Used to show or hide smartspace with the right - * opacity based on the clock after setup. - */ - private fun updateSmartspaceWithSetupClock() { + // Hide smart space if the clock has weather display; otherwise show it val hasCustomWeatherDataDisplay = - clockController - .clock - ?.largeClock - ?.config - ?.hasCustomWeatherDataDisplay == true - + clockController.clock?.largeClock?.config?.hasCustomWeatherDataDisplay == true hideSmartspace(hasCustomWeatherDataDisplay) - - smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt index 9997730fa938..fa4211487d1d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt @@ -43,7 +43,6 @@ import android.media.session.PlaybackState import android.net.Uri import android.os.Parcelable import android.os.Process -import android.os.RemoteException import android.os.UserHandle import android.provider.Settings import android.service.notification.StatusBarNotification @@ -53,7 +52,6 @@ import android.util.Log import android.util.Pair as APair import androidx.media.utils.MediaConstants import com.android.internal.logging.InstanceId -import com.android.internal.statusbar.IStatusBarService import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.Dumpable import com.android.systemui.R @@ -139,8 +137,6 @@ internal val EMPTY_SMARTSPACE_MEDIA_DATA = expiryTimeMs = 0, ) -const val MEDIA_TITLE_ERROR_MESSAGE = "Invalid media data: title is null or blank." - fun isMediaNotification(sbn: StatusBarNotification): Boolean { return sbn.notification.isMediaNotification() } @@ -185,7 +181,6 @@ class MediaDataManager( private val logger: MediaUiEventLogger, private val smartspaceManager: SmartspaceManager, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, - private val statusBarService: IStatusBarService, ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener { companion object { @@ -257,7 +252,6 @@ class MediaDataManager( mediaFlags: MediaFlags, logger: MediaUiEventLogger, smartspaceManager: SmartspaceManager, - statusBarService: IStatusBarService, keyguardUpdateMonitor: KeyguardUpdateMonitor, ) : this( context, @@ -283,7 +277,6 @@ class MediaDataManager( logger, smartspaceManager, keyguardUpdateMonitor, - statusBarService, ) private val appChangeReceiver = @@ -385,21 +378,21 @@ class MediaDataManager( fun onNotificationAdded(key: String, sbn: StatusBarNotification) { if (useQsMediaPlayer && isMediaNotification(sbn)) { - var isNewlyActiveEntry = false + var logEvent = false Assert.isMainThread() val oldKey = findExistingEntry(key, sbn.packageName) if (oldKey == null) { val instanceId = logger.getNewInstanceId() val temp = LOADING.copy(packageName = sbn.packageName, instanceId = instanceId) mediaEntries.put(key, temp) - isNewlyActiveEntry = true + logEvent = true } else if (oldKey != key) { // Resume -> active conversion; move to new key val oldData = mediaEntries.remove(oldKey)!! - isNewlyActiveEntry = true + logEvent = true mediaEntries.put(key, oldData) } - loadMediaData(key, sbn, oldKey, isNewlyActiveEntry) + loadMediaData(key, sbn, oldKey, logEvent) } else { onNotificationRemoved(key) } @@ -482,9 +475,9 @@ class MediaDataManager( key: String, sbn: StatusBarNotification, oldKey: String?, - isNewlyActiveEntry: Boolean = false, + logEvent: Boolean = false ) { - backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, isNewlyActiveEntry) } + backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, logEvent) } } /** Add a listener for changes in this class */ @@ -608,11 +601,9 @@ class MediaDataManager( } } - private fun removeEntry(key: String, logEvent: Boolean = true) { + private fun removeEntry(key: String) { mediaEntries.remove(key)?.let { - if (logEvent) { - logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId) - } + logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId) } notifyMediaDataRemoved(key) } @@ -760,7 +751,7 @@ class MediaDataManager( key: String, sbn: StatusBarNotification, oldKey: String?, - isNewlyActiveEntry: Boolean = false, + logEvent: Boolean = false ) { val token = sbn.notification.extras.getParcelable( @@ -774,34 +765,6 @@ class MediaDataManager( val metadata = mediaController.metadata val notif: Notification = sbn.notification - // Song name - var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE) - if (song == null) { - song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE) - } - if (song == null) { - song = HybridGroupManager.resolveTitle(notif) - } - // Media data must have a title. - if (song.isNullOrBlank()) { - try { - statusBarService.onNotificationError( - sbn.packageName, - sbn.tag, - sbn.id, - sbn.uid, - sbn.initialPid, - MEDIA_TITLE_ERROR_MESSAGE, - sbn.user.identifier - ) - } catch (e: RemoteException) { - Log.e(TAG, "cancelNotification failed: $e") - } - // Only add log for media removed if active media is updated with invalid title. - foregroundExecutor.execute { removeEntry(key, !isNewlyActiveEntry) } - return - } - val appInfo = notif.extras.getParcelable( Notification.EXTRA_BUILDER_APPLICATION_INFO, @@ -830,6 +793,15 @@ class MediaDataManager( // App Icon val smallIcon = sbn.notification.smallIcon + // Song name + var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE) + if (song == null) { + song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE) + } + if (song == null) { + song = HybridGroupManager.resolveTitle(notif) + } + // Explicit Indicator var isExplicit = false if (mediaFlags.isExplicitIndicatorEnabled()) { @@ -901,7 +873,7 @@ class MediaDataManager( val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId() val appUid = appInfo?.uid ?: Process.INVALID_UID - if (isNewlyActiveEntry) { + if (logEvent) { logSingleVsMultipleMediaAdded(appUid, sbn.packageName, instanceId) logger.logActiveMediaAdded(appUid, sbn.packageName, instanceId, playbackLocation) } else if (playbackLocation != currentEntry?.playbackLocation) { diff --git a/packages/SystemUI/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivity.kt new file mode 100644 index 000000000000..c209a000deb4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivity.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.notetask + +import android.os.Bundle +import androidx.activity.ComponentActivity +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig +import javax.inject.Inject + +/** + * An internal proxy activity that starts the notes role setting. + * + * This activity is introduced mainly for the error handling of the notes app lock screen shortcut + * picker, which only supports package + action but not extras. See + * [KeyguardQuickAffordanceConfig.PickerScreenState.Disabled.actionComponentName]. + */ +class LaunchNotesRoleSettingsTrampolineActivity +@Inject +constructor( + private val controller: NoteTaskController, +) : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val entryPoint = + if (intent?.action == ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE) { + NoteTaskEntryPoint.QUICK_AFFORDANCE + } else { + null + } + controller.startNotesRoleSetting(this, entryPoint) + finish() + } + + companion object { + const val ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE = + "com.android.systemui.action.MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index d4052f54b3da..7e9b346c9577 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -31,15 +31,14 @@ import android.content.Intent import android.content.pm.PackageManager import android.content.pm.ShortcutManager import android.graphics.drawable.Icon -import android.os.Build import android.os.UserHandle import android.os.UserManager -import android.util.Log import android.widget.Toast import androidx.annotation.VisibleForTesting import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled +import com.android.systemui.log.DebugLogger.debugLog import com.android.systemui.notetask.NoteTaskRoleManagerExt.createNoteShortcutInfoAsUser import com.android.systemui.notetask.NoteTaskRoleManagerExt.getDefaultRoleHolderAsUser import com.android.systemui.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity @@ -92,10 +91,10 @@ constructor( if (info.launchMode != NoteTaskLaunchMode.AppBubble) return if (isExpanding) { - logDebug { "onBubbleExpandChanged - expanding: $info" } + debugLog { "onBubbleExpandChanged - expanding: $info" } eventLogger.logNoteTaskOpened(info) } else { - logDebug { "onBubbleExpandChanged - collapsing: $info" } + debugLog { "onBubbleExpandChanged - collapsing: $info" } eventLogger.logNoteTaskClosed(info) } } @@ -112,6 +111,43 @@ constructor( ) } + /** Starts the notes role setting. */ + fun startNotesRoleSetting(activityContext: Context, entryPoint: NoteTaskEntryPoint?) { + val user = + if (entryPoint == null) { + userTracker.userHandle + } else { + getUserForHandlingNotesTaking(entryPoint) + } + activityContext.startActivityAsUser( + Intent(Intent.ACTION_MANAGE_DEFAULT_APP).apply { + putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NOTES) + }, + user + ) + } + + /** + * Returns the [UserHandle] of an android user that should handle the notes taking [entryPoint]. + * + * On company owned personally enabled (COPE) devices, if the given [entryPoint] is in the + * [FORCE_WORK_NOTE_APPS_ENTRY_POINTS_ON_COPE_DEVICES] list, the default notes app in the work + * profile user will always be launched. + * + * On non managed devices or devices with other management modes, the current [UserHandle] is + * returned. + */ + fun getUserForHandlingNotesTaking(entryPoint: NoteTaskEntryPoint): UserHandle = + if ( + entryPoint in FORCE_WORK_NOTE_APPS_ENTRY_POINTS_ON_COPE_DEVICES && + devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile + ) { + userTracker.userProfiles.firstOrNull { userManager.isManagedProfile(it.id) }?.userHandle + ?: userTracker.userHandle + } else { + userTracker.userHandle + } + /** * Shows a note task. How the task is shown will depend on when the method is invoked. * @@ -122,30 +158,13 @@ constructor( * bubble is already opened. * * That will let users open other apps in full screen, and take contextual notes. - * - * On company owned personally enabled (COPE) devices, if the given [entryPoint] is in the - * [FORCE_WORK_NOTE_APPS_ENTRY_POINTS_ON_COPE_DEVICES] list, the default notes app in the work - * profile user will always be launched. */ fun showNoteTask( entryPoint: NoteTaskEntryPoint, ) { if (!isEnabled) return - val user: UserHandle = - if ( - entryPoint in FORCE_WORK_NOTE_APPS_ENTRY_POINTS_ON_COPE_DEVICES && - devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile - ) { - userTracker.userProfiles - .firstOrNull { userManager.isManagedProfile(it.id) } - ?.userHandle - ?: userTracker.userHandle - } else { - userTracker.userHandle - } - - showNoteTaskAsUser(entryPoint, user) + showNoteTaskAsUser(entryPoint, getUserForHandlingNotesTaking(entryPoint)) } /** A variant of [showNoteTask] which launches note task in the given [user]. */ @@ -168,14 +187,14 @@ constructor( isKeyguardLocked && devicePolicyManager.areKeyguardShortcutsDisabled(userId = user.identifier) ) { - logDebug { "Enterprise policy disallows launching note app when the screen is locked." } + debugLog { "Enterprise policy disallows launching note app when the screen is locked." } return } val info = resolver.resolveInfo(entryPoint, isKeyguardLocked, user) if (info == null) { - logDebug { "Default notes app isn't set" } + debugLog { "Default notes app isn't set" } showNoDefaultNotesAppToast() return } @@ -184,7 +203,7 @@ constructor( try { // TODO(b/266686199): We should handle when app not available. For now, we log. - logDebug { "onShowNoteTask - start: $info on user#${user.identifier}" } + debugLog { "onShowNoteTask - start: $info on user#${user.identifier}" } when (info.launchMode) { is NoteTaskLaunchMode.AppBubble -> { val intent = createNoteTaskIntent(info) @@ -192,7 +211,7 @@ constructor( Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget) bubbles.showOrHideAppBubble(intent, user, icon) // App bubble logging happens on `onBubbleExpandChanged`. - logDebug { "onShowNoteTask - opened as app bubble: $info" } + debugLog { "onShowNoteTask - opened as app bubble: $info" } } is NoteTaskLaunchMode.Activity -> { if (activityManager.isInForeground(info.packageName)) { @@ -200,20 +219,20 @@ constructor( val intent = createHomeIntent() context.startActivityAsUser(intent, user) eventLogger.logNoteTaskClosed(info) - logDebug { "onShowNoteTask - closed as activity: $info" } + debugLog { "onShowNoteTask - closed as activity: $info" } } else { val intent = createNoteTaskIntent(info) context.startActivityAsUser(intent, user) eventLogger.logNoteTaskOpened(info) - logDebug { "onShowNoteTask - opened as activity: $info" } + debugLog { "onShowNoteTask - opened as activity: $info" } } } } - logDebug { "onShowNoteTask - success: $info" } + debugLog { "onShowNoteTask - success: $info" } } catch (e: ActivityNotFoundException) { - logDebug { "onShowNoteTask - failed: $info" } + debugLog { "onShowNoteTask - failed: $info" } } - logDebug { "onShowNoteTask - completed: $info" } + debugLog { "onShowNoteTask - completed: $info" } } @VisibleForTesting @@ -253,7 +272,7 @@ constructor( PackageManager.DONT_KILL_APP, ) - logDebug { "setNoteTaskShortcutEnabled - completed: $isEnabled" } + debugLog { "setNoteTaskShortcutEnabled - completed: $isEnabled" } } /** @@ -352,11 +371,6 @@ private fun createNoteTaskIntent(info: NoteTaskInfo): Intent = } } -/** [Log.println] a [Log.DEBUG] message, only when [Build.IS_DEBUGGABLE]. */ -private inline fun Any.logDebug(message: () -> String) { - if (Build.IS_DEBUGGABLE) Log.d(this::class.java.simpleName.orEmpty(), message()) -} - /** Creates an [Intent] which forces the current app to background by calling home. */ private fun createHomeIntent(): Intent = Intent(Intent.ACTION_MAIN).apply { diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt index 2c62ffd4841f..4d5173a1ec0a 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt @@ -45,6 +45,10 @@ interface NoteTaskModule { @[Binds IntoMap ClassKey(LaunchNoteTaskManagedProfileProxyActivity::class)] fun LaunchNoteTaskManagedProfileProxyActivity.bindNoteTaskLauncherProxyActivity(): Activity + @[Binds IntoMap ClassKey(LaunchNotesRoleSettingsTrampolineActivity::class)] + fun LaunchNotesRoleSettingsTrampolineActivity.bindLaunchNotesRoleSettingsTrampolineActivity(): + Activity + companion object { @[Provides NoteTaskEnabledKey] diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt index 2da5b7621723..444407c2341a 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt @@ -16,9 +16,12 @@ package com.android.systemui.notetask.quickaffordance +import android.app.role.OnRoleHoldersChangedListener +import android.app.role.RoleManager import android.content.Context import android.hardware.input.InputSettings import android.os.Build +import android.os.UserHandle import android.os.UserManager import android.util.Log import com.android.keyguard.KeyguardUpdateMonitor @@ -27,17 +30,22 @@ import com.android.systemui.R 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.dagger.qualifiers.Background import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.PickerScreenState import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository +import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.Companion.ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE import com.android.systemui.notetask.NoteTaskController import com.android.systemui.notetask.NoteTaskEnabledKey -import com.android.systemui.notetask.NoteTaskEntryPoint +import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE +import com.android.systemui.notetask.NoteTaskInfoResolver +import com.android.systemui.shared.customization.data.content.CustomizationProviderContract.LockScreenQuickAffordances.AffordanceTable.COMPONENT_NAME_SEPARATOR import com.android.systemui.stylus.StylusManager import dagger.Lazy +import java.util.concurrent.Executor import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.trySendBlocking @@ -49,13 +57,16 @@ import kotlinx.coroutines.flow.onEach class NoteTaskQuickAffordanceConfig @Inject constructor( - context: Context, + private val context: Context, private val controller: NoteTaskController, + private val noteTaskInfoResolver: NoteTaskInfoResolver, private val stylusManager: StylusManager, + private val roleManager: RoleManager, private val keyguardMonitor: KeyguardUpdateMonitor, private val userManager: UserManager, private val lazyRepository: Lazy<KeyguardQuickAffordanceRepository>, @NoteTaskEnabledKey private val isEnabled: Boolean, + @Background private val backgroundExecutor: Executor, ) : KeyguardQuickAffordanceConfig { override val key = BuiltInKeyguardQuickAffordanceKeys.CREATE_NOTE @@ -73,15 +84,24 @@ constructor( val configSelectedFlow = repository.createConfigSelectedFlow(key) val stylusEverUsedFlow = stylusManager.createStylusEverUsedFlow(context) val userUnlockedFlow = userManager.createUserUnlockedFlow(keyguardMonitor) - combine(userUnlockedFlow, stylusEverUsedFlow, configSelectedFlow) { + val defaultNotesAppFlow = + roleManager.createNotesRoleFlow(backgroundExecutor, controller, noteTaskInfoResolver) + combine(userUnlockedFlow, stylusEverUsedFlow, configSelectedFlow, defaultNotesAppFlow) { isUserUnlocked, isStylusEverUsed, - isConfigSelected -> + isConfigSelected, + isDefaultNotesAppSet -> logDebug { "lockScreenState:isUserUnlocked=$isUserUnlocked" } logDebug { "lockScreenState:isStylusEverUsed=$isStylusEverUsed" } logDebug { "lockScreenState:isConfigSelected=$isConfigSelected" } + logDebug { "lockScreenState:isDefaultNotesAppSet=$isDefaultNotesAppSet" } - if (isEnabled && isUserUnlocked && (isConfigSelected || isStylusEverUsed)) { + if ( + isEnabled && + isUserUnlocked && + isDefaultNotesAppSet && + (isConfigSelected || isStylusEverUsed) + ) { val contentDescription = ContentDescription.Resource(pickerNameResourceId) val icon = Icon.Resource(pickerIconResourceId, contentDescription) LockScreenState.Visible(icon) @@ -92,15 +112,34 @@ constructor( .onEach { state -> logDebug { "lockScreenState=$state" } } } - override suspend fun getPickerScreenState() = - if (isEnabled) { - PickerScreenState.Default() - } else { - PickerScreenState.UnavailableOnDevice + override suspend fun getPickerScreenState(): PickerScreenState { + val isDefaultNotesAppSet = + noteTaskInfoResolver.resolveInfo( + QUICK_AFFORDANCE, + user = controller.getUserForHandlingNotesTaking(QUICK_AFFORDANCE) + ) != null + return when { + isEnabled && isDefaultNotesAppSet -> PickerScreenState.Default() + isEnabled -> { + PickerScreenState.Disabled( + listOf( + context.getString( + R.string.keyguard_affordance_enablement_dialog_notes_app_instruction + ) + ), + context.getString( + R.string.keyguard_affordance_enablement_dialog_notes_app_action + ), + "${context.packageName}$COMPONENT_NAME_SEPARATOR" + + "$ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE", + ) + } + else -> PickerScreenState.UnavailableOnDevice } + } override fun onTriggered(expandable: Expandable?): OnTriggeredResult { - controller.showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE) + controller.showNoteTask(entryPoint = QUICK_AFFORDANCE) return OnTriggeredResult.Handled } } @@ -129,6 +168,27 @@ private fun StylusManager.createStylusEverUsedFlow(context: Context) = callbackF awaitClose { unregisterCallback(callback) } } +private fun RoleManager.createNotesRoleFlow( + executor: Executor, + noteTaskController: NoteTaskController, + noteTaskInfoResolver: NoteTaskInfoResolver, +) = callbackFlow { + fun isDefaultNotesAppSetForUser() = + noteTaskInfoResolver.resolveInfo( + QUICK_AFFORDANCE, + user = noteTaskController.getUserForHandlingNotesTaking(QUICK_AFFORDANCE) + ) != null + + trySendBlocking(isDefaultNotesAppSetForUser()) + val callback = OnRoleHoldersChangedListener { roleName, _ -> + if (roleName == RoleManager.ROLE_NOTES) { + trySendBlocking(isDefaultNotesAppSetForUser()) + } + } + addOnRoleHoldersChangedListenerAsUser(executor, callback, UserHandle.ALL) + awaitClose { removeOnRoleHoldersChangedListenerAsUser(callback, UserHandle.ALL) } +} + private fun KeyguardQuickAffordanceRepository.createConfigSelectedFlow(key: String) = selections.map { selected -> selected.values.flatten().any { selectedConfig -> selectedConfig.key == key } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt index 0f38d32e0b64..8ca13b9776bb 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt @@ -18,12 +18,11 @@ package com.android.systemui.notetask.shortcut import android.content.Context import android.content.Intent -import android.os.Build import android.os.Bundle import android.os.UserHandle import android.os.UserManager -import android.util.Log import androidx.activity.ComponentActivity +import com.android.systemui.log.DebugLogger.debugLog import com.android.systemui.notetask.NoteTaskController import com.android.systemui.notetask.NoteTaskEntryPoint import com.android.systemui.settings.UserTracker @@ -68,7 +67,7 @@ constructor( val mainUser: UserHandle? = userManager.mainUser if (userManager.isManagedProfile) { if (mainUser == null) { - logDebug { "Can't find the main user. Skipping the notes app launch." } + debugLog { "Can't find the main user. Skipping the notes app launch." } } else { controller.startNoteTaskProxyActivityForUser(mainUser) } @@ -89,8 +88,3 @@ constructor( } } } - -/** [Log.println] a [Log.DEBUG] message, only when [Build.IS_DEBUGGABLE]. */ -private inline fun Any.logDebug(message: () -> String) { - if (Build.IS_DEBUGGABLE) Log.d(this::class.java.simpleName.orEmpty(), message()) -} diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt index 79167f276576..166ba9fba166 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt @@ -15,6 +15,7 @@ package com.android.systemui.privacy import android.content.Context +import android.content.res.Configuration import android.util.AttributeSet import android.view.Gravity.CENTER_VERTICAL import android.view.Gravity.END @@ -102,6 +103,11 @@ class OngoingPrivacyChip @JvmOverloads constructor( R.string.ongoing_privacy_chip_content_multiple_apps, typesText) } + override fun onConfigurationChanged(newConfig: Configuration?) { + super.onConfigurationChanged(newConfig) + updateResources() + } + private fun updateResources() { iconMargin = context.resources .getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_margin) @@ -110,8 +116,11 @@ class OngoingPrivacyChip @JvmOverloads constructor( iconColor = Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary) + val height = context.resources + .getDimensionPixelSize(R.dimen.ongoing_appops_chip_height) val padding = context.resources .getDimensionPixelSize(R.dimen.ongoing_appops_chip_side_padding) + iconsContainer.layoutParams.height = height iconsContainer.setPaddingRelative(padding, 0, padding, 0) iconsContainer.background = context.getDrawable(R.drawable.statusbar_privacy_chip_bg) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index b7f9f6bc0e4a..1afc885cb70d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -38,7 +38,9 @@ import java.io.PrintWriter; */ public class QSContainerImpl extends FrameLayout implements Dumpable { + private int mFancyClippingLeftInset; private int mFancyClippingTop; + private int mFancyClippingRightInset; private int mFancyClippingBottom; private final float[] mFancyClippingRadii = new float[] {0, 0, 0, 0, 0, 0, 0, 0}; private final Path mFancyClippingPath = new Path(); @@ -53,6 +55,7 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { private boolean mQsDisabled; private int mContentHorizontalPadding = -1; private boolean mClippingEnabled; + private boolean mIsFullWidth; public QSContainerImpl(Context context, AttributeSet attrs) { super(context, attrs); @@ -237,7 +240,8 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { /** * Clip QS bottom using a concave shape. */ - public void setFancyClipping(int top, int bottom, int radius, boolean enabled) { + public void setFancyClipping(int leftInset, int top, int rightInset, int bottom, int radius, + boolean enabled, boolean fullWidth) { boolean updatePath = false; if (mFancyClippingRadii[0] != radius) { mFancyClippingRadii[0] = radius; @@ -246,10 +250,18 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { mFancyClippingRadii[3] = radius; updatePath = true; } + if (mFancyClippingLeftInset != leftInset) { + mFancyClippingLeftInset = leftInset; + updatePath = true; + } if (mFancyClippingTop != top) { mFancyClippingTop = top; updatePath = true; } + if (mFancyClippingRightInset != rightInset) { + mFancyClippingRightInset = rightInset; + updatePath = true; + } if (mFancyClippingBottom != bottom) { mFancyClippingBottom = bottom; updatePath = true; @@ -258,6 +270,10 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { mClippingEnabled = enabled; updatePath = true; } + if (mIsFullWidth != fullWidth) { + mIsFullWidth = fullWidth; + updatePath = true; + } if (updatePath) { updateClippingPath(); @@ -281,15 +297,21 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { return; } - mFancyClippingPath.addRoundRect(0, mFancyClippingTop, getWidth(), + int clippingLeft = mIsFullWidth ? -mFancyClippingLeftInset : 0; + int clippingRight = mIsFullWidth ? getWidth() + mFancyClippingRightInset : getWidth(); + mFancyClippingPath.addRoundRect(clippingLeft, mFancyClippingTop, clippingRight, mFancyClippingBottom, mFancyClippingRadii, Path.Direction.CW); invalidate(); } @Override public void dump(PrintWriter pw, String[] args) { - pw.println(getClass().getSimpleName() + " updateClippingPath: top(" - + mFancyClippingTop + ") bottom(" + mFancyClippingBottom + ") mClippingEnabled(" - + mClippingEnabled + ")"); + pw.println(getClass().getSimpleName() + " updateClippingPath: " + + "leftInset(" + mFancyClippingLeftInset + ") " + + "top(" + mFancyClippingTop + ") " + + "rightInset(" + mFancyClippingRightInset + ") " + + "bottom(" + mFancyClippingBottom + ") " + + "mClippingEnabled(" + mClippingEnabled + ") " + + "mIsFullWidth(" + mIsFullWidth + ")"); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index d806afa91dd1..fd3f70148a69 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -18,9 +18,10 @@ import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS; import static com.android.systemui.media.dagger.MediaModule.QS_PANEL; import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL; -import static com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED; +import static com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.content.res.Configuration; @@ -395,9 +396,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } @Override - public void setFancyClipping(int top, int bottom, int cornerRadius, boolean visible) { + public void setFancyClipping(int leftInset, int top, int rightInset, int bottom, + int cornerRadius, boolean visible, boolean fullWidth) { if (getView() instanceof QSContainerImpl) { - ((QSContainerImpl) getView()).setFancyClipping(top, bottom, cornerRadius, visible); + ((QSContainerImpl) getView()).setFancyClipping(leftInset, top, rightInset, bottom, + cornerRadius, visible, fullWidth); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java index 0bce1f728e18..b70b94b00923 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java @@ -719,7 +719,8 @@ public class QSSecurityFooterUtils implements DialogInterface.OnClickListener { String name = vpnName != null ? vpnName : vpnNameWorkProfile; String namedVp = mDpm.getResources().getString( QS_DIALOG_MANAGEMENT_NAMED_VPN, - () -> mContext.getString(R.string.monitoring_description_named_vpn, name), + () -> mContext.getString( + R.string.monitoring_description_managed_device_named_vpn, name), name); message.append(namedVp); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 4a3199850e0f..b80668379e49 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -385,6 +385,11 @@ open class QSTileViewImpl @JvmOverloads constructor( super.onInitializeAccessibilityNodeInfo(info) // Clear selected state so it is not announce by talkback. info.isSelected = false + info.text = if (TextUtils.isEmpty(secondaryLabel.text)) { + "${label.text}" + } else { + "${label.text}, ${secondaryLabel.text}" + } if (lastDisabledByPolicy) { info.addAction( AccessibilityNodeInfo.AccessibilityAction( @@ -402,12 +407,6 @@ open class QSTileViewImpl @JvmOverloads constructor( accessibilityClass } if (Switch::class.java.name == accessibilityClass) { - val label = resources.getString( - if (tileState) R.string.switch_bar_on else R.string.switch_bar_off) - // Set the text here for tests in - // android.platform.test.scenario.sysui.quicksettings. Can be removed when - // UiObject2 has a new getStateDescription() API and tests are updated. - info.text = label info.isChecked = tileState info.isCheckable = true if (isLongClickable) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 51179153fc73..137a99ef39b8 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -4474,7 +4474,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mDisplayTopInset = combinedInsets.top; mDisplayRightInset = combinedInsets.right; mDisplayLeftInset = combinedInsets.left; - mQsController.setDisplayInsets(mDisplayRightInset, mDisplayLeftInset); + mQsController.setDisplayInsets(mDisplayLeftInset, mDisplayRightInset); mNavigationBarBottomHeight = insets.getStableInsetBottom(); updateMaxHeadsUpTranslation(); diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index 4012736b8beb..a1fa8fbf9075 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -85,11 +85,13 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.KeyguardStatusBarView; +import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; +import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.LargeScreenUtils; @@ -116,6 +118,7 @@ public class QuickSettingsController { private final PulseExpansionHandler mPulseExpansionHandler; private final ShadeExpansionStateManager mShadeExpansionStateManager; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + private final LightBarController mLightBarController; private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; private final NotificationShadeDepthController mDepthController; @@ -135,6 +138,7 @@ public class QuickSettingsController { private final LockscreenGestureLogger mLockscreenGestureLogger; private final ShadeLogger mShadeLog; private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor; + private final CastController mCastController; private final FeatureFlags mFeatureFlags; private final InteractionJankMonitor mInteractionJankMonitor; private final ShadeRepository mShadeRepository; @@ -302,6 +306,7 @@ public class QuickSettingsController { NotificationRemoteInputManager remoteInputManager, ShadeExpansionStateManager shadeExpansionStateManager, StatusBarKeyguardViewManager statusBarKeyguardViewManager, + LightBarController lightBarController, NotificationStackScrollLayoutController notificationStackScrollLayoutController, LockscreenShadeTransitionController lockscreenShadeTransitionController, NotificationShadeDepthController notificationShadeDepthController, @@ -324,7 +329,8 @@ public class QuickSettingsController { InteractionJankMonitor interactionJankMonitor, ShadeLogger shadeLog, KeyguardFaceAuthInteractor keyguardFaceAuthInteractor, - ShadeRepository shadeRepository + ShadeRepository shadeRepository, + CastController castController ) { mPanelViewControllerLazy = panelViewControllerLazy; mPanelView = panelView; @@ -343,6 +349,7 @@ public class QuickSettingsController { mRemoteInputManager = remoteInputManager; mShadeExpansionStateManager = shadeExpansionStateManager; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; + mLightBarController = lightBarController; mNotificationStackScrollLayoutController = notificationStackScrollLayoutController; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; mDepthController = notificationShadeDepthController; @@ -364,6 +371,7 @@ public class QuickSettingsController { mMetricsLogger = metricsLogger; mShadeLog = shadeLog; mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor; + mCastController = castController; mFeatureFlags = featureFlags; mInteractionJankMonitor = interactionJankMonitor; mShadeRepository = shadeRepository; @@ -879,7 +887,9 @@ public class QuickSettingsController { } void setOverScrollAmount(int overExpansion) { - mQs.setOverScrollAmount(overExpansion); + if (mQs != null) { + mQs.setOverScrollAmount(overExpansion); + } } private void setOverScrolling(boolean overscrolling) { @@ -1014,6 +1024,9 @@ public class QuickSettingsController { mShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction); mShadeHeaderController.setQsExpandedFraction(qsExpansionFraction); mShadeHeaderController.setQsVisible(mVisible); + + // Update the light bar + mLightBarController.setQsExpanded(mFullyExpanded); } float getLockscreenShadeDragProgress() { @@ -1188,7 +1201,9 @@ public class QuickSettingsController { mLastClipBounds.set(left, top, right, bottom); if (mIsFullWidth) { clipStatusView = qsVisible; - float screenCornerRadius = mRecordingController.isRecording() ? 0 : mScreenCornerRadius; + float screenCornerRadius = + mRecordingController.isRecording() || mCastController.hasConnectedCastDevice() + ? 0 : mScreenCornerRadius; radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius, Math.min(top / (float) mScrimCornerRadius, 1f)); mScrimController.setNotificationBottomRadius(radius); @@ -1217,10 +1232,13 @@ public class QuickSettingsController { mVisible = qsVisible; mQs.setQsVisible(qsVisible); mQs.setFancyClipping( + mDisplayLeftInset, clipTop, + mDisplayRightInset, clipBottom, radius, - qsVisible && !mSplitShadeEnabled); + qsVisible && !mSplitShadeEnabled, + mIsFullWidth); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt index 07b686948d7f..b6970aef6cad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt @@ -20,6 +20,7 @@ import android.app.ActivityManager import android.content.res.Resources import android.os.SystemProperties import android.os.Trace +import android.os.Trace.TRACE_TAG_APP import android.util.IndentingPrintWriter import android.util.MathUtils import android.view.CrossWindowBlurListeners @@ -43,8 +44,8 @@ open class BlurUtils @Inject constructor( ) : Dumpable { val minBlurRadius = resources.getDimensionPixelSize(R.dimen.min_window_blur_radius) val maxBlurRadius = resources.getDimensionPixelSize(R.dimen.max_window_blur_radius) - private val traceCookie = System.identityHashCode(this) private var lastAppliedBlur = 0 + private var earlyWakeupEnabled = false init { dumpManager.registerDumpable(javaClass.name, this) @@ -72,6 +73,26 @@ open class BlurUtils @Inject constructor( } /** + * This method should be called before [applyBlur] so that, if needed, we can set the + * early-wakeup flag in SurfaceFlinger. + */ + fun prepareBlur(viewRootImpl: ViewRootImpl?, radius: Int) { + if (viewRootImpl == null || !viewRootImpl.surfaceControl.isValid || + !supportsBlursOnWindows() || earlyWakeupEnabled + ) { + return + } + if (lastAppliedBlur == 0 && radius != 0) { + Trace.asyncTraceForTrackBegin(TRACE_TAG_APP, TRACK_NAME, EARLY_WAKEUP_SLICE_NAME, 0) + earlyWakeupEnabled = true + createTransaction().use { + it.setEarlyWakeupStart() + it.apply() + } + } + } + + /** * Applies background blurs to a {@link ViewRootImpl}. * * @param viewRootImpl The window root. @@ -85,14 +106,20 @@ open class BlurUtils @Inject constructor( createTransaction().use { if (supportsBlursOnWindows()) { it.setBackgroundBlurRadius(viewRootImpl.surfaceControl, radius) - if (lastAppliedBlur == 0 && radius != 0) { - Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, TRACK_NAME, - EARLY_WAKEUP_SLICE_NAME, traceCookie) + if (!earlyWakeupEnabled && lastAppliedBlur == 0 && radius != 0) { + Trace.asyncTraceForTrackBegin( + TRACE_TAG_APP, + TRACK_NAME, + EARLY_WAKEUP_SLICE_NAME, + 0 + ) it.setEarlyWakeupStart() + earlyWakeupEnabled = true } - if (lastAppliedBlur != 0 && radius == 0) { + if (earlyWakeupEnabled && lastAppliedBlur != 0 && radius == 0) { it.setEarlyWakeupEnd() - Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TRACK_NAME, traceCookie) + Trace.asyncTraceForTrackEnd(TRACE_TAG_APP, TRACK_NAME, 0) + earlyWakeupEnabled = false } lastAppliedBlur = radius } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index 8dc78426da7d..a3bd247668fe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -189,12 +189,7 @@ class NotificationShadeDepthController @Inject constructor( scheduleUpdate() } - /** - * Callback that updates the window blur value and is called only once per frame. - */ - @VisibleForTesting - val updateBlurCallback = Choreographer.FrameCallback { - updateScheduled = false + private fun computeBlurAndZoomOut(): Pair<Int, Float> { val animationRadius = MathUtils.constrain(shadeAnimation.radius, blurUtils.minBlurRadius.toFloat(), blurUtils.maxBlurRadius.toFloat()) val expansionRadius = blurUtils.blurRadiusOfRatio( @@ -232,6 +227,16 @@ class NotificationShadeDepthController @Inject constructor( // Brightness slider removes blur, but doesn't affect zooms blur = (blur * (1f - brightnessMirrorSpring.ratio)).toInt() + return Pair(blur, zoomOut) + } + + /** + * Callback that updates the window blur value and is called only once per frame. + */ + @VisibleForTesting + val updateBlurCallback = Choreographer.FrameCallback { + updateScheduled = false + val (blur, zoomOut) = computeBlurAndZoomOut() val opaque = scrimsVisible && !blursDisabledForAppLaunch Trace.traceCounter(Trace.TRACE_TAG_APP, "shade_blur_radius", blur) blurUtils.applyBlur(root.viewRootImpl, blur, opaque) @@ -441,6 +446,8 @@ class NotificationShadeDepthController @Inject constructor( return } updateScheduled = true + val (blur, _) = computeBlurAndZoomOut() + blurUtils.prepareBlur(root.viewRootImpl, blur) choreographer.postFrameCallback(updateBlurCallback) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index 77550038c94a..129c8594e48e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -26,6 +26,7 @@ import android.annotation.IntDef; import android.app.ActivityManager; import android.app.Notification; import android.content.Context; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.res.ColorStateList; import android.content.res.Configuration; @@ -75,9 +76,9 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi */ private static final float DARK_ALPHA_BOOST = 0.67f; /** - * Status icons are currently drawn with the intention of being 17dp tall, but we - * want to scale them (in a way that doesn't require an asset dump) down 2dp. So - * 17dp * (15 / 17) = 15dp, the new height. After the first call to {@link #reloadDimens} all + * Status icons are currently drawn with the intention of being 17sp tall, but we + * want to scale them (in a way that doesn't require an asset dump) down 2sp. So + * 17sp * (15 / 17) = 15sp, the new height. After the first call to {@link #reloadDimens} all * values will be in px. */ private float mSystemIconDesiredHeight = 15f; @@ -144,7 +145,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi private String mNumberText; private StatusBarNotification mNotification; private final boolean mBlocked; - private int mDensity; + private Configuration mConfiguration; private boolean mNightMode; private float mIconScale = 1.0f; private final Paint mDotPaint = new Paint(Paint.ANTI_ALIAS_FLAG); @@ -198,9 +199,8 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi mNumberPain.setAntiAlias(true); setNotification(sbn); setScaleType(ScaleType.CENTER); - mDensity = context.getResources().getDisplayMetrics().densityDpi; - Configuration configuration = context.getResources().getConfiguration(); - mNightMode = (configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK) + mConfiguration = new Configuration(context.getResources().getConfiguration()); + mNightMode = (mConfiguration.uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; initializeDecorColor(); reloadDimens(); @@ -214,7 +214,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi mAlwaysScaleIcon = true; reloadDimens(); maybeUpdateIconScaleDimens(); - mDensity = context.getResources().getDisplayMetrics().densityDpi; + mConfiguration = new Configuration(context.getResources().getConfiguration()); } /** Should always be preceded by {@link #reloadDimens()} */ @@ -231,12 +231,17 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi private void updateIconScaleForNotifications() { final float imageBounds = mIncreasedSize ? mStatusBarIconDrawingSizeIncreased : mStatusBarIconDrawingSize; - final int outerBounds = mStatusBarIconSize; - mIconScale = imageBounds / (float)outerBounds; + float iconHeight = getIconHeight(); + if (iconHeight != 0) { + mIconScale = imageBounds / iconHeight; + } else { + final int outerBounds = mStatusBarIconSize; + mIconScale = imageBounds / (float) outerBounds; + } updatePivot(); } - // Makes sure that all icons are scaled to the same height (15dp). If we cannot get a height + // Makes sure that all icons are scaled to the same height (15sp). If we cannot get a height // for the icon, it uses the default SCALE (15f / 17f) which is the old behavior private void updateIconScaleForSystemIcons() { float iconHeight = getIconHeight(); @@ -267,12 +272,10 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - int density = newConfig.densityDpi; - if (density != mDensity) { - mDensity = density; - reloadDimens(); - updateDrawable(); - maybeUpdateIconScaleDimens(); + final int configDiff = newConfig.diff(mConfiguration); + mConfiguration.setTo(newConfig); + if ((configDiff & (ActivityInfo.CONFIG_DENSITY | ActivityInfo.CONFIG_FONT_SCALE)) != 0) { + updateIconDimens(); } boolean nightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; @@ -282,6 +285,15 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi } } + /** + * Update the icon dimens and drawable with current resources + */ + public void updateIconDimens() { + reloadDimens(); + updateDrawable(); + maybeUpdateIconScaleDimens(); + } + private void reloadDimens() { boolean applyRadius = mDotRadius == mStaticDotRadius; Resources res = getResources(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java index 534edb97bc5d..a058bf88e2e4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java @@ -25,6 +25,7 @@ import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARE import android.annotation.ColorInt; import android.content.Context; import android.graphics.Rect; +import android.util.Log; import android.view.InsetsFlags; import android.view.ViewDebug; import android.view.WindowInsetsController.Appearance; @@ -35,13 +36,17 @@ import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.statusbar.policy.BatteryController; +import com.android.systemui.util.Compile; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Date; import javax.inject.Inject; @@ -51,10 +56,14 @@ import javax.inject.Inject; @SysUISingleton public class LightBarController implements BatteryController.BatteryStateChangeCallback, Dumpable { + private static final String TAG = "LightBarController"; + private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); + private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f; private final SysuiDarkIconDispatcher mStatusBarIconController; private final BatteryController mBatteryController; + private final boolean mUseNewLightBarLogic; private BiometricUnlockController mBiometricUnlockController; private LightBarTransitionsController mNavigationBarController; @@ -67,13 +76,17 @@ public class LightBarController implements BatteryController.BatteryStateChangeC private final int mLightIconColor; /** - * Whether the navigation bar should be light factoring in already how much alpha the scrim has + * Whether the navigation bar should be light factoring in already how much alpha the scrim has. + * "Light" refers to the background color of the navigation bar, so when this is true, + * it's referring to a state where the navigation bar icons are tinted dark. */ private boolean mNavigationLight; /** - * Whether the flags indicate that a light status bar is requested. This doesn't factor in the - * scrim alpha yet. + * Whether the flags indicate that a light navigation bar is requested. + * "Light" refers to the background color of the navigation bar, so when this is true, + * it's referring to a state where the navigation bar icons would be tinted dark. + * This doesn't factor in the scrim alpha yet. */ private boolean mHasLightNavigationBar; @@ -82,22 +95,34 @@ public class LightBarController implements BatteryController.BatteryStateChangeC * {@link #mNavigationLight} {@code false}. */ private boolean mForceDarkForScrim; + /** + * {@code true} if {@link #mHasLightNavigationBar} should be ignored and forcefully make + * {@link #mNavigationLight} {@code true}. + */ + private boolean mForceLightForScrim; private boolean mQsCustomizing; + private boolean mQsExpanded; + private boolean mGlobalActionsVisible; private boolean mDirectReplying; private boolean mNavbarColorManagedByIme; private boolean mIsCustomizingForBackNav; + private String mLastSetScrimStateLog; + private String mLastNavigationBarAppearanceChangedLog; + @Inject public LightBarController( Context ctx, DarkIconDispatcher darkIconDispatcher, BatteryController batteryController, NavigationModeController navModeController, + FeatureFlags featureFlags, DumpManager dumpManager, DisplayTracker displayTracker) { + mUseNewLightBarLogic = featureFlags.isEnabled(Flags.NEW_LIGHT_BAR_LOGIC); mDarkIconColor = ctx.getColor(R.color.dark_mode_icon_color_single_tone); mLightIconColor = ctx.getColor(R.color.light_mode_icon_color_single_tone); mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher; @@ -159,9 +184,42 @@ public class LightBarController implements BatteryController.BatteryStateChangeC final boolean last = mNavigationLight; mHasLightNavigationBar = isLight(appearance, navigationBarMode, APPEARANCE_LIGHT_NAVIGATION_BARS); - mNavigationLight = mHasLightNavigationBar - && (mDirectReplying && mNavbarColorManagedByIme || !mForceDarkForScrim) - && !mQsCustomizing; + if (mUseNewLightBarLogic) { + final boolean ignoreScrimForce = mDirectReplying && mNavbarColorManagedByIme; + final boolean darkForScrim = mForceDarkForScrim && !ignoreScrimForce; + final boolean lightForScrim = mForceLightForScrim && !ignoreScrimForce; + final boolean darkForQs = mQsCustomizing || mQsExpanded || mGlobalActionsVisible; + mNavigationLight = + ((mHasLightNavigationBar && !darkForScrim) || lightForScrim) && !darkForQs; + mLastNavigationBarAppearanceChangedLog = "onNavigationBarAppearanceChanged()" + + " appearance=" + appearance + + " nbModeChanged=" + nbModeChanged + + " navigationBarMode=" + navigationBarMode + + " navbarColorManagedByIme=" + navbarColorManagedByIme + + " mHasLightNavigationBar=" + mHasLightNavigationBar + + " ignoreScrimForce=" + ignoreScrimForce + + " darkForScrim=" + darkForScrim + + " lightForScrim=" + lightForScrim + + " darkForQs=" + darkForQs + + " mNavigationLight=" + mNavigationLight + + " last=" + last + + " timestamp=" + new Date(); + if (DEBUG) Log.d(TAG, mLastNavigationBarAppearanceChangedLog); + } else { + mNavigationLight = mHasLightNavigationBar + && (mDirectReplying && mNavbarColorManagedByIme || !mForceDarkForScrim) + && !mQsCustomizing; + mLastNavigationBarAppearanceChangedLog = "onNavigationBarAppearanceChanged()" + + " appearance=" + appearance + + " nbModeChanged=" + nbModeChanged + + " navigationBarMode=" + navigationBarMode + + " navbarColorManagedByIme=" + navbarColorManagedByIme + + " mHasLightNavigationBar=" + mHasLightNavigationBar + + " mNavigationLight=" + mNavigationLight + + " last=" + last + + " timestamp=" + new Date(); + if (DEBUG) Log.d(TAG, mLastNavigationBarAppearanceChangedLog); + } if (mNavigationLight != last) { updateNavigation(); } @@ -188,6 +246,20 @@ public class LightBarController implements BatteryController.BatteryStateChangeC reevaluate(); } + /** Set if Quick Settings is fully expanded, which affects notification scrim visibility */ + public void setQsExpanded(boolean expanded) { + if (mQsExpanded == expanded) return; + mQsExpanded = expanded; + reevaluate(); + } + + /** Set if Global Actions dialog is visible, which requires dark mode (light buttons) */ + public void setGlobalActionsVisible(boolean visible) { + if (mGlobalActionsVisible == visible) return; + mGlobalActionsVisible = visible; + reevaluate(); + } + /** * Controls the light status bar temporarily for back navigation. * @param appearance the custmoized appearance. @@ -225,16 +297,52 @@ public class LightBarController implements BatteryController.BatteryStateChangeC public void setScrimState(ScrimState scrimState, float scrimBehindAlpha, GradientColors scrimInFrontColor) { - boolean forceDarkForScrimLast = mForceDarkForScrim; - // For BOUNCER/BOUNCER_SCRIMMED cases, we assume that alpha is always below threshold. - // This enables IMEs to control the navigation bar color. - // For other cases, scrim should be able to veto the light navigation bar. - mForceDarkForScrim = scrimState != ScrimState.BOUNCER - && scrimState != ScrimState.BOUNCER_SCRIMMED - && scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD - && !scrimInFrontColor.supportsDarkText(); - if (mHasLightNavigationBar && (mForceDarkForScrim != forceDarkForScrimLast)) { - reevaluate(); + if (mUseNewLightBarLogic) { + boolean forceDarkForScrimLast = mForceDarkForScrim; + boolean forceLightForScrimLast = mForceLightForScrim; + final boolean forceForScrim = + scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD; + final boolean scrimColorIsLight = scrimInFrontColor.supportsDarkText(); + + mForceDarkForScrim = forceForScrim && !scrimColorIsLight; + mForceLightForScrim = forceForScrim && scrimColorIsLight; + if (mHasLightNavigationBar) { + if (mForceDarkForScrim != forceDarkForScrimLast) reevaluate(); + } else { + if (mForceLightForScrim != forceLightForScrimLast) reevaluate(); + } + mLastSetScrimStateLog = "setScrimState()" + + " scrimState=" + scrimState + + " scrimBehindAlpha=" + scrimBehindAlpha + + " scrimInFrontColor=" + scrimInFrontColor + + " forceForScrim=" + forceForScrim + + " scrimColorIsLight=" + scrimColorIsLight + + " mHasLightNavigationBar=" + mHasLightNavigationBar + + " mForceDarkForScrim=" + mForceDarkForScrim + + " mForceLightForScrim=" + mForceLightForScrim + + " timestamp=" + new Date(); + if (DEBUG) Log.d(TAG, mLastSetScrimStateLog); + } else { + boolean forceDarkForScrimLast = mForceDarkForScrim; + // For BOUNCER/BOUNCER_SCRIMMED cases, we assume that alpha is always below threshold. + // This enables IMEs to control the navigation bar color. + // For other cases, scrim should be able to veto the light navigation bar. + // NOTE: this was also wrong for S and has been removed in the new logic. + mForceDarkForScrim = scrimState != ScrimState.BOUNCER + && scrimState != ScrimState.BOUNCER_SCRIMMED + && scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD + && !scrimInFrontColor.supportsDarkText(); + if (mHasLightNavigationBar && (mForceDarkForScrim != forceDarkForScrimLast)) { + reevaluate(); + } + mLastSetScrimStateLog = "setScrimState()" + + " scrimState=" + scrimState + + " scrimBehindAlpha=" + scrimBehindAlpha + + " scrimInFrontColor=" + scrimInFrontColor + + " mHasLightNavigationBar=" + mHasLightNavigationBar + + " mForceDarkForScrim=" + mForceDarkForScrim + + " timestamp=" + new Date(); + if (DEBUG) Log.d(TAG, mLastSetScrimStateLog); } } @@ -309,16 +417,24 @@ public class LightBarController implements BatteryController.BatteryStateChangeC pw.print(mAppearanceRegions[i].toString()); pw.print(" isLight="); pw.println(isLight); } - pw.print(" mNavigationLight="); pw.print(mNavigationLight); + pw.print(" mNavigationLight="); pw.println(mNavigationLight); pw.print(" mHasLightNavigationBar="); pw.println(mHasLightNavigationBar); - + pw.println(); pw.print(" mStatusBarMode="); pw.print(mStatusBarMode); pw.print(" mNavigationBarMode="); pw.println(mNavigationBarMode); - - pw.print(" mForceDarkForScrim="); pw.print(mForceDarkForScrim); - pw.print(" mQsCustomizing="); pw.print(mQsCustomizing); + pw.println(); + pw.print(" mForceDarkForScrim="); pw.println(mForceDarkForScrim); + pw.print(" mForceLightForScrim="); pw.println(mForceLightForScrim); + pw.println(); + pw.print(" mQsCustomizing="); pw.println(mQsCustomizing); + pw.print(" mQsExpanded="); pw.println(mQsExpanded); + pw.print(" mGlobalActionsVisible="); pw.println(mGlobalActionsVisible); pw.print(" mDirectReplying="); pw.println(mDirectReplying); pw.print(" mNavbarColorManagedByIme="); pw.println(mNavbarColorManagedByIme); + pw.println(); + pw.println(" Recent Calculation Logs:"); + pw.print(" "); pw.println(mLastSetScrimStateLog); + pw.print(" "); pw.println(mLastNavigationBarAppearanceChangedLog); pw.println(); @@ -344,6 +460,7 @@ public class LightBarController implements BatteryController.BatteryStateChangeC private final DarkIconDispatcher mDarkIconDispatcher; private final BatteryController mBatteryController; private final NavigationModeController mNavModeController; + private final FeatureFlags mFeatureFlags; private final DumpManager mDumpManager; private final DisplayTracker mDisplayTracker; @@ -352,12 +469,14 @@ public class LightBarController implements BatteryController.BatteryStateChangeC DarkIconDispatcher darkIconDispatcher, BatteryController batteryController, NavigationModeController navModeController, + FeatureFlags featureFlags, DumpManager dumpManager, DisplayTracker displayTracker) { mDarkIconDispatcher = darkIconDispatcher; mBatteryController = batteryController; mNavModeController = navModeController; + mFeatureFlags = featureFlags; mDumpManager = dumpManager; mDisplayTracker = displayTracker; } @@ -365,7 +484,7 @@ public class LightBarController implements BatteryController.BatteryStateChangeC /** Create an {@link LightBarController} */ public LightBarController create(Context context) { return new LightBarController(context, mDarkIconDispatcher, mBatteryController, - mNavModeController, mDumpManager, mDisplayTracker); + mNavModeController, mFeatureFlags, mDumpManager, mDisplayTracker); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index 006a029de8e0..b9a12e28b8ca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -306,7 +306,7 @@ public class NotificationIconContainer extends ViewGroup { public void applyIconStates() { for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); - ViewState childState = mIconStates.get(child); + IconState childState = mIconStates.get(child); if (childState != null) { childState.applyToView(child); } @@ -339,6 +339,7 @@ public class NotificationIconContainer extends ViewGroup { } } if (child instanceof StatusBarIconView) { + ((StatusBarIconView) child).updateIconDimens(); ((StatusBarIconView) child).setDozing(mDozing, false, 0); } } @@ -447,9 +448,14 @@ public class NotificationIconContainer extends ViewGroup { @VisibleForTesting boolean isOverflowing(boolean isLastChild, float translationX, float layoutEnd, float iconSize) { - // Layout end, as used here, does not include padding end. - final float overflowX = isLastChild ? layoutEnd : layoutEnd - iconSize; - return translationX >= overflowX; + if (isLastChild) { + return translationX + iconSize > layoutEnd; + } else { + // If the child is not the last child, we need to ensure that we have room for the next + // icon and the dot. The dot could be as large as an icon, so verify that we have room + // for 2 icons. + return translationX + iconSize * 2f > layoutEnd; + } } /** @@ -489,10 +495,7 @@ public class NotificationIconContainer extends ViewGroup { // First icon to overflow. if (firstOverflowIndex == -1 && isOverflowing) { firstOverflowIndex = i; - mVisualOverflowStart = layoutEnd - mIconSize; - if (forceOverflow || mIsStaticLayout) { - mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart); - } + mVisualOverflowStart = translationX; } final float drawingScale = mOnLockScreen && view instanceof StatusBarIconView ? ((StatusBarIconView) view).getIconScaleIncreased() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 51c56a021825..fdb772bc6c13 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -42,6 +42,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.colorextraction.ColorExtractor.GradientColors; import com.android.internal.graphics.ColorUtils; +import com.android.internal.util.ContrastColorUtil; import com.android.internal.util.function.TriConsumer; import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.keyguard.KeyguardUpdateMonitor; @@ -249,6 +250,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener; private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator; private final FeatureFlags mFeatureFlags; + private final boolean mUseNewLightBarLogic; private Consumer<Integer> mScrimVisibleListener; private boolean mBlankScreen; private boolean mScreenBlankingCallbackCalled; @@ -306,6 +308,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mScrimStateListener = lightBarController::setScrimState; mLargeScreenShadeInterpolator = largeScreenShadeInterpolator; mFeatureFlags = featureFlags; + mUseNewLightBarLogic = featureFlags.isEnabled(Flags.NEW_LIGHT_BAR_LOGIC); mDefaultScrimAlpha = BUSY_SCRIM_ALPHA; mKeyguardStateController = keyguardStateController; @@ -1159,7 +1162,13 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump if (mClipsQsScrim && mQsBottomVisible) { alpha = mNotificationsAlpha; } - mScrimStateListener.accept(mState, alpha, mScrimInFront.getColors()); + if (mUseNewLightBarLogic) { + mScrimStateListener.accept(mState, alpha, mColors); + } else { + // NOTE: This wasn't wrong, but it implied that each scrim might have different colors, + // when in fact they all share the same GradientColors instance, which we own. + mScrimStateListener.accept(mState, alpha, mScrimInFront.getColors()); + } } private void dispatchScrimsVisible() { @@ -1487,8 +1496,15 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump int accent = Utils.getColorAccent(mScrimBehind.getContext()).getDefaultColor(); mColors.setMainColor(background); mColors.setSecondaryColor(accent); - mColors.setSupportsDarkText( - ColorUtils.calculateContrast(mColors.getMainColor(), Color.WHITE) > 4.5); + if (mUseNewLightBarLogic) { + final boolean isBackgroundLight = !ContrastColorUtil.isColorDark(background); + mColors.setSupportsDarkText(isBackgroundLight); + } else { + // NOTE: This was totally backward, but LightBarController was flipping it back. + // There may be other consumers of this which would struggle though + mColors.setSupportsDarkText( + ColorUtils.calculateContrast(mColors.getMainColor(), Color.WHITE) > 4.5); + } mNeedsDrawableColorUpdate = true; } 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 a8a834f1e8f4..678873c0165c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -203,8 +203,7 @@ public interface StatusBarIconController { @Override protected LayoutParams onCreateLayoutParams() { - LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize); + LinearLayout.LayoutParams lp = super.onCreateLayoutParams(); lp.setMargins(mIconHPadding, 0, mIconHPadding, 0); return lp; } @@ -370,7 +369,7 @@ public interface StatusBarIconController { private final MobileIconsViewModel mMobileIconsViewModel; protected final Context mContext; - protected final int mIconSize; + protected int mIconSize; // Whether or not these icons show up in dumpsys protected boolean mShouldLog = false; private StatusBarIconController mController; @@ -395,10 +394,10 @@ public interface StatusBarIconController { mStatusBarPipelineFlags = statusBarPipelineFlags; mMobileContextProvider = mobileContextProvider; mContext = group.getContext(); - mIconSize = mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_icon_size); mLocation = location; + reloadDimens(); + if (statusBarPipelineFlags.runNewMobileIconsBackend()) { // This starts the flow for the new pipeline, and will notify us of changes if // {@link StatusBarPipelineFlags#useNewMobileIcons} is also true. @@ -609,13 +608,9 @@ public interface StatusBarIconController { mGroup.removeAllViews(); } - protected void onDensityOrFontScaleChanged() { - for (int i = 0; i < mGroup.getChildCount(); i++) { - View child = mGroup.getChildAt(i); - LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize); - child.setLayoutParams(lp); - } + protected void reloadDimens() { + mIconSize = mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_icon_size); } private void setHeightAndCenter(ImageView imageView, int height) { 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 3a184239ac43..80d5651a65dc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java @@ -109,6 +109,7 @@ public class StatusBarIconControllerImpl implements Tunable, } group.setController(this); + group.reloadDimens(); mIconGroups.add(group); List<Slot> allSlots = mStatusBarIconList.getSlots(); for (int i = 0; i < allSlots.size(); i++) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java index 26c17674ab10..ddbfd43f9bf6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java @@ -22,6 +22,8 @@ import static com.android.systemui.statusbar.StatusBarIconView.STATE_ICON; import android.annotation.Nullable; import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; @@ -72,13 +74,16 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { // Any ignored icon will never be added as a child private ArrayList<String> mIgnoredSlots = new ArrayList<>(); + private Configuration mConfiguration; + public StatusIconContainer(Context context) { this(context, null); } public StatusIconContainer(Context context, AttributeSet attrs) { super(context, attrs); - initDimens(); + mConfiguration = new Configuration(context.getResources().getConfiguration()); + reloadDimens(); setWillNotDraw(!DEBUG_OVERFLOW); } @@ -95,7 +100,7 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { return mShouldRestrictIcons; } - private void initDimens() { + private void reloadDimens() { // This is the same value that StatusBarIconView uses mIconDotFrameWidth = getResources().getDimensionPixelSize( com.android.internal.R.dimen.status_bar_icon_size); @@ -211,6 +216,16 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { child.setTag(R.id.status_bar_view_state_tag, null); } + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + final int configDiff = newConfig.diff(mConfiguration); + mConfiguration.setTo(newConfig); + if ((configDiff & (ActivityInfo.CONFIG_DENSITY | ActivityInfo.CONFIG_FONT_SCALE)) != 0) { + reloadDimens(); + } + } + /** * Add a name of an icon slot to be ignored. It will not show up nor be measured * @param slotName name of the icon as it exists in @@ -348,13 +363,17 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { int totalVisible = mLayoutStates.size(); int maxVisible = totalVisible <= MAX_ICONS ? MAX_ICONS : MAX_ICONS - 1; - mUnderflowStart = 0; + // Init mUnderflowStart value with the offset to let the dot be placed next to battery icon. + // It to prevent if the underflow happens at rightest(totalVisible - 1) child then break the + // for loop with mUnderflowStart staying 0(initial value), causing the dot be placed at the + // leftest side. + mUnderflowStart = (int) Math.max(contentStart, width - getPaddingEnd() - mUnderflowWidth); int visible = 0; int firstUnderflowIndex = -1; for (int i = totalVisible - 1; i >= 0; i--) { StatusIconState state = mLayoutStates.get(i); // Allow room for underflow if we found we need it in onMeasure - if (mNeedsUnderflow && (state.getXTranslation() < (contentStart + mUnderflowWidth)) + if ((mNeedsUnderflow && (state.getXTranslation() < (contentStart + mUnderflowWidth))) || (mShouldRestrictIcons && (visible >= maxVisible))) { firstUnderflowIndex = i; break; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java index 98cde2a049da..abedd3220c93 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java @@ -28,6 +28,11 @@ public interface CastController extends CallbackController<Callback>, Dumpable { void startCasting(CastDevice device); void stopCasting(CastDevice device); + /** + * @return whether we have a connected device. + */ + boolean hasConnectedCastDevice(); + public interface Callback { void onCastDevicesChanged(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java index 7290a1a82070..f7b601b9d284 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java @@ -217,6 +217,12 @@ public class CastControllerImpl implements CastController { } } + @Override + public boolean hasConnectedCastDevice() { + return getCastDevices().stream().anyMatch( + castDevice -> castDevice.state == CastDevice.STATE_CONNECTED); + } + private void setProjection(MediaProjectionInfo projection, boolean started) { boolean changed = false; final MediaProjectionInfo oldProjection = mProjection; diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt index 412b3150489c..27aaa6828036 100644 --- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt +++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt @@ -22,7 +22,6 @@ import android.content.Context import android.hardware.BatteryState import android.hardware.input.InputManager import android.hardware.input.InputSettings -import android.os.Build import android.os.Handler import android.util.ArrayMap import android.util.Log @@ -35,6 +34,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.log.DebugLogger.debugLog import com.android.systemui.shared.hardware.hasInputDevice import com.android.systemui.shared.hardware.isInternalStylusSource import java.util.concurrent.CopyOnWriteArrayList @@ -81,7 +81,7 @@ constructor( fun startListener() { handler.post { if (hasStarted) return@post - logDebug { "Listener has started." } + debugLog { "Listener has started." } hasStarted = true isInUsiSession = @@ -109,7 +109,7 @@ constructor( val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return - logDebug { + debugLog { "Stylus InputDevice added: $deviceId ${device.name}, " + "External: ${device.isExternal}" } @@ -134,7 +134,7 @@ constructor( val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return - logDebug { "Stylus InputDevice changed: $deviceId ${device.name}" } + debugLog { "Stylus InputDevice changed: $deviceId ${device.name}" } val currAddress: String? = device.bluetoothAddress val prevAddress: String? = inputDeviceAddressMap[deviceId] @@ -155,7 +155,7 @@ constructor( if (!hasStarted) return if (!inputDeviceAddressMap.contains(deviceId)) return - logDebug { "Stylus InputDevice removed: $deviceId" } + debugLog { "Stylus InputDevice removed: $deviceId" } unregisterBatteryListener(deviceId) @@ -180,7 +180,7 @@ constructor( val isCharging = String(value) == "true" - logDebug { + debugLog { "Charging state metadata changed for device $inputDeviceId " + "${device.address}: $isCharging" } @@ -199,7 +199,7 @@ constructor( handler.post { if (!hasStarted) return@post - logDebug { + debugLog { "Battery state changed for $deviceId. " + "batteryState present: ${batteryState.isPresent}, " + "capacity: ${batteryState.capacity}" @@ -247,7 +247,7 @@ constructor( if (!featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)) return if (InputSettings.isStylusEverUsed(context)) return - logDebug { "Stylus used for the first time." } + debugLog { "Stylus used for the first time." } InputSettings.setStylusEverUsed(context, true) executeStylusCallbacks { cb -> cb.onStylusFirstUsed() } } @@ -264,7 +264,7 @@ constructor( val hasBtConnection = if (inputDeviceBtSessionIdMap.isEmpty()) 0 else 1 if (batteryStateValid && usiSessionId == null) { - logDebug { "USI battery newly present, entering new USI session: $deviceId" } + debugLog { "USI battery newly present, entering new USI session: $deviceId" } usiSessionId = instanceIdSequence.newInstanceId() uiEventLogger.logWithInstanceIdAndPosition( StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED, @@ -274,7 +274,7 @@ constructor( hasBtConnection, ) } else if (!batteryStateValid && usiSessionId != null) { - logDebug { "USI battery newly absent, exiting USI session: $deviceId" } + debugLog { "USI battery newly absent, exiting USI session: $deviceId" } uiEventLogger.logWithInstanceIdAndPosition( StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_REMOVED, 0, @@ -291,7 +291,7 @@ constructor( btAddress: String, btConnected: Boolean ) { - logDebug { + debugLog { "Bluetooth stylus ${if (btConnected) "connected" else "disconnected"}:" + " $deviceId $btAddress" } @@ -386,9 +386,3 @@ constructor( val TAG = StylusManager::class.simpleName.orEmpty() } } - -private inline fun logDebug(message: () -> String) { - if (Build.IS_DEBUGGABLE) { - Log.d(StylusManager.TAG, message()) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt index 21b0efadb8d5..6eddd9eb7ad2 100644 --- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt +++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt @@ -26,7 +26,6 @@ import android.content.Intent import android.content.IntentFilter import android.hardware.BatteryState import android.hardware.input.InputManager -import android.os.Build import android.os.Bundle import android.os.Handler import android.os.UserHandle @@ -40,6 +39,7 @@ import com.android.internal.logging.UiEventLogger import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.log.DebugLogger.debugLog import com.android.systemui.shared.hardware.hasInputDevice import com.android.systemui.shared.hardware.isAnyStylusSource import com.android.systemui.util.NotificationChannels @@ -110,7 +110,7 @@ constructor( inputDeviceId = deviceId batteryCapacity = batteryState.capacity - logDebug { + debugLog { "Updating notification battery state to $batteryCapacity " + "for InputDevice $deviceId." } @@ -130,14 +130,14 @@ constructor( handler.post updateSuppressed@{ if (suppressed == suppress) return@updateSuppressed - logDebug { "Updating notification suppression to $suppress." } + debugLog { "Updating notification suppression to $suppress." } suppressed = suppress refresh() } } private fun hideNotification() { - logDebug { "Cancelling USI low battery notification." } + debugLog { "Cancelling USI low battery notification." } instanceId = null notificationManager.cancel(USI_NOTIFICATION_ID) } @@ -160,7 +160,7 @@ constructor( .setAutoCancel(true) .build() - logDebug { "Show or update USI low battery notification at $batteryCapacity." } + debugLog { "Show or update USI low battery notification at $batteryCapacity." } logUiEvent(StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_SHOWN) notificationManager.notify(USI_NOTIFICATION_ID, notification) } @@ -188,12 +188,12 @@ constructor( override fun onReceive(context: Context, intent: Intent) { when (intent.action) { ACTION_DISMISSED_LOW_BATTERY -> { - logDebug { "USI low battery notification dismissed." } + debugLog { "USI low battery notification dismissed." } logUiEvent(StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_DISMISSED) updateSuppression(true) } ACTION_CLICKED_LOW_BATTERY -> { - logDebug { "USI low battery notification clicked." } + debugLog { "USI low battery notification clicked." } logUiEvent(StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_CLICKED) updateSuppression(true) if (inputDeviceId == null) return @@ -263,9 +263,3 @@ constructor( @VisibleForTesting const val KEY_SETTINGS_FRAGMENT_ARGS = ":settings:show_fragment_args" } } - -private inline fun logDebug(message: () -> String) { - if (Build.IS_DEBUGGABLE) { - Log.d(StylusUsiPowerUI.TAG, message()) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index b3e7cb0c77eb..e60f9b65dc1c 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -33,6 +33,7 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.inputmethodservice.InputMethodService; import android.os.IBinder; +import android.view.Display; import android.view.KeyEvent; import androidx.annotation.NonNull; @@ -347,12 +348,16 @@ public final class WMShell implements void initDesktopMode(DesktopMode desktopMode) { desktopMode.addVisibleTasksListener( new DesktopModeTaskRepository.VisibleTasksListener() { - @Override - public void onVisibilityChanged(boolean hasFreeformTasks) { - mSysUiState.setFlag(SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE, hasFreeformTasks) - .commitUpdate(mDisplayTracker.getDefaultDisplayId()); - } - }, mSysUiMainExecutor); + @Override + public void onVisibilityChanged(int displayId, boolean hasFreeformTasks) { + if (displayId == Display.DEFAULT_DISPLAY) { + mSysUiState.setFlag(SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE, + hasFreeformTasks) + .commitUpdate(mDisplayTracker.getDefaultDisplayId()); + } + // TODO(b/278084491): update sysui state for changes on other displays + } + }, mSysUiMainExecutor); } @Override diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml index e2b568cfea77..080be6d8cf25 100644 --- a/packages/SystemUI/tests/AndroidManifest.xml +++ b/packages/SystemUI/tests/AndroidManifest.xml @@ -196,6 +196,17 @@ android:exported="false" android:permission="com.android.systemui.permission.SELF" android:excludeFromRecents="true" /> + + <activity + android:name="com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity" + android:exported="false" + android:permission="com.android.systemui.permission.SELF" + android:excludeFromRecents="true" > + <intent-filter> + <action android:name="com.android.systemui.action.MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> </application> <instrumentation android:name="android.testing.TestableInstrumentation" diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index f4df26dec89e..8a05a37ad0dd 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -682,8 +682,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { } @Test - public void testReinflateViewFlipper_asyncBouncerFlagOn() { - when(mFeatureFlags.isEnabled(Flags.ASYNC_INFLATE_BOUNCER)).thenReturn(true); + public void testReinflateViewFlipper() { KeyguardSecurityViewFlipperController.OnViewInflatedCallback onViewInflatedCallback = controller -> { }; diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index a20875b5d9de..6e37ee791bd2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -40,14 +40,19 @@ import com.android.internal.widget.LockPatternUtils import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.FakePromptRepository +import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor +import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl +import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope import org.junit.After import org.junit.Ignore import org.junit.Rule @@ -87,13 +92,26 @@ class AuthContainerViewTest : SysuiTestCase() { @Mock lateinit var interactionJankMonitor: InteractionJankMonitor + private val testScope = TestScope(StandardTestDispatcher()) + private val fakeExecutor = FakeExecutor(FakeSystemClock()) private val biometricPromptRepository = FakePromptRepository() + private val rearDisplayStateRepository = FakeRearDisplayStateRepository() private val credentialInteractor = FakeCredentialInteractor() private val bpCredentialInteractor = BiometricPromptCredentialInteractor( Dispatchers.Main.immediate, biometricPromptRepository, credentialInteractor ) + private val displayStateInteractor = DisplayStateInteractorImpl( + testScope.backgroundScope, + mContext, + fakeExecutor, + rearDisplayStateRepository + ) + + private val authBiometricFingerprintViewModel = AuthBiometricFingerprintViewModel( + displayStateInteractor + ) private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor) private var authContainer: TestAuthContainerView? = null @@ -469,9 +487,10 @@ class AuthContainerViewTest : SysuiTestCase() { lockPatternUtils, interactionJankMonitor, { bpCredentialInteractor }, + { authBiometricFingerprintViewModel }, { credentialViewModel }, Handler(TestableLooper.get(this).looper), - FakeExecutor(FakeSystemClock()) + fakeExecutor ) { override fun postOnAnimation(runnable: Runnable) { runnable.run() 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 4f24b3ab5e09..a326cc7045dd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -93,6 +93,7 @@ import com.android.systemui.RoboPilotTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor; import com.android.systemui.biometrics.domain.interactor.LogContextInteractor; +import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel; import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.statusbar.CommandQueue; @@ -172,6 +173,8 @@ public class AuthControllerTest extends SysuiTestCase { @Mock private BiometricPromptCredentialInteractor mBiometricPromptCredentialInteractor; @Mock + private AuthBiometricFingerprintViewModel mAuthBiometricFingerprintViewModel; + @Mock private CredentialViewModel mCredentialViewModel; @Mock private UdfpsUtils mUdfpsUtils; @@ -995,8 +998,9 @@ public class AuthControllerTest extends SysuiTestCase { () -> mSideFpsController, mDisplayManager, mWakefulnessLifecycle, mPanelInteractionDetector, mUserManager, mLockPatternUtils, mUdfpsLogger, mLogContextInteractor, () -> mBiometricPromptCredentialInteractor, - () -> mCredentialViewModel, mInteractionJankMonitor, mHandler, - mBackgroundExecutor, mVibratorHelper, mUdfpsUtils); + () -> mAuthBiometricFingerprintViewModel, () -> mCredentialViewModel, + mInteractionJankMonitor, mHandler, mBackgroundExecutor, mVibratorHelper, + mUdfpsUtils); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt index e6334cf2e4a1..40d9009ee81d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt @@ -55,6 +55,9 @@ import com.android.systemui.R import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestableContext +import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository +import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor +import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository @@ -66,7 +69,9 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestCoroutineScope +import kotlinx.coroutines.test.TestScope import org.junit.Before import org.junit.Rule import org.junit.Test @@ -90,6 +95,8 @@ import org.mockito.junit.MockitoJUnit private const val DISPLAY_ID = 2 private const val SENSOR_ID = 1 +private const val REAR_DISPLAY_MODE_DEVICE_STATE = 3 + @SmallTest @RoboPilotTest @RunWith(AndroidJUnit4::class) @@ -112,7 +119,12 @@ class SideFpsControllerTest : SysuiTestCase() { private lateinit var keyguardBouncerRepository: FakeKeyguardBouncerRepository private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor + private lateinit var displayStateInteractor: DisplayStateInteractor + private val executor = FakeExecutor(FakeSystemClock()) + private val rearDisplayStateRepository = FakeRearDisplayStateRepository() + private val testScope = TestScope(StandardTestDispatcher()) + private lateinit var overlayController: ISidefpsController private lateinit var sideFpsController: SideFpsController @@ -142,6 +154,13 @@ class SideFpsControllerTest : SysuiTestCase() { FakeDeviceEntryFingerprintAuthRepository(), FakeSystemClock(), ) + displayStateInteractor = + DisplayStateInteractorImpl( + testScope.backgroundScope, + context, + executor, + rearDisplayStateRepository + ) context.addMockSystemService(DisplayManager::class.java, displayManager) context.addMockSystemService(WindowManager::class.java, windowManager) @@ -168,6 +187,7 @@ class SideFpsControllerTest : SysuiTestCase() { isReverseDefaultRotation: Boolean = false, initInfo: DisplayInfo.() -> Unit = {}, windowInsets: WindowInsets = insetsForSmallNavbar(), + inRearDisplayMode: Boolean = false, block: () -> Unit ) { this.deviceConfig = deviceConfig @@ -228,6 +248,13 @@ class SideFpsControllerTest : SysuiTestCase() { isReverseDefaultRotation ) + val rearDisplayDeviceStates = + if (inRearDisplayMode) intArrayOf(REAR_DISPLAY_MODE_DEVICE_STATE) else intArrayOf() + sideFpsControllerContext.orCreateTestableResources.addOverride( + com.android.internal.R.array.config_rearDisplayDeviceStates, + rearDisplayDeviceStates + ) + sideFpsController = SideFpsController( sideFpsControllerContext, @@ -237,12 +264,14 @@ class SideFpsControllerTest : SysuiTestCase() { activityTaskManager, overviewProxyService, displayManager, + displayStateInteractor, executor, handler, alternateBouncerInteractor, TestCoroutineScope(), - dumpManager, + dumpManager ) + rearDisplayStateRepository.setIsInRearDisplayMode(inRearDisplayMode) overlayController = ArgumentCaptor.forClass(ISidefpsController::class.java) @@ -584,10 +613,62 @@ class SideFpsControllerTest : SysuiTestCase() { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) } + @Test + fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_0() = + testWithDisplay( + deviceConfig = DeviceConfig.Y_ALIGNED, + isReverseDefaultRotation = false, + { rotation = Surface.ROTATION_0 }, + inRearDisplayMode = true, + ) { + verifySfpsIndicator_notAdded_InRearDisplayMode() + } + + @Test + fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_90() = + testWithDisplay( + deviceConfig = DeviceConfig.Y_ALIGNED, + isReverseDefaultRotation = false, + { rotation = Surface.ROTATION_90 }, + inRearDisplayMode = true, + ) { + verifySfpsIndicator_notAdded_InRearDisplayMode() + } + + @Test + fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_180() = + testWithDisplay( + deviceConfig = DeviceConfig.Y_ALIGNED, + isReverseDefaultRotation = false, + { rotation = Surface.ROTATION_180 }, + inRearDisplayMode = true, + ) { + verifySfpsIndicator_notAdded_InRearDisplayMode() + } + + @Test + fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_270() = + testWithDisplay( + deviceConfig = DeviceConfig.Y_ALIGNED, + isReverseDefaultRotation = false, + { rotation = Surface.ROTATION_270 }, + inRearDisplayMode = true, + ) { + verifySfpsIndicator_notAdded_InRearDisplayMode() + } + private fun verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible: Boolean) { sideFpsController.overlayOffsets = sensorLocation } + private fun verifySfpsIndicator_notAdded_InRearDisplayMode() { + sideFpsController.overlayOffsets = sensorLocation + overlayController.show(SENSOR_ID, REASON_UNKNOWN) + executor.runAllReady() + + verify(windowManager, never()).addView(any(), any()) + } + fun alternateBouncerVisibility_showAndHideSideFpsUI() = testWithDisplay { // WHEN alternate bouncer is visible keyguardBouncerRepository.setAlternateVisible(true) @@ -624,7 +705,7 @@ class SideFpsControllerTest : SysuiTestCase() { * in other rotations have been omitted. */ @Test - fun verifiesIndicatorPlacementForXAlignedSensor_0() { + fun verifiesIndicatorPlacementForXAlignedSensor_0() = testWithDisplay( deviceConfig = DeviceConfig.X_ALIGNED, isReverseDefaultRotation = false, @@ -641,7 +722,6 @@ class SideFpsControllerTest : SysuiTestCase() { assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX) assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0) } - } /** * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_270 @@ -650,7 +730,7 @@ class SideFpsControllerTest : SysuiTestCase() { * correctly, tests for indicator placement in other rotations have been omitted. */ @Test - fun verifiesIndicatorPlacementForXAlignedSensor_InReverseDefaultRotation_270() { + fun verifiesIndicatorPlacementForXAlignedSensor_InReverseDefaultRotation_270() = testWithDisplay( deviceConfig = DeviceConfig.X_ALIGNED, isReverseDefaultRotation = true, @@ -667,7 +747,6 @@ class SideFpsControllerTest : SysuiTestCase() { assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX) assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0) } - } /** * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_0, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepositoryTest.kt new file mode 100644 index 000000000000..dfe8d36504d0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepositoryTest.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.repository + +import android.hardware.devicestate.DeviceStateManager +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.RearDisplayStateRepository +import com.android.systemui.biometrics.data.repository.RearDisplayStateRepositoryImpl +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.withArgCaptor +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule + +private const val NORMAL_DISPLAY_MODE_DEVICE_STATE = 2 +private const val REAR_DISPLAY_MODE_DEVICE_STATE = 3 + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class RearDisplayStateRepositoryTest : SysuiTestCase() { + @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule() + @Mock private lateinit var deviceStateManager: DeviceStateManager + private lateinit var underTest: RearDisplayStateRepository + + private val testScope = TestScope(StandardTestDispatcher()) + private val fakeExecutor = FakeExecutor(FakeSystemClock()) + + @Captor + private lateinit var callbackCaptor: ArgumentCaptor<DeviceStateManager.DeviceStateCallback> + + @Before + fun setUp() { + val rearDisplayDeviceStates = intArrayOf(REAR_DISPLAY_MODE_DEVICE_STATE) + mContext.orCreateTestableResources.addOverride( + com.android.internal.R.array.config_rearDisplayDeviceStates, + rearDisplayDeviceStates + ) + + underTest = + RearDisplayStateRepositoryImpl( + testScope.backgroundScope, + mContext, + deviceStateManager, + fakeExecutor + ) + } + + @Test + fun updatesIsInRearDisplayMode_whenRearDisplayStateChanges() = + testScope.runTest { + val isInRearDisplayMode = collectLastValue(underTest.isInRearDisplayMode) + runCurrent() + + val callback = deviceStateManager.captureCallback() + + callback.onStateChanged(NORMAL_DISPLAY_MODE_DEVICE_STATE) + assertThat(isInRearDisplayMode()).isFalse() + + callback.onStateChanged(REAR_DISPLAY_MODE_DEVICE_STATE) + assertThat(isInRearDisplayMode()).isTrue() + } +} + +private fun DeviceStateManager.captureCallback() = + withArgCaptor<DeviceStateManager.DeviceStateCallback> { + verify(this@captureCallback).registerCallback(any(), capture()) + } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt new file mode 100644 index 000000000000..2217c5c677d5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt @@ -0,0 +1,84 @@ +package com.android.systemui.biometrics.domain.interactor + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.unfold.compat.ScreenSizeFoldProvider +import com.android.systemui.unfold.updates.FoldProvider +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.withArgCaptor +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class DisplayStateInteractorImplTest : SysuiTestCase() { + + @JvmField @Rule var mockitoRule = MockitoJUnit.rule() + + private val fakeExecutor = FakeExecutor(FakeSystemClock()) + private val testScope = TestScope(StandardTestDispatcher()) + private val rearDisplayStateRepository = FakeRearDisplayStateRepository() + + @Mock private lateinit var screenSizeFoldProvider: ScreenSizeFoldProvider + private lateinit var interactor: DisplayStateInteractorImpl + + @Before + fun setup() { + interactor = + DisplayStateInteractorImpl( + testScope.backgroundScope, + mContext, + fakeExecutor, + rearDisplayStateRepository + ) + interactor.setScreenSizeFoldProvider(screenSizeFoldProvider) + } + + @Test + fun isInRearDisplayModeChanges() = + testScope.runTest { + val isInRearDisplayMode = collectLastValue(interactor.isInRearDisplayMode) + + rearDisplayStateRepository.setIsInRearDisplayMode(false) + assertThat(isInRearDisplayMode()).isFalse() + + rearDisplayStateRepository.setIsInRearDisplayMode(true) + assertThat(isInRearDisplayMode()).isTrue() + } + + @Test + fun isFoldedChanges() = + testScope.runTest { + val isFolded = collectLastValue(interactor.isFolded) + runCurrent() + val callback = screenSizeFoldProvider.captureCallback() + + callback.onFoldUpdated(isFolded = true) + assertThat(isFolded()).isTrue() + + callback.onFoldUpdated(isFolded = false) + assertThat(isFolded()).isFalse() + } +} + +private fun FoldProvider.captureCallback() = + withArgCaptor<FoldProvider.FoldCallback> { + verify(this@captureCallback).registerCallback(capture(), any()) + } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/AuthBiometricFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/AuthBiometricFingerprintViewModelTest.kt new file mode 100644 index 000000000000..0c210e51cb90 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/AuthBiometricFingerprintViewModelTest.kt @@ -0,0 +1,69 @@ +package com.android.systemui.biometrics.ui.viewmodel + +import android.content.res.Configuration +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository +import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor +import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +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 AuthBiometricFingerprintViewModelTest : SysuiTestCase() { + + private val rearDisplayStateRepository = FakeRearDisplayStateRepository() + private val testScope = TestScope(StandardTestDispatcher()) + private val fakeExecutor = FakeExecutor(FakeSystemClock()) + + private lateinit var interactor: DisplayStateInteractor + private lateinit var viewModel: AuthBiometricFingerprintViewModel + + @Before + fun setup() { + interactor = + DisplayStateInteractorImpl( + testScope.backgroundScope, + mContext, + fakeExecutor, + rearDisplayStateRepository + ) + viewModel = AuthBiometricFingerprintViewModel(interactor) + } + + @Test + fun iconUpdates_onConfigurationChanged() { + testScope.runTest { + runCurrent() + val testConfig = Configuration() + val folded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP - 1 + val unfolded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + 1 + val currentIcon = collectLastValue(viewModel.iconAsset) + + testConfig.smallestScreenWidthDp = folded + viewModel.onConfigurationChanged(testConfig) + val foldedIcon = currentIcon() + + testConfig.smallestScreenWidthDp = unfolded + viewModel.onConfigurationChanged(testConfig) + val unfoldedIcon = currentIcon() + + assertThat(foldedIcon).isNotEqualTo(unfoldedIcon) + } + } +} + +internal const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600 diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java index 8795ac013bd3..c9ee1e8ef5b9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java @@ -70,6 +70,7 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.phone.CentralSurfaces; +import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.telephony.TelephonyListenerManager; @@ -115,6 +116,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { @Mock private MetricsLogger mMetricsLogger; @Mock private SysuiColorExtractor mColorExtractor; @Mock private IStatusBarService mStatusBarService; + @Mock private LightBarController mLightBarController; @Mock private NotificationShadeWindowController mNotificationShadeWindowController; @Mock private IWindowManager mWindowManager; @Mock private Executor mBackgroundExecutor; @@ -166,6 +168,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { mMetricsLogger, mColorExtractor, mStatusBarService, + mLightBarController, mNotificationShadeWindowController, mWindowManager, mBackgroundExecutor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java index b9cfc6550966..e981d627b582 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java @@ -25,13 +25,11 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.IActivityTaskManager; import android.app.IApplicationThread; import android.app.ProfilerInfo; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Bundle; @@ -59,13 +57,14 @@ import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidJUnit4.class) public class WorkLockActivityControllerTest extends SysuiTestCase { - private static final int USER_ID = 333; + private static final int TASK_USER_ID = 333; + private static final int PROFILE_USER_ID = 555; private static final int TASK_ID = 444; private static final ActivityManager.RunningTaskInfo TASK_INFO = new ActivityManager.RunningTaskInfo(); static { - TASK_INFO.userId = USER_ID; + TASK_INFO.userId = TASK_USER_ID; TASK_INFO.taskId = TASK_ID; } @@ -101,10 +100,10 @@ public class WorkLockActivityControllerTest extends SysuiTestCase { setActivityStartCode(TASK_ID, true /*taskOverlay*/, ActivityManager.START_SUCCESS); // And the controller receives a message saying the profile is locked, - mTaskStackListener.onTaskProfileLocked(TASK_INFO); + mTaskStackListener.onTaskProfileLocked(TASK_INFO, PROFILE_USER_ID); // The overlay should start and the task the activity started in should not be removed. - verifyStartActivity(TASK_ID, true /*taskOverlay*/); + verifyStartActivity(TASK_ID, true /*taskOverlay*/, PROFILE_USER_ID); verify(mIActivityTaskManager, never()).removeTask(anyInt() /*taskId*/); } @@ -114,11 +113,11 @@ public class WorkLockActivityControllerTest extends SysuiTestCase { setActivityStartCode(TASK_ID, true /*taskOverlay*/, ActivityManager.START_CLASS_NOT_FOUND); // And the controller receives a message saying the profile is locked, - mTaskStackListener.onTaskProfileLocked(TASK_INFO); + mTaskStackListener.onTaskProfileLocked(TASK_INFO, PROFILE_USER_ID); // The task the activity started in should be removed to prevent the locked task from // being shown. - verifyStartActivity(TASK_ID, true /*taskOverlay*/); + verifyStartActivity(TASK_ID, true /*taskOverlay*/, PROFILE_USER_ID); verify(mIActivityTaskManager).removeTask(TASK_ID); } @@ -141,12 +140,13 @@ public class WorkLockActivityControllerTest extends SysuiTestCase { eq(ActivityManager.getCurrentUser())); } - private void verifyStartActivity(int taskId, boolean taskOverlay) throws Exception { + private void verifyStartActivity(int taskId, boolean taskOverlay, int profileUserId) + throws Exception { verify(mIActivityTaskManager).startActivityAsUser( eq((IApplicationThread) null), eq((String) null), eq((String) null), - any(Intent.class), + argThat(hasUserId(profileUserId)), eq((String) null), eq((IBinder) null), eq((String) null), @@ -157,24 +157,15 @@ public class WorkLockActivityControllerTest extends SysuiTestCase { eq(ActivityManager.getCurrentUser())); } - private static ArgumentMatcher<Intent> hasComponent(final Context context, - final Class<? extends Activity> activityClass) { - return new ArgumentMatcher<Intent>() { - @Override - public boolean matches(Intent intent) { - return new ComponentName(context, activityClass).equals(intent.getComponent()); - } - }; + private static ArgumentMatcher<Intent> hasUserId(int userId) { + return intent -> intent.getIntExtra(Intent.EXTRA_USER_ID, -1) == userId; } private static ArgumentMatcher<Bundle> hasOptions(final int taskId, final boolean overlay) { - return new ArgumentMatcher<Bundle>() { - @Override - public boolean matches(Bundle item) { - final ActivityOptions options = ActivityOptions.fromBundle(item); - return (options.getLaunchTaskId() == taskId) - && (options.getTaskOverlay() == overlay); - } + return item -> { + final ActivityOptions options = ActivityOptions.fromBundle(item); + return (options.getLaunchTaskId() == taskId) + && (options.getTaskOverlay() == overlay); }; } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt index 0a1db60e075c..d428db7b9dda 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt @@ -40,7 +40,6 @@ import android.testing.TestableLooper.RunWithLooper import androidx.media.utils.MediaConstants import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId -import com.android.internal.statusbar.IStatusBarService import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.InstanceIdSequenceFake import com.android.systemui.R @@ -131,7 +130,6 @@ class MediaDataManagerTest : SysuiTestCase() { @Mock lateinit var activityStarter: ActivityStarter @Mock lateinit var smartspaceManager: SmartspaceManager @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor - @Mock lateinit var statusBarService: IStatusBarService lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider @Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget @Mock private lateinit var mediaRecommendationItem: SmartspaceAction @@ -194,8 +192,7 @@ class MediaDataManagerTest : SysuiTestCase() { mediaFlags = mediaFlags, logger = logger, smartspaceManager = smartspaceManager, - keyguardUpdateMonitor = keyguardUpdateMonitor, - statusBarService = statusBarService, + keyguardUpdateMonitor = keyguardUpdateMonitor ) verify(tunerService) .addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION)) @@ -520,136 +517,19 @@ class MediaDataManagerTest : SysuiTestCase() { } @Test - fun testOnNotificationAdded_emptyTitle_notLoaded() { - // GIVEN that the manager has a notification with an empty title. + fun testOnNotificationRemoved_emptyTitle_notConverted() { + // GIVEN that the manager has a notification with a resume action and empty title. whenever(controller.metadata) .thenReturn( metadataBuilder .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE) .build() ) - mediaDataManager.onNotificationAdded(KEY, mediaNotification) - - assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) - assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - verify(statusBarService) - .onNotificationError( - eq(PACKAGE_NAME), - eq(mediaNotification.tag), - eq(mediaNotification.id), - eq(mediaNotification.uid), - eq(mediaNotification.initialPid), - eq(MEDIA_TITLE_ERROR_MESSAGE), - eq(mediaNotification.user.identifier) - ) - verify(listener, never()) - .onMediaDataLoaded( - eq(KEY), - eq(null), - capture(mediaDataCaptor), - eq(true), - eq(0), - eq(false) - ) - verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any()) - verify(logger, never()).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), any()) - } - - @Test - fun testOnNotificationAdded_blankTitle_notLoaded() { - // GIVEN that the manager has a notification with a blank title. - whenever(controller.metadata) - .thenReturn( - metadataBuilder - .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE) - .build() - ) - mediaDataManager.onNotificationAdded(KEY, mediaNotification) - - assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) - assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - verify(statusBarService) - .onNotificationError( - eq(PACKAGE_NAME), - eq(mediaNotification.tag), - eq(mediaNotification.id), - eq(mediaNotification.uid), - eq(mediaNotification.initialPid), - eq(MEDIA_TITLE_ERROR_MESSAGE), - eq(mediaNotification.user.identifier) - ) - verify(listener, never()) - .onMediaDataLoaded( - eq(KEY), - eq(null), - capture(mediaDataCaptor), - eq(true), - eq(0), - eq(false) - ) - verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any()) - verify(logger, never()).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), any()) - } - - @Test - fun testOnNotificationUpdated_invalidTitle_logMediaRemoved() { - addNotificationAndLoad() - val data = mediaDataCaptor.value - - verify(listener) - .onMediaDataLoaded( - eq(KEY), - eq(null), - capture(mediaDataCaptor), - eq(true), - eq(0), - eq(false) - ) - - reset(listener) - whenever(controller.metadata) - .thenReturn( - metadataBuilder - .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE) - .build() - ) - mediaDataManager.onNotificationAdded(KEY, mediaNotification) - assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) - assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - verify(statusBarService) - .onNotificationError( - eq(PACKAGE_NAME), - eq(mediaNotification.tag), - eq(mediaNotification.id), - eq(mediaNotification.uid), - eq(mediaNotification.initialPid), - eq(MEDIA_TITLE_ERROR_MESSAGE), - eq(mediaNotification.user.identifier) - ) - verify(listener, never()) - .onMediaDataLoaded( - eq(KEY), - eq(null), - capture(mediaDataCaptor), - eq(true), - eq(0), - eq(false) - ) - verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) - } - - @Test - fun testOnNotificationRemoved_emptyTitle_notConverted() { - // GIVEN that the manager has a notification with a resume action and empty title. addNotificationAndLoad() val data = mediaDataCaptor.value val instanceId = data.instanceId assertThat(data.resumption).isFalse() - mediaDataManager.onMediaDataLoaded( - KEY, - null, - data.copy(song = SESSION_EMPTY_TITLE, resumeAction = Runnable {}) - ) + mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {})) // WHEN the notification is removed reset(listener) @@ -674,15 +554,17 @@ class MediaDataManagerTest : SysuiTestCase() { @Test fun testOnNotificationRemoved_blankTitle_notConverted() { // GIVEN that the manager has a notification with a resume action and blank title. + whenever(controller.metadata) + .thenReturn( + metadataBuilder + .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE) + .build() + ) addNotificationAndLoad() val data = mediaDataCaptor.value val instanceId = data.instanceId assertThat(data.resumption).isFalse() - mediaDataManager.onMediaDataLoaded( - KEY, - null, - data.copy(song = SESSION_BLANK_TITLE, resumeAction = Runnable {}) - ) + mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {})) // WHEN the notification is removed reset(listener) diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt new file mode 100644 index 000000000000..36b913fc3e7b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.notetask + +import android.content.Context +import android.content.Intent +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import androidx.test.rule.ActivityTestRule +import androidx.test.runner.intercepting.SingleActivityFactory +import com.android.dx.mockito.inline.extended.ExtendedMockito.verify +import com.android.systemui.SysuiTestCase +import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.Companion.ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE +import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +@TestableLooper.RunWithLooper +class LaunchNotesRoleSettingsTrampolineActivityTest : SysuiTestCase() { + + @Mock lateinit var noteTaskController: NoteTaskController + + @Rule + @JvmField + val activityRule = + ActivityTestRule<LaunchNotesRoleSettingsTrampolineActivity>( + /* activityFactory= */ object : + SingleActivityFactory<LaunchNotesRoleSettingsTrampolineActivity>( + LaunchNotesRoleSettingsTrampolineActivity::class.java + ) { + override fun create(intent: Intent?) = + LaunchNotesRoleSettingsTrampolineActivity(noteTaskController) + }, + /* initialTouchMode= */ false, + /* launchActivity= */ false, + ) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + } + + @After + fun tearDown() { + activityRule.finishActivity() + } + + @Test + fun startActivity_noAction_shouldLaunchNotesRoleSettingTaskWithNullEntryPoint() { + activityRule.launchActivity(/* startIntent= */ null) + + verify(noteTaskController).startNotesRoleSetting(any(Context::class.java), eq(null)) + } + + @Test + fun startActivity_quickAffordanceAction_shouldLaunchNotesRoleSettingTaskWithQuickAffordanceEntryPoint() { // ktlint-disable max-line-length + activityRule.launchActivity(Intent(ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE)) + + verify(noteTaskController) + .startNotesRoleSetting(any(Context::class.java), eq(QUICK_AFFORDANCE)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt index 5dbcd33ab0e6..5f897050099a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt @@ -47,6 +47,9 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.notetask.NoteTaskController.Companion.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE import com.android.systemui.notetask.NoteTaskController.Companion.SETTINGS_CREATE_NOTE_TASK_SHORTCUT_COMPONENT import com.android.systemui.notetask.NoteTaskController.Companion.SHORTCUT_ID +import com.android.systemui.notetask.NoteTaskEntryPoint.APP_CLIPS +import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE +import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity import com.android.systemui.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity import com.android.systemui.settings.FakeUserTracker @@ -493,7 +496,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { ) .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL) - createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE) + createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE) verifyZeroInteractions(context, bubbles, eventLogger) } @@ -509,7 +512,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { ) .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL) - createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE) + createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE) verifyZeroInteractions(context, bubbles, eventLogger) } @@ -525,7 +528,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { ) .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL) - createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE) + createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE) verifyNoteTaskOpenInBubbleInUser(userTracker.userHandle) } @@ -541,7 +544,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { ) .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL) - createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE) + createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE) verifyNoteTaskOpenInBubbleInUser(userTracker.userHandle) } @@ -553,7 +556,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true) userTracker.set(listOf(mainUserInfo), mainAndWorkProfileUsers.indexOf(mainUserInfo)) - createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE) + createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE) verifyNoteTaskOpenInBubbleInUser(mainUserInfo.userHandle) } @@ -563,7 +566,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true) userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo)) - createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE) + createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE) verifyNoteTaskOpenInBubbleInUser(workUserInfo.userHandle) } @@ -734,6 +737,129 @@ internal class NoteTaskControllerTest : SysuiTestCase() { } // endregion + // region getUserForHandlingNotesTaking + @Test + fun getUserForHandlingNotesTaking_cope_quickAffordance_shouldReturnWorkProfileUser() { + whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true) + userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo)) + + val user = createNoteTaskController().getUserForHandlingNotesTaking(QUICK_AFFORDANCE) + + assertThat(user).isEqualTo(UserHandle.of(workUserInfo.id)) + } + + @Test + fun getUserForHandlingNotesTaking_cope_tailButton_shouldReturnWorkProfileUser() { + whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true) + userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo)) + + val user = createNoteTaskController().getUserForHandlingNotesTaking(TAIL_BUTTON) + + assertThat(user).isEqualTo(UserHandle.of(workUserInfo.id)) + } + + @Test + fun getUserForHandlingNotesTaking_cope_appClip_shouldReturnCurrentUser() { + whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true) + userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo)) + + val user = createNoteTaskController().getUserForHandlingNotesTaking(APP_CLIPS) + + assertThat(user).isEqualTo(UserHandle.of(mainUserInfo.id)) + } + + @Test + fun getUserForHandlingNotesTaking_noManagement_quickAffordance_shouldReturnCurrentUser() { + userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo)) + + val user = createNoteTaskController().getUserForHandlingNotesTaking(QUICK_AFFORDANCE) + + assertThat(user).isEqualTo(UserHandle.of(mainUserInfo.id)) + } + + @Test + fun getUserForHandlingNotesTaking_noManagement_tailButton_shouldReturnCurrentUser() { + userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo)) + + val user = createNoteTaskController().getUserForHandlingNotesTaking(TAIL_BUTTON) + + assertThat(user).isEqualTo(UserHandle.of(mainUserInfo.id)) + } + + @Test + fun getUserForHandlingNotesTaking_noManagement_appClip_shouldReturnCurrentUser() { + userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo)) + + val user = createNoteTaskController().getUserForHandlingNotesTaking(APP_CLIPS) + + assertThat(user).isEqualTo(UserHandle.of(mainUserInfo.id)) + } + // endregion + + // startregion startNotesRoleSetting + @Test + fun startNotesRoleSetting_cope_quickAffordance_shouldStartNoteRoleIntentWithWorkProfileUser() { + whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true) + userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo)) + + createNoteTaskController().startNotesRoleSetting(context, QUICK_AFFORDANCE) + + val intentCaptor = argumentCaptor<Intent>() + val userCaptor = argumentCaptor<UserHandle>() + verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor)) + intentCaptor.value.let { intent -> + assertThat(intent).hasAction(Intent.ACTION_MANAGE_DEFAULT_APP) + } + assertThat(userCaptor.value).isEqualTo(UserHandle.of(workUserInfo.id)) + } + + @Test + fun startNotesRoleSetting_cope_nullEntryPoint_shouldStartNoteRoleIntentWithCurrentUser() { + whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true) + userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo)) + + createNoteTaskController().startNotesRoleSetting(context, entryPoint = null) + + val intentCaptor = argumentCaptor<Intent>() + val userCaptor = argumentCaptor<UserHandle>() + verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor)) + intentCaptor.value.let { intent -> + assertThat(intent).hasAction(Intent.ACTION_MANAGE_DEFAULT_APP) + } + assertThat(userCaptor.value).isEqualTo(UserHandle.of(mainUserInfo.id)) + } + + @Test + fun startNotesRoleSetting_noManagement_quickAffordance_shouldStartNoteRoleIntentWithCurrentUser() { // ktlint-disable max-line-length + userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo)) + + createNoteTaskController().startNotesRoleSetting(context, QUICK_AFFORDANCE) + + val intentCaptor = argumentCaptor<Intent>() + val userCaptor = argumentCaptor<UserHandle>() + verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor)) + intentCaptor.value.let { intent -> + assertThat(intent).hasAction(Intent.ACTION_MANAGE_DEFAULT_APP) + } + assertThat(userCaptor.value).isEqualTo(UserHandle.of(mainUserInfo.id)) + } + + @Test + fun startNotesRoleSetting_noManagement_nullEntryPoint_shouldStartNoteRoleIntentWithCurrentUser() { // ktlint-disable max-line-length + userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo)) + + createNoteTaskController().startNotesRoleSetting(context, entryPoint = null) + + val intentCaptor = argumentCaptor<Intent>() + val userCaptor = argumentCaptor<UserHandle>() + verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor)) + intentCaptor.value.let { intent -> + assertThat(intent).hasAction(Intent.ACTION_MANAGE_DEFAULT_APP) + } + assertThat(userCaptor.value).isEqualTo(UserHandle.of(mainUserInfo.id)) + } + // endregion + private companion object { const val NOTE_TASK_SHORT_LABEL = "Notetaking" const val NOTE_TASK_ACTIVITY_NAME = "NoteTaskActivity" diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt index 42ef2b5ad3ab..c17d6d192698 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt @@ -18,7 +18,12 @@ package com.android.systemui.notetask.quickaffordance +import android.app.role.RoleManager +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.pm.PackageManager.ApplicationInfoFlags import android.hardware.input.InputSettings +import android.os.UserHandle import android.os.UserManager import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner @@ -31,11 +36,18 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository +import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.Companion.ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE import com.android.systemui.notetask.NoteTaskController import com.android.systemui.notetask.NoteTaskEntryPoint +import com.android.systemui.notetask.NoteTaskInfoResolver +import com.android.systemui.shared.customization.data.content.CustomizationProviderContract.LockScreenQuickAffordances.AffordanceTable.COMPONENT_NAME_SEPARATOR import com.android.systemui.stylus.StylusManager +import com.android.systemui.util.concurrency.FakeExecutor +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.whenever +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -45,6 +57,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.anyString import org.mockito.Mockito.verify import org.mockito.MockitoSession import org.mockito.quality.Strictness @@ -58,6 +71,8 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { @Mock lateinit var stylusManager: StylusManager @Mock lateinit var repository: KeyguardQuickAffordanceRepository @Mock lateinit var userManager: UserManager + @Mock lateinit var roleManager: RoleManager + @Mock lateinit var packageManager: PackageManager private lateinit var mockitoSession: MockitoSession @@ -69,6 +84,23 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { .mockStatic(InputSettings::class.java) .strictness(Strictness.LENIENT) .startMocking() + + whenever( + packageManager.getApplicationInfoAsUser( + anyString(), + any(ApplicationInfoFlags::class.java), + any(UserHandle::class.java) + ) + ) + .thenReturn(ApplicationInfo()) + whenever(controller.getUserForHandlingNotesTaking(any())).thenReturn(UserHandle.SYSTEM) + whenever( + roleManager.getRoleHoldersAsUser( + eq(RoleManager.ROLE_NOTES), + any(UserHandle::class.java) + ) + ) + .thenReturn(listOf("com.google.test.notes")) } @After @@ -85,6 +117,9 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { keyguardMonitor = mock(), lazyRepository = { repository }, isEnabled = isEnabled, + backgroundExecutor = FakeExecutor(FakeSystemClock()), + roleManager = roleManager, + noteTaskInfoResolver = NoteTaskInfoResolver(roleManager, packageManager) ) private fun createLockScreenStateVisible(): LockScreenState = @@ -112,6 +147,27 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { } @Test + fun lockScreenState_stylusUsed_userUnlocked_isSelected_noDefaultNotesAppSet_shouldEmitHidden() = + runTest { + TestConfig() + .setStylusEverUsed(true) + .setUserUnlocked(true) + .setConfigSelections(mock<NoteTaskQuickAffordanceConfig>()) + whenever( + roleManager.getRoleHoldersAsUser( + eq(RoleManager.ROLE_NOTES), + any(UserHandle::class.java) + ) + ) + .thenReturn(emptyList()) + + val underTest = createUnderTest() + val actual by collectLastValue(underTest.lockScreenState) + + assertThat(actual).isEqualTo(LockScreenState.Hidden) + } + + @Test fun lockScreenState_stylusUnused_userUnlocked_isSelected_shouldEmitHidden() = runTest { TestConfig() .setStylusEverUsed(false) @@ -217,6 +273,39 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { verify(controller).showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE) } + // region getPickerScreenState + @Test + fun getPickerScreenState_defaultNoteAppSet_shouldReturnDefault() = runTest { + val underTest = createUnderTest(isEnabled = true) + + assertThat(underTest.getPickerScreenState()) + .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default()) + } + + @Test + fun getPickerScreenState_nodefaultNoteAppSet_shouldReturnDisable() = runTest { + val underTest = createUnderTest(isEnabled = true) + whenever( + roleManager.getRoleHoldersAsUser( + eq(RoleManager.ROLE_NOTES), + any(UserHandle::class.java) + ) + ) + .thenReturn(emptyList()) + + assertThat(underTest.getPickerScreenState()) + .isEqualTo( + KeyguardQuickAffordanceConfig.PickerScreenState.Disabled( + listOf("Select a default notes app to use notetaking shortcut"), + actionText = "Open settings", + actionComponentName = + "${context.packageName}$COMPONENT_NAME_SEPARATOR" + + "$ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE" + ) + ) + } + // endregion + private inner class TestConfig { fun setStylusEverUsed(value: Boolean) = also { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java index 88d7e9cb4de4..2dc78a323f55 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java @@ -587,14 +587,14 @@ public class QSSecurityFooterTest extends SysuiTestCase { assertEquals(addLink(mContext.getString(R.string.monitoring_description_two_named_vpns, VPN_PACKAGE, VPN_PACKAGE_2)), mFooterUtils.getVpnMessage(false, true, VPN_PACKAGE, VPN_PACKAGE_2)); - assertEquals(addLink(mContext.getString(R.string.monitoring_description_named_vpn, - VPN_PACKAGE)), + assertEquals(addLink(mContext.getString( + R.string.monitoring_description_managed_device_named_vpn, VPN_PACKAGE)), mFooterUtils.getVpnMessage(true, false, VPN_PACKAGE, null)); assertEquals(addLink(mContext.getString(R.string.monitoring_description_named_vpn, VPN_PACKAGE)), mFooterUtils.getVpnMessage(false, false, VPN_PACKAGE, null)); - assertEquals(addLink(mContext.getString(R.string.monitoring_description_named_vpn, - VPN_PACKAGE_2)), + assertEquals(addLink(mContext.getString( + R.string.monitoring_description_managed_device_named_vpn, VPN_PACKAGE_2)), mFooterUtils.getVpnMessage(true, true, null, VPN_PACKAGE_2)); assertEquals(addLink(mContext.getString( R.string.monitoring_description_managed_profile_named_vpn, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 1edc63c4417e..9a8ec88e992a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -151,6 +151,7 @@ import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.KeyguardStatusBarView; import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController; +import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.ScrimController; @@ -158,6 +159,7 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; import com.android.systemui.statusbar.phone.TapAgainViewController; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; +import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -239,6 +241,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock protected KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent; @Mock protected KeyguardClockSwitchController mKeyguardClockSwitchController; @Mock protected KeyguardStatusBarViewController mKeyguardStatusBarViewController; + @Mock protected LightBarController mLightBarController; @Mock protected NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; @Mock protected NotificationShadeDepthController mNotificationShadeDepthController; @@ -306,6 +309,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock protected ActivityStarter mActivityStarter; @Mock protected KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor; @Mock protected ShadeRepository mShadeRepository; + @Mock private CastController mCastController; protected final int mMaxUdfpsBurnInOffsetY = 5; protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; @@ -653,6 +657,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mNotificationRemoteInputManager, mShadeExpansionStateManager, mStatusBarKeyguardViewManager, + mLightBarController, mNotificationStackScrollLayoutController, mLockscreenShadeTransitionController, mNotificationShadeDepthController, @@ -675,7 +680,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mInteractionJankMonitor, mShadeLog, mKeyguardFaceAuthInteractor, - mShadeRepository + mShadeRepository, + mCastController ); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java index 8a9161e6d46d..1cf38732fdb2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java @@ -83,10 +83,12 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.KeyguardStatusBarView; +import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; +import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.KeyguardStateController; import dagger.Lazy; @@ -132,6 +134,7 @@ public class QuickSettingsControllerTest extends SysuiTestCase { @Mock private PulseExpansionHandler mPulseExpansionHandler; @Mock private NotificationRemoteInputManager mNotificationRemoteInputManager; @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + @Mock private LightBarController mLightBarController; @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController; @Mock private NotificationShadeDepthController mNotificationShadeDepthController; @@ -155,6 +158,7 @@ public class QuickSettingsControllerTest extends SysuiTestCase { @Mock private ShadeLogger mShadeLogger; @Mock private DumpManager mDumpManager; @Mock private UiEventLogger mUiEventLogger; + @Mock private CastController mCastController; private SysuiStatusBarStateController mStatusBarStateController; @@ -221,6 +225,7 @@ public class QuickSettingsControllerTest extends SysuiTestCase { mNotificationRemoteInputManager, mShadeExpansionStateManager, mStatusBarKeyguardViewManager, + mLightBarController, mNotificationStackScrollLayoutController, mLockscreenShadeTransitionController, mNotificationShadeDepthController, @@ -243,7 +248,8 @@ public class QuickSettingsControllerTest extends SysuiTestCase { mInteractionJankMonitor, mShadeLogger, mock(KeyguardFaceAuthInteractor.class), - mock(ShadeRepository.class) + mock(ShadeRepository.class), + mCastController ); mFragmentListener = mQsController.getQsFragmentListener(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt index beaf3009125b..c49f179c925e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt @@ -356,6 +356,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { @Test fun ignoreShadeBlurUntilHidden_schedulesFrame() { notificationShadeDepthController.blursDisabledForAppLaunch = true + verify(blurUtils).prepareBlur(any(), anyInt()) verify(choreographer) .postFrameCallback(eq(notificationShadeDepthController.updateBlurCallback)) } @@ -419,6 +420,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { notificationShadeDepthController.updateBlurCallback.doFrame(0) verify(notificationShadeWindowController).setBackgroundBlurRadius(eq(0)) verify(wallpaperController).setNotificationShadeZoom(eq(1f)) + verify(blurUtils).prepareBlur(any(), eq(0)) verify(blurUtils).applyBlur(eq(viewRootImpl), eq(0), eq(false)) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java index 529519a6246e..a50155628027 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java @@ -22,21 +22,31 @@ import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARE import static junit.framework.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.graphics.Color; import android.graphics.Rect; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.annotation.ColorInt; import androidx.test.filters.SmallTest; +import com.android.internal.colorextraction.ColorExtractor.GradientColors; +import com.android.internal.util.ContrastColorUtil; import com.android.internal.view.AppearanceRegion; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FakeFeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.statusbar.policy.BatteryController; @@ -53,13 +63,25 @@ import java.util.ArrayList; @TestableLooper.RunWithLooper public class LightBarControllerTest extends SysuiTestCase { + private static final GradientColors COLORS_LIGHT = makeColors(Color.WHITE); + private static final GradientColors COLORS_DARK = makeColors(Color.BLACK); + private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); private LightBarTransitionsController mLightBarTransitionsController; + private LightBarTransitionsController mNavBarController; private SysuiDarkIconDispatcher mStatusBarIconController; private LightBarController mLightBarController; + /** Allow testing with NEW_LIGHT_BAR_LOGIC flag in different states */ + protected boolean testNewLightBarLogic() { + return false; + } + @Before public void setup() { + mFeatureFlags.set(Flags.NEW_LIGHT_BAR_LOGIC, testNewLightBarLogic()); mStatusBarIconController = mock(SysuiDarkIconDispatcher.class); + mNavBarController = mock(LightBarTransitionsController.class); + when(mNavBarController.supportsIconTintForNavMode(anyInt())).thenReturn(true); mLightBarTransitionsController = mock(LightBarTransitionsController.class); when(mStatusBarIconController.getTransitionsController()).thenReturn( mLightBarTransitionsController); @@ -68,10 +90,19 @@ public class LightBarControllerTest extends SysuiTestCase { mStatusBarIconController, mock(BatteryController.class), mock(NavigationModeController.class), + mFeatureFlags, mock(DumpManager.class), new FakeDisplayTracker(mContext)); } + private static GradientColors makeColors(@ColorInt int bgColor) { + GradientColors colors = new GradientColors(); + colors.setMainColor(bgColor); + colors.setSecondaryColor(bgColor); + colors.setSupportsDarkText(!ContrastColorUtil.isColorDark(bgColor)); + return colors; + } + @Test public void testOnStatusBarAppearanceChanged_multipleStacks_allStacksLight() { final Rect firstBounds = new Rect(0, 0, 1, 1); @@ -177,4 +208,54 @@ public class LightBarControllerTest extends SysuiTestCase { false /* navbarColorManagedByIme */); verify(mLightBarTransitionsController).setIconsDark(eq(false), anyBoolean()); } + + @Test + public void validateNavBarChangesUpdateIcons() { + assumeTrue(testNewLightBarLogic()); // Only run in the new suite + + // On the launcher in dark mode buttons are light + mLightBarController.setScrimState(ScrimState.UNLOCKED, 0f, COLORS_DARK); + mLightBarController.onNavigationBarAppearanceChanged( + 0, /* nbModeChanged = */ true, + MODE_TRANSPARENT, /* navbarColorManagedByIme = */ false); + verifyNavBarIconsUnchanged(); // no changes yet; not attached + + // Initial state is set when controller is set + mLightBarController.setNavigationBar(mNavBarController); + verifyNavBarIconsDarkSetTo(false); + + // Changing the color of the transparent scrim has no effect + mLightBarController.setScrimState(ScrimState.UNLOCKED, 0f, COLORS_LIGHT); + verifyNavBarIconsUnchanged(); // still light + + // Showing the notification shade with white scrim requires dark icons + mLightBarController.setScrimState(ScrimState.UNLOCKED, 1f, COLORS_LIGHT); + verifyNavBarIconsDarkSetTo(true); + + // Expanded QS always provides a black background, so icons become light again + mLightBarController.setQsExpanded(true); + verifyNavBarIconsDarkSetTo(false); + + // Tapping the QS tile to change to dark theme has no effect in this state + mLightBarController.setScrimState(ScrimState.UNLOCKED, 1f, COLORS_DARK); + verifyNavBarIconsUnchanged(); // still light + + // collapsing QS in dark mode doesn't affect button color + mLightBarController.setQsExpanded(false); + verifyNavBarIconsUnchanged(); // still light + + // Closing the shade has no affect + mLightBarController.setScrimState(ScrimState.UNLOCKED, 0f, COLORS_DARK); + verifyNavBarIconsUnchanged(); // still light + } + + private void verifyNavBarIconsUnchanged() { + verify(mNavBarController, never()).setIconsDark(anyBoolean(), anyBoolean()); + } + + private void verifyNavBarIconsDarkSetTo(boolean iconsDark) { + verify(mNavBarController).setIconsDark(eq(iconsDark), anyBoolean()); + verify(mNavBarController, never()).setIconsDark(eq(!iconsDark), anyBoolean()); + clearInvocations(mNavBarController); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerWithNewLogicTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerWithNewLogicTest.kt new file mode 100644 index 000000000000..d9c2cfa5ea7f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerWithNewLogicTest.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.phone + +import androidx.test.filters.SmallTest +import com.android.systemui.flags.Flags.NEW_LIGHT_BAR_LOGIC + +/** + * This file only needs to live as long as [NEW_LIGHT_BAR_LOGIC] does. When we delete that flag, we + * can roll this back into the old test. + */ +@SmallTest +class LightBarControllerWithNewLogicTest : LightBarControllerTest() { + override fun testNewLightBarLogic(): Boolean = true +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt index b80b825d87dc..c282c1ef0cf6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt @@ -21,6 +21,8 @@ import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT +import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue @@ -49,7 +51,7 @@ class NotificationIconContainerTest : SysuiTestCase() { fun calculateWidthFor_oneIcon_widthForOneIcon() { iconContainer.setActualPaddingStart(10f) iconContainer.setActualPaddingEnd(10f) - iconContainer.setIconSize(10); + iconContainer.setIconSize(10) assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 1f), /* actual= */ 30f) @@ -59,7 +61,7 @@ class NotificationIconContainerTest : SysuiTestCase() { fun calculateWidthFor_fourIcons_widthForFourIcons() { iconContainer.setActualPaddingStart(10f) iconContainer.setActualPaddingEnd(10f) - iconContainer.setIconSize(10); + iconContainer.setIconSize(10) assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 4f), /* actual= */ 60f) @@ -69,7 +71,7 @@ class NotificationIconContainerTest : SysuiTestCase() { fun calculateWidthFor_fiveIcons_widthForFourIcons() { iconContainer.setActualPaddingStart(10f) iconContainer.setActualPaddingEnd(10f) - iconContainer.setIconSize(10); + iconContainer.setIconSize(10) assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 5f), /* actual= */ 60f) } @@ -78,7 +80,7 @@ class NotificationIconContainerTest : SysuiTestCase() { fun calculateIconXTranslations_shortShelfOneIcon_atCorrectXWithoutOverflowDot() { iconContainer.setActualPaddingStart(10f) iconContainer.setActualPaddingEnd(10f) - iconContainer.setIconSize(10); + iconContainer.setIconSize(10) val icon = mockStatusBarIcon() iconContainer.addView(icon) @@ -99,7 +101,7 @@ class NotificationIconContainerTest : SysuiTestCase() { fun calculateIconXTranslations_shortShelfFourIcons_atCorrectXWithoutOverflowDot() { iconContainer.setActualPaddingStart(10f) iconContainer.setActualPaddingEnd(10f) - iconContainer.setIconSize(10); + iconContainer.setIconSize(10) val iconOne = mockStatusBarIcon() val iconTwo = mockStatusBarIcon() @@ -128,7 +130,7 @@ class NotificationIconContainerTest : SysuiTestCase() { fun calculateIconXTranslations_shortShelfFiveIcons_atCorrectXWithOverflowDot() { iconContainer.setActualPaddingStart(10f) iconContainer.setActualPaddingEnd(10f) - iconContainer.setIconSize(10); + iconContainer.setIconSize(10) val iconOne = mockStatusBarIcon() val iconTwo = mockStatusBarIcon() @@ -154,6 +156,55 @@ class NotificationIconContainerTest : SysuiTestCase() { } @Test + fun calculateIconXTranslations_givenWidthEnoughForThreeIcons_atCorrectXWithoutOverflowDot() { + iconContainer.setActualPaddingStart(0f) + iconContainer.setActualPaddingEnd(0f) + iconContainer.setActualLayoutWidth(30) + iconContainer.setIconSize(10) + + val iconOne = mockStatusBarIcon() + val iconTwo = mockStatusBarIcon() + val iconThree = mockStatusBarIcon() + + iconContainer.addView(iconOne) + iconContainer.addView(iconTwo) + iconContainer.addView(iconThree) + assertEquals(3, iconContainer.childCount) + + iconContainer.calculateIconXTranslations() + assertEquals(0f, iconContainer.getIconState(iconOne).xTranslation) + assertEquals(10f, iconContainer.getIconState(iconTwo).xTranslation) + assertEquals(20f, iconContainer.getIconState(iconThree).xTranslation) + assertFalse(iconContainer.areIconsOverflowing()) + } + + @Test + fun calculateIconXTranslations_givenWidthNotEnoughForFourIcons_atCorrectXWithOverflowDot() { + iconContainer.setActualPaddingStart(0f) + iconContainer.setActualPaddingEnd(0f) + iconContainer.setActualLayoutWidth(35) + iconContainer.setIconSize(10) + + val iconOne = mockStatusBarIcon() + val iconTwo = mockStatusBarIcon() + val iconThree = mockStatusBarIcon() + val iconFour = mockStatusBarIcon() + + iconContainer.addView(iconOne) + iconContainer.addView(iconTwo) + iconContainer.addView(iconThree) + iconContainer.addView(iconFour) + assertEquals(4, iconContainer.childCount) + + iconContainer.calculateIconXTranslations() + assertEquals(0f, iconContainer.getIconState(iconOne).xTranslation) + assertEquals(10f, iconContainer.getIconState(iconTwo).xTranslation) + assertEquals(STATE_DOT, iconContainer.getIconState(iconThree).visibleState) + assertEquals(STATE_HIDDEN, iconContainer.getIconState(iconFour).visibleState) + assertTrue(iconContainer.areIconsOverflowing()) + } + + @Test fun shouldForceOverflow_appearingAboveSpeedBump_true() { val forceOverflow = iconContainer.shouldForceOverflow( /* i= */ 1, @@ -161,7 +212,7 @@ class NotificationIconContainerTest : SysuiTestCase() { /* iconAppearAmount= */ 1f, /* maxVisibleIcons= */ 5 ) - assertTrue(forceOverflow); + assertTrue(forceOverflow) } @Test @@ -172,7 +223,7 @@ class NotificationIconContainerTest : SysuiTestCase() { /* iconAppearAmount= */ 0f, /* maxVisibleIcons= */ 5 ) - assertTrue(forceOverflow); + assertTrue(forceOverflow) } @Test @@ -183,7 +234,7 @@ class NotificationIconContainerTest : SysuiTestCase() { /* iconAppearAmount= */ 0f, /* maxVisibleIcons= */ 5 ) - assertFalse(forceOverflow); + assertFalse(forceOverflow) } @Test @@ -210,6 +261,17 @@ class NotificationIconContainerTest : SysuiTestCase() { } @Test + fun isOverflowing_lastChildXGreaterThanDotX_true() { + val isOverflowing = iconContainer.isOverflowing( + /* isLastChild= */ true, + /* translationX= */ 9f, + /* layoutEnd= */ 10f, + /* iconSize= */ 2f, + ) + assertTrue(isOverflowing) + } + + @Test fun isOverflowing_lastChildXGreaterThanLayoutEnd_true() { val isOverflowing = iconContainer.isOverflowing( /* isLastChild= */ true, @@ -253,7 +315,7 @@ class NotificationIconContainerTest : SysuiTestCase() { assertTrue(isOverflowing) } - private fun mockStatusBarIcon() : StatusBarIconView { + private fun mockStatusBarIcon(): StatusBarIconView { val iconView = mock(StatusBarIconView::class.java) whenever(iconView.width).thenReturn(10) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java index db50163ce6a7..b8e4306a319d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java @@ -1,6 +1,7 @@ package com.android.systemui.statusbar.policy; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -125,4 +126,20 @@ public class CastControllerImplTest extends SysuiTestCase { fail("Concurrent modification exception"); } } + + @Test + public void hasConnectedCastDevice_connected() { + CastController.CastDevice castDevice = new CastController.CastDevice(); + castDevice.state = CastController.CastDevice.STATE_CONNECTED; + mController.startCasting(castDevice); + assertTrue(mController.hasConnectedCastDevice()); + } + + @Test + public void hasConnectedCastDevice_notConnected() { + CastController.CastDevice castDevice = new CastController.CastDevice(); + castDevice.state = CastController.CastDevice.STATE_CONNECTING; + mController.startCasting(castDevice); + assertTrue(mController.hasConnectedCastDevice()); + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeRearDisplayStateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeRearDisplayStateRepository.kt new file mode 100644 index 000000000000..fd9139165c85 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeRearDisplayStateRepository.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.biometrics.data.repository + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeRearDisplayStateRepository : RearDisplayStateRepository { + private val _isInRearDisplayMode = MutableStateFlow<Boolean>(false) + override val isInRearDisplayMode: StateFlow<Boolean> = _isInRearDisplayMode.asStateFlow() + + fun setIsInRearDisplayMode(isInRearDisplayMode: Boolean) { + _isInRearDisplayMode.value = isInRearDisplayMode + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java index f6b24da9b821..84ace7cc9540 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java @@ -51,4 +51,9 @@ public class FakeCastController extends BaseLeakChecker<Callback> implements Cas public void stopCasting(CastDevice device) { } + + @Override + public boolean hasConnectedCastDevice() { + return false; + } } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index a3a067481022..0172eaf03fa7 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -54,7 +54,6 @@ import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.NotificationManager; import android.app.PendingIntent; -import android.bluetooth.BluetoothDevice; import android.companion.AssociationInfo; import android.companion.AssociationRequest; import android.companion.DeviceNotAssociatedException; @@ -108,6 +107,9 @@ import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.companion.datatransfer.SystemDataTransferProcessor; import com.android.server.companion.datatransfer.SystemDataTransferRequestStore; +import com.android.server.companion.datatransfer.contextsync.CrossDeviceCall; +import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController; +import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncControllerCallback; import com.android.server.companion.presence.CompanionDevicePresenceMonitor; import com.android.server.companion.transport.CompanionTransportManager; import com.android.server.pm.UserManagerInternal; @@ -117,6 +119,7 @@ import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -200,6 +203,8 @@ public class CompanionDeviceManagerService extends SystemService { private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners = new RemoteCallbackList<>(); + private CrossDeviceSyncController mCrossDeviceSyncController; + public CompanionDeviceManagerService(Context context) { super(context); @@ -229,7 +234,7 @@ public class CompanionDeviceManagerService extends SystemService { loadAssociationsFromDisk(); mAssociationStore.registerListener(mAssociationStoreChangeListener); - mDevicePresenceMonitor = new CompanionDevicePresenceMonitor(mUserManager, + mDevicePresenceMonitor = new CompanionDevicePresenceMonitor( mAssociationStore, mDevicePresenceCallback); mAssociationRequestsProcessor = new AssociationRequestsProcessor( @@ -239,6 +244,8 @@ public class CompanionDeviceManagerService extends SystemService { mTransportManager = new CompanionTransportManager(context, mAssociationStore); mSystemDataTransferProcessor = new SystemDataTransferProcessor(this, mAssociationStore, mSystemDataTransferRequestStore, mTransportManager); + // TODO(b/279663946): move context sync to a dedicated system service + mCrossDeviceSyncController = new CrossDeviceSyncController(getContext(), mTransportManager); // Publish "binder" service. final CompanionDeviceManagerImpl impl = new CompanionDeviceManagerImpl(); @@ -315,21 +322,6 @@ public class CompanionDeviceManagerService extends SystemService { MINUTES.toMillis(10)); } - @Override - public void onUserUnlocked(@NonNull TargetUser user) { - // Notify and bind the app after the phone is unlocked. - final int userId = user.getUserIdentifier(); - final Set<BluetoothDevice> blueToothDevices = - mDevicePresenceMonitor.getPendingReportConnectedDevices().get(userId); - for (BluetoothDevice bluetoothDevice : blueToothDevices) { - for (AssociationInfo ai: - mAssociationStore.getAssociationsByAddress(bluetoothDevice.getAddress())) { - Slog.i(TAG, "onUserUnlocked, device id( " + ai.getId() + " ) is connected"); - mDevicePresenceMonitor.onBluetoothCompanionDeviceConnected(ai.getId()); - } - } - } - @NonNull AssociationInfo getAssociationWithCallerChecks( @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) { @@ -1369,6 +1361,39 @@ public class CompanionDeviceManagerService extends SystemService { public void removeInactiveSelfManagedAssociations() { CompanionDeviceManagerService.this.removeInactiveSelfManagedAssociations(); } + + @Override + public void registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback) { + if (CompanionDeviceConfig.isEnabled( + CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) { + mCrossDeviceSyncController.registerCallMetadataSyncCallback(callback); + } + } + + @Override + public void crossDeviceSync(int userId, Collection<CrossDeviceCall> calls) { + if (CompanionDeviceConfig.isEnabled( + CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) { + mCrossDeviceSyncController.syncToAllDevicesForUserId(userId, calls); + } + } + + @Override + public void crossDeviceSync(AssociationInfo associationInfo, + Collection<CrossDeviceCall> calls) { + if (CompanionDeviceConfig.isEnabled( + CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) { + mCrossDeviceSyncController.syncToSingleDevice(associationInfo, calls); + } + } + + @Override + public void sendCrossDeviceSyncMessage(int associationId, byte[] message) { + if (CompanionDeviceConfig.isEnabled( + CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) { + mCrossDeviceSyncController.syncMessageToDevice(associationId, message); + } + } } /** diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java index 36492407d463..3b108e63e13d 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java @@ -16,12 +16,41 @@ package com.android.server.companion; +import android.companion.AssociationInfo; + +import com.android.server.companion.datatransfer.contextsync.CrossDeviceCall; +import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncControllerCallback; + +import java.util.Collection; + /** * Companion Device Manager Local System Service Interface. */ -interface CompanionDeviceManagerServiceInternal { +public interface CompanionDeviceManagerServiceInternal { /** * @see CompanionDeviceManagerService#removeInactiveSelfManagedAssociations */ void removeInactiveSelfManagedAssociations(); + + /** + * Registers a callback from an InCallService / ConnectionService to CDM to process sync + * requests and perform call control actions. + */ + void registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback); + + /** + * Requests a sync from an InCallService / ConnectionService to CDM, for the given association + * and message. + */ + void sendCrossDeviceSyncMessage(int associationId, byte[] message); + + /** + * Requests a sync from an InCallService to CDM, for the given user and call metadata. + */ + void crossDeviceSync(int userId, Collection<CrossDeviceCall> calls); + + /** + * Requests a sync from an InCallService to CDM, for the given association and call metadata. + */ + void crossDeviceSync(AssociationInfo associationInfo, Collection<CrossDeviceCall> calls); } diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java index ae4766ac9fda..443a732eb6f1 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java +++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java @@ -17,15 +17,20 @@ package com.android.server.companion.datatransfer.contextsync; import android.annotation.Nullable; +import android.companion.AssociationInfo; import android.telecom.Call; import android.telecom.InCallService; import android.telecom.TelecomManager; +import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; import com.android.server.companion.CompanionDeviceConfig; +import com.android.server.companion.CompanionDeviceManagerServiceInternal; import java.util.Collection; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import java.util.stream.Collectors; @@ -35,90 +40,132 @@ import java.util.stream.Collectors; */ public class CallMetadataSyncInCallService extends InCallService { + private static final String TAG = "CallMetadataIcs"; private static final long NOT_VALID = -1L; + private CompanionDeviceManagerServiceInternal mCdmsi; + @VisibleForTesting final Map<Call, CrossDeviceCall> mCurrentCalls = new HashMap<>(); - @VisibleForTesting - boolean mShouldSync; + @VisibleForTesting int mNumberOfActiveSyncAssociations; final Call.Callback mTelecomCallback = new Call.Callback() { @Override public void onDetailsChanged(Call call, Call.Details details) { - mCurrentCalls.get(call).updateCallDetails(details); + if (mNumberOfActiveSyncAssociations > 0) { + final CrossDeviceCall crossDeviceCall = mCurrentCalls.get(call); + if (crossDeviceCall != null) { + crossDeviceCall.updateCallDetails(details); + sync(getUserId()); + } else { + Slog.w(TAG, "Could not update details for nonexistent call"); + } + } } }; - final CallMetadataSyncCallback mCallMetadataSyncCallback = new CallMetadataSyncCallback() { - @Override - void processCallControlAction(int crossDeviceCallId, int callControlAction) { - final CrossDeviceCall crossDeviceCall = getCallForId(crossDeviceCallId, - mCurrentCalls.values()); - switch (callControlAction) { - case android.companion.Telecom.Call.ACCEPT: - if (crossDeviceCall != null) { - crossDeviceCall.doAccept(); - } - break; - case android.companion.Telecom.Call.REJECT: - if (crossDeviceCall != null) { - crossDeviceCall.doReject(); + final CrossDeviceSyncControllerCallback + mCrossDeviceSyncControllerCallback = new CrossDeviceSyncControllerCallback() { + @Override + void processContextSyncMessage(int associationId, + CallMetadataSyncData callMetadataSyncData) { + final Iterator<CallMetadataSyncData.Call> iterator = + callMetadataSyncData.getRequests().iterator(); + while (iterator.hasNext()) { + final CallMetadataSyncData.Call call = iterator.next(); + if (call.getId() != 0) { + // The call is already assigned an id; treat as control invocations. + for (int control : call.getControls()) { + processCallControlAction(call.getId(), control); + } + } + iterator.remove(); } - break; - case android.companion.Telecom.Call.SILENCE: - doSilence(); - break; - case android.companion.Telecom.Call.MUTE: - doMute(); - break; - case android.companion.Telecom.Call.UNMUTE: - doUnmute(); - break; - case android.companion.Telecom.Call.END: - if (crossDeviceCall != null) { - crossDeviceCall.doEnd(); - } - break; - case android.companion.Telecom.Call.PUT_ON_HOLD: - if (crossDeviceCall != null) { - crossDeviceCall.doPutOnHold(); - } - break; - case android.companion.Telecom.Call.TAKE_OFF_HOLD: - if (crossDeviceCall != null) { - crossDeviceCall.doTakeOffHold(); + } + + private void processCallControlAction(long crossDeviceCallId, + int callControlAction) { + final CrossDeviceCall crossDeviceCall = getCallForId(crossDeviceCallId, + mCurrentCalls.values()); + switch (callControlAction) { + case android.companion.Telecom.Call.ACCEPT: + if (crossDeviceCall != null) { + crossDeviceCall.doAccept(); + } + break; + case android.companion.Telecom.Call.REJECT: + if (crossDeviceCall != null) { + crossDeviceCall.doReject(); + } + break; + case android.companion.Telecom.Call.SILENCE: + doSilence(); + break; + case android.companion.Telecom.Call.MUTE: + doMute(); + break; + case android.companion.Telecom.Call.UNMUTE: + doUnmute(); + break; + case android.companion.Telecom.Call.END: + if (crossDeviceCall != null) { + crossDeviceCall.doEnd(); + } + break; + case android.companion.Telecom.Call.PUT_ON_HOLD: + if (crossDeviceCall != null) { + crossDeviceCall.doPutOnHold(); + } + break; + case android.companion.Telecom.Call.TAKE_OFF_HOLD: + if (crossDeviceCall != null) { + crossDeviceCall.doTakeOffHold(); + } + break; + default: } - break; - default: - } - } + } - @Override - void requestCrossDeviceSync(int userId) { - } + @Override + void requestCrossDeviceSync(AssociationInfo associationInfo) { + if (associationInfo.getUserId() == getUserId()) { + sync(associationInfo); + } + } - @Override - void updateStatus(int userId, boolean shouldSyncCallMetadata) { - if (userId == getUserId()) { - mShouldSync = shouldSyncCallMetadata; - if (shouldSyncCallMetadata) { - initializeCalls(); - } else { - mCurrentCalls.clear(); + @Override + void updateNumberOfActiveSyncAssociations(int userId, boolean added) { + if (userId == getUserId()) { + final boolean wasActivelySyncing = mNumberOfActiveSyncAssociations > 0; + if (added) { + mNumberOfActiveSyncAssociations++; + } else { + mNumberOfActiveSyncAssociations--; + } + if (!wasActivelySyncing && mNumberOfActiveSyncAssociations > 0) { + initializeCalls(); + } else if (wasActivelySyncing && mNumberOfActiveSyncAssociations <= 0) { + mCurrentCalls.clear(); + } + } } - } - } }; @Override public void onCreate() { super.onCreate(); - initializeCalls(); + if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) { + mCdmsi = LocalServices.getService(CompanionDeviceManagerServiceInternal.class); + mCdmsi.registerCallMetadataSyncCallback(mCrossDeviceSyncControllerCallback); + } } private void initializeCalls() { if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM) - && mShouldSync) { + && mNumberOfActiveSyncAssociations > 0) { mCurrentCalls.putAll(getCalls().stream().collect(Collectors.toMap(call -> call, call -> new CrossDeviceCall(getPackageManager(), call, getCallAudioState())))); + mCurrentCalls.keySet().forEach(call -> call.registerCallback(mTelecomCallback, + getMainThreadHandler())); + sync(getUserId()); } } @@ -139,33 +186,39 @@ public class CallMetadataSyncInCallService extends InCallService { @Override public void onCallAdded(Call call) { if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM) - && mShouldSync) { + && mNumberOfActiveSyncAssociations > 0) { mCurrentCalls.put(call, new CrossDeviceCall(getPackageManager(), call, getCallAudioState())); + call.registerCallback(mTelecomCallback); + sync(getUserId()); } } @Override public void onCallRemoved(Call call) { if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM) - && mShouldSync) { + && mNumberOfActiveSyncAssociations > 0) { mCurrentCalls.remove(call); + call.unregisterCallback(mTelecomCallback); + sync(getUserId()); } } @Override public void onMuteStateChanged(boolean isMuted) { if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM) - && mShouldSync) { + && mNumberOfActiveSyncAssociations > 0) { mCurrentCalls.values().forEach(call -> call.updateMuted(isMuted)); + sync(getUserId()); } } @Override public void onSilenceRinger() { if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM) - && mShouldSync) { + && mNumberOfActiveSyncAssociations > 0) { mCurrentCalls.values().forEach(call -> call.updateSilencedIfRinging()); + sync(getUserId()); } } @@ -183,4 +236,12 @@ public class CallMetadataSyncInCallService extends InCallService { telecomManager.silenceRinger(); } } + + private void sync(int userId) { + mCdmsi.crossDeviceSync(userId, mCurrentCalls.values()); + } + + private void sync(AssociationInfo associationInfo) { + mCdmsi.crossDeviceSync(associationInfo, mCurrentCalls.values()); + } }
\ No newline at end of file diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java index 3d8fb7a8d5bf..adc5faf24f2c 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java +++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java @@ -16,27 +16,32 @@ package com.android.server.companion.datatransfer.contextsync; +import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_CONTEXT_SYNC; + import android.app.admin.DevicePolicyManager; import android.companion.AssociationInfo; import android.companion.ContextSyncMessage; +import android.companion.IOnMessageReceivedListener; +import android.companion.IOnTransportsChangedListener; import android.companion.Telecom; -import android.companion.Telecom.Call; import android.content.Context; +import android.os.Binder; import android.os.UserHandle; -import android.util.Pair; import android.util.Slog; +import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; +import android.util.proto.ProtoParseException; +import android.util.proto.ProtoUtils; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.companion.CompanionDeviceConfig; +import com.android.server.companion.transport.CompanionTransportManager; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; +import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; /** @@ -45,149 +50,308 @@ import java.util.Set; public class CrossDeviceSyncController { private static final String TAG = "CrossDeviceSyncController"; - private static final int BYTE_ARRAY_SIZE = 64; + + private static final int VERSION_1 = 1; + private static final int CURRENT_VERSION = VERSION_1; private final Context mContext; - private final Callback mCdmCallback; - private final Map<Integer, List<AssociationInfo>> mUserIdToAssociationInfo = new HashMap<>(); - private final Map<Integer, Pair<InputStream, OutputStream>> mAssociationIdToStreams = - new HashMap<>(); + private final CompanionTransportManager mCompanionTransportManager; + private final List<AssociationInfo> mConnectedAssociations = new ArrayList<>(); private final Set<Integer> mBlocklist = new HashSet<>(); - private CallMetadataSyncCallback mInCallServiceCallMetadataSyncCallback; + private CrossDeviceSyncControllerCallback mCrossDeviceSyncControllerCallback; - public CrossDeviceSyncController(Context context, Callback callback) { + public CrossDeviceSyncController(Context context, + CompanionTransportManager companionTransportManager) { mContext = context; - mCdmCallback = callback; + mCompanionTransportManager = companionTransportManager; + mCompanionTransportManager.addListener(new IOnTransportsChangedListener.Stub() { + @Override + public void onTransportsChanged(List<AssociationInfo> newAssociations) { + final long token = Binder.clearCallingIdentity(); + try { + if (!CompanionDeviceConfig.isEnabled( + CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) { + return; + } + } finally { + Binder.restoreCallingIdentity(token); + } + final List<AssociationInfo> existingAssociations = new ArrayList<>( + mConnectedAssociations); + mConnectedAssociations.clear(); + mConnectedAssociations.addAll(newAssociations); + + if (mCrossDeviceSyncControllerCallback == null) { + Slog.w(TAG, "No callback to report transports changed"); + return; + } + for (AssociationInfo associationInfo : newAssociations) { + if (!existingAssociations.contains(associationInfo) + && !isAssociationBlocked(associationInfo.getId())) { + mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations( + associationInfo.getUserId(), /* added= */ true); + mCrossDeviceSyncControllerCallback.requestCrossDeviceSync(associationInfo); + } + } + for (AssociationInfo associationInfo : existingAssociations) { + if (!newAssociations.contains(associationInfo)) { + if (isAssociationBlocked(associationInfo.getId())) { + mBlocklist.remove(associationInfo.getId()); + } else { + mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations( + associationInfo.getUserId(), /* added= */ false); + } + } + } + } + }); + mCompanionTransportManager.addListener(MESSAGE_REQUEST_CONTEXT_SYNC, + new IOnMessageReceivedListener.Stub() { + @Override + public void onMessageReceived(int associationId, byte[] data) { + if (mCrossDeviceSyncControllerCallback == null) { + Slog.w(TAG, "No callback to process context sync message"); + return; + } + mCrossDeviceSyncControllerCallback.processContextSyncMessage(associationId, + processTelecomDataFromSync(data)); + } + }); + } + + private boolean isAssociationBlocked(int associationId) { + return mBlocklist.contains(associationId); } /** Registers the call metadata callback. */ - public void registerCallMetadataSyncCallback(CallMetadataSyncCallback callback) { - mInCallServiceCallMetadataSyncCallback = callback; + public void registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback) { + mCrossDeviceSyncControllerCallback = callback; + for (AssociationInfo associationInfo : mConnectedAssociations) { + if (!isAssociationBlocked(associationInfo.getId())) { + mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations( + associationInfo.getUserId(), /* added= */ true); + mCrossDeviceSyncControllerCallback.requestCrossDeviceSync(associationInfo); + } + } } /** Allow specific associated devices to enable / disable syncing. */ public void setSyncEnabled(AssociationInfo associationInfo, boolean enabled) { if (enabled) { - if (mBlocklist.contains(associationInfo.getId())) { + if (isAssociationBlocked(associationInfo.getId())) { mBlocklist.remove(associationInfo.getId()); - openChannel(associationInfo); + mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations( + associationInfo.getUserId(), /* added= */ true); + mCrossDeviceSyncControllerCallback.requestCrossDeviceSync(associationInfo); } } else { - if (!mBlocklist.contains(associationInfo.getId())) { + if (!isAssociationBlocked(associationInfo.getId())) { mBlocklist.add(associationInfo.getId()); - closeChannel(associationInfo); + mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations( + associationInfo.getUserId(), /* added= */ false); + // Send empty message to device to clear its data (otherwise it will get stale) + syncMessageToDevice(associationInfo.getId(), createEmptyMessage()); } } } + private boolean isAdminBlocked(int userId) { + return mContext.getSystemService(DevicePolicyManager.class) + .getBluetoothContactSharingDisabled(UserHandle.of(userId)); + } + /** - * Opens channels to newly associated devices, and closes channels to newly disassociated - * devices. + * Sync data to associated devices. * - * TODO(b/265466098): this needs to be limited to just connected devices + * @param userId The user whose data should be synced. + * @param calls The full list of current calls for all users. */ - public void onAssociationsChanged(int userId, List<AssociationInfo> newAssociationInfoList) { - final List<AssociationInfo> existingAssociationInfoList = mUserIdToAssociationInfo.get( - userId); - // Close channels to newly-disconnected devices. - for (AssociationInfo existingAssociationInfo : existingAssociationInfoList) { - if (!newAssociationInfoList.contains(existingAssociationInfo) && !mBlocklist.contains( - existingAssociationInfo.getId())) { - closeChannel(existingAssociationInfo); + public void syncToAllDevicesForUserId(int userId, Collection<CrossDeviceCall> calls) { + final Set<Integer> associationIds = new HashSet<>(); + for (AssociationInfo associationInfo : mConnectedAssociations) { + if (associationInfo.getUserId() == userId && !isAssociationBlocked( + associationInfo.getId())) { + associationIds.add(associationInfo.getId()); } } - // Open channels to newly-connected devices. - for (AssociationInfo newAssociationInfo : newAssociationInfoList) { - if (!existingAssociationInfoList.contains(newAssociationInfo) && !mBlocklist.contains( - newAssociationInfo.getId())) { - openChannel(newAssociationInfo); - } + if (associationIds.isEmpty()) { + Slog.w(TAG, "No eligible devices to sync to"); + return; } - mUserIdToAssociationInfo.put(userId, newAssociationInfoList); - } - private boolean isAdminBlocked(int userId) { - return mContext.getSystemService(DevicePolicyManager.class) - .getBluetoothContactSharingDisabled(UserHandle.of(userId)); + mCompanionTransportManager.sendMessage(MESSAGE_REQUEST_CONTEXT_SYNC, + createCallUpdateMessage(calls, userId), + associationIds.stream().mapToInt(Integer::intValue).toArray()); } - /** Stop reading, close streams, and close secure channel. */ - private void closeChannel(AssociationInfo associationInfo) { - // TODO(b/265466098): stop reading from secure channel - final Pair<InputStream, OutputStream> streams = mAssociationIdToStreams.get( - associationInfo.getId()); - if (streams != null) { - try { - if (streams.first != null) { - streams.first.close(); - } - if (streams.second != null) { - streams.second.close(); - } - } catch (IOException e) { - Slog.e(TAG, "Could not close streams for association " + associationInfo.getId(), - e); - } + /** + * Sync data to associated devices. + * + * @param associationInfo The association whose data should be synced. + * @param calls The full list of current calls for all users. + */ + public void syncToSingleDevice(AssociationInfo associationInfo, + Collection<CrossDeviceCall> calls) { + if (isAssociationBlocked(associationInfo.getId())) { + Slog.e(TAG, "Cannot sync to requested device; connection is blocked"); + return; } - mCdmCallback.closeSecureChannel(associationInfo.getId()); - } - /** Sync initial snapshot and start reading. */ - private void openChannel(AssociationInfo associationInfo) { - final InputStream is = new ByteArrayInputStream(new byte[BYTE_ARRAY_SIZE]); - final OutputStream os = new ByteArrayOutputStream(BYTE_ARRAY_SIZE); - mAssociationIdToStreams.put(associationInfo.getId(), new Pair<>(is, os)); - mCdmCallback.createSecureChannel(associationInfo.getId(), is, os); - // TODO(b/265466098): only requestSync for this specific association / connection? - mInCallServiceCallMetadataSyncCallback.requestCrossDeviceSync(associationInfo.getUserId()); - // TODO(b/265466098): start reading from secure channel + mCompanionTransportManager.sendMessage(MESSAGE_REQUEST_CONTEXT_SYNC, + createCallUpdateMessage(calls, associationInfo.getUserId()), + new int[]{associationInfo.getId()}); } /** * Sync data to associated devices. * - * @param userId The user whose data should be synced. - * @param calls The full list of current calls for all users. + * @param associationId The association whose data should be synced. + * @param message The message to sync. */ - public void crossDeviceSync(int userId, Collection<CrossDeviceCall> calls) { - final boolean isAdminBlocked = isAdminBlocked(userId); - for (AssociationInfo associationInfo : mUserIdToAssociationInfo.get(userId)) { - final Pair<InputStream, OutputStream> streams = mAssociationIdToStreams.get( - associationInfo.getId()); - final ProtoOutputStream pos = new ProtoOutputStream(streams.second); - final long telecomToken = pos.start(ContextSyncMessage.TELECOM); - for (CrossDeviceCall call : calls) { - final long callsToken = pos.start(Telecom.CALLS); - pos.write(Call.ID, call.getId()); - final long originToken = pos.start(Call.ORIGIN); - pos.write(Call.Origin.CALLER_ID, call.getReadableCallerId(isAdminBlocked)); - pos.write(Call.Origin.APP_ICON, call.getCallingAppIcon()); - pos.write(Call.Origin.APP_NAME, call.getCallingAppName()); - pos.end(originToken); - pos.write(Call.STATUS, call.getStatus()); - for (int control : call.getControls()) { - pos.write(Call.CONTROLS_AVAILABLE, control); + public void syncMessageToDevice(int associationId, byte[] message) { + if (isAssociationBlocked(associationId)) { + Slog.e(TAG, "Cannot sync to requested device; connection is blocked"); + return; + } + + mCompanionTransportManager.sendMessage(MESSAGE_REQUEST_CONTEXT_SYNC, message, + new int[]{associationId}); + } + + @VisibleForTesting + CallMetadataSyncData processTelecomDataFromSync(byte[] data) { + final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData(); + final ProtoInputStream pis = new ProtoInputStream(data); + try { + int version = -1; + while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pis.getFieldNumber()) { + case (int) ContextSyncMessage.VERSION: + version = pis.readInt(ContextSyncMessage.VERSION); + Slog.e(TAG, "Processing context sync message version " + version); + break; + case (int) ContextSyncMessage.TELECOM: + if (version == VERSION_1) { + final long telecomToken = pis.start(ContextSyncMessage.TELECOM); + while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + if (pis.getFieldNumber() == (int) Telecom.CALLS) { + final long callsToken = pis.start(Telecom.CALLS); + callMetadataSyncData.addCall(processCallDataFromSync(pis)); + pis.end(callsToken); + } else if (pis.getFieldNumber() == (int) Telecom.REQUESTS) { + final long requestsToken = pis.start(Telecom.REQUESTS); + callMetadataSyncData.addRequest(processCallDataFromSync(pis)); + pis.end(requestsToken); + } else { + Slog.e(TAG, "Unhandled field in Telecom:" + + ProtoUtils.currentFieldToString(pis)); + } + } + pis.end(telecomToken); + } else { + Slog.e(TAG, "Cannot process unsupported version " + version); + } + break; + default: + Slog.e(TAG, "Unhandled field in ContextSyncMessage:" + + ProtoUtils.currentFieldToString(pis)); } - pos.end(callsToken); } - pos.end(telecomToken); - pos.flush(); + } catch (IOException | ProtoParseException e) { + throw new RuntimeException(e); } + return callMetadataSyncData; } - /** - * Callback to be implemented by CompanionDeviceManagerService. - */ - public interface Callback { - /** - * Create a secure channel to send messages. - */ - void createSecureChannel(int associationId, InputStream input, OutputStream output); - - /** - * Close the secure channel created previously. - */ - void closeSecureChannel(int associationId); + @VisibleForTesting + CallMetadataSyncData.Call processCallDataFromSync(ProtoInputStream pis) throws IOException { + final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call(); + while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pis.getFieldNumber()) { + case (int) Telecom.Call.ID: + call.setId(pis.readLong(Telecom.Call.ID)); + break; + case (int) Telecom.Call.ORIGIN: + final long originToken = pis.start(Telecom.Call.ORIGIN); + while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pis.getFieldNumber()) { + case (int) Telecom.Call.Origin.APP_ICON: + call.setAppIcon(pis.readBytes(Telecom.Call.Origin.APP_ICON)); + break; + case (int) Telecom.Call.Origin.APP_NAME: + call.setAppName(pis.readString(Telecom.Call.Origin.APP_NAME)); + break; + case (int) Telecom.Call.Origin.CALLER_ID: + call.setCallerId(pis.readString(Telecom.Call.Origin.CALLER_ID)); + break; + case (int) Telecom.Call.Origin.APP_IDENTIFIER: + call.setAppIdentifier( + pis.readString(Telecom.Call.Origin.APP_IDENTIFIER)); + break; + default: + Slog.e(TAG, "Unhandled field in Origin:" + + ProtoUtils.currentFieldToString(pis)); + } + } + pis.end(originToken); + break; + case (int) Telecom.Call.STATUS: + call.setStatus(pis.readInt(Telecom.Call.STATUS)); + break; + case (int) Telecom.Call.CONTROLS: + call.addControl(pis.readInt(Telecom.Call.CONTROLS)); + break; + default: + Slog.e(TAG, + "Unhandled field in Telecom:" + ProtoUtils.currentFieldToString(pis)); + } + } + return call; + } + + @VisibleForTesting + byte[] createCallUpdateMessage(Collection<CrossDeviceCall> calls, int userId) { + final ProtoOutputStream pos = new ProtoOutputStream(); + pos.write(ContextSyncMessage.VERSION, CURRENT_VERSION); + final long telecomToken = pos.start(ContextSyncMessage.TELECOM); + for (CrossDeviceCall call : calls) { + final long callsToken = pos.start(Telecom.CALLS); + pos.write(Telecom.Call.ID, call.getId()); + final long originToken = pos.start(Telecom.Call.ORIGIN); + pos.write(Telecom.Call.Origin.CALLER_ID, + call.getReadableCallerId(isAdminBlocked(userId))); + pos.write(Telecom.Call.Origin.APP_ICON, call.getCallingAppIcon()); + pos.write(Telecom.Call.Origin.APP_NAME, call.getCallingAppName()); + pos.write(Telecom.Call.Origin.APP_IDENTIFIER, call.getCallingAppPackageName()); + pos.end(originToken); + pos.write(Telecom.Call.STATUS, call.getStatus()); + for (int control : call.getControls()) { + pos.write(Telecom.Call.CONTROLS, control); + } + pos.end(callsToken); + } + pos.end(telecomToken); + return pos.getBytes(); + } + + /** Create a call control message. */ + public static byte[] createCallControlMessage(long callId, int control) { + final ProtoOutputStream pos = new ProtoOutputStream(); + pos.write(ContextSyncMessage.VERSION, CURRENT_VERSION); + final long telecomToken = pos.start(ContextSyncMessage.TELECOM); + final long requestsToken = pos.start(Telecom.REQUESTS); + pos.write(Telecom.Call.ID, callId); + pos.write(Telecom.Call.CONTROLS, control); + pos.end(requestsToken); + pos.end(telecomToken); + return pos.getBytes(); + } + + /** Create an empty context sync message, used to clear state. */ + public static byte[] createEmptyMessage() { + final ProtoOutputStream pos = new ProtoOutputStream(); + pos.write(ContextSyncMessage.VERSION, CURRENT_VERSION); + return pos.getBytes(); } } diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java index 7c339d213483..31e10a814568 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java +++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java @@ -16,12 +16,14 @@ package com.android.server.companion.datatransfer.contextsync; +import android.companion.AssociationInfo; + /** Callback for call metadata syncing. */ -public abstract class CallMetadataSyncCallback { +public abstract class CrossDeviceSyncControllerCallback { - abstract void processCallControlAction(int crossDeviceCallId, int callControlAction); + void processContextSyncMessage(int associationId, CallMetadataSyncData callMetadataSyncData) {} - abstract void requestCrossDeviceSync(int userId); + void requestCrossDeviceSync(AssociationInfo associationInfo) {} - abstract void updateStatus(int userId, boolean shouldSyncCallMetadata); + void updateNumberOfActiveSyncAssociations(int userId, boolean added) {} } diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java index 82d0325cd749..f6b99b551ecb 100644 --- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java +++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java @@ -27,23 +27,17 @@ import android.companion.AssociationInfo; import android.net.MacAddress; import android.os.Handler; import android.os.HandlerExecutor; -import android.os.UserHandle; -import android.os.UserManager; import android.util.Log; -import android.util.SparseArray; -import com.android.internal.annotations.GuardedBy; import com.android.server.companion.AssociationStore; import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; @SuppressLint("LongLogTag") -public class BluetoothCompanionDeviceConnectionListener +class BluetoothCompanionDeviceConnectionListener extends BluetoothAdapter.BluetoothConnectionCallback implements AssociationStore.OnChangeListener { private static final String TAG = "CDM_BluetoothCompanionDeviceConnectionListener"; @@ -54,23 +48,15 @@ public class BluetoothCompanionDeviceConnectionListener void onBluetoothCompanionDeviceDisconnected(int associationId); } - private final UserManager mUserManager; private final @NonNull AssociationStore mAssociationStore; private final @NonNull Callback mCallback; /** A set of ALL connected BT device (not only companion.) */ private final @NonNull Map<MacAddress, BluetoothDevice> mAllConnectedDevices = new HashMap<>(); - - @GuardedBy("mPendingReportConnectedDevices") - @NonNull - final SparseArray<Set<BluetoothDevice>> mPendingReportConnectedDevices = - new SparseArray<>(); - - BluetoothCompanionDeviceConnectionListener(UserManager userManager, - @NonNull AssociationStore associationStore, @NonNull Callback callback) { + BluetoothCompanionDeviceConnectionListener(@NonNull AssociationStore associationStore, + @NonNull Callback callback) { mAssociationStore = associationStore; mCallback = callback; - mUserManager = userManager; } public void init(@NonNull BluetoothAdapter btAdapter) { @@ -90,32 +76,12 @@ public class BluetoothCompanionDeviceConnectionListener if (DEBUG) Log.i(TAG, "onDevice_Connected() " + btDeviceToString(device)); final MacAddress macAddress = MacAddress.fromString(device.getAddress()); - final int userId = UserHandle.myUserId(); - if (mAllConnectedDevices.put(macAddress, device) != null) { if (DEBUG) Log.w(TAG, "Device " + btDeviceToString(device) + " is already connected."); return; } - // Try to bind and notify the app after the phone is unlocked. - if (!mUserManager.isUserUnlockingOrUnlocked(UserHandle.myUserId())) { - if (DEBUG) { - Log.i(TAG, "Current user is not in unlocking or unlocked stage yet. Notify " - + "the application when the phone is unlocked"); - } - synchronized (mPendingReportConnectedDevices) { - Set<BluetoothDevice> bluetoothDevices = mPendingReportConnectedDevices.get(userId); - - if (bluetoothDevices == null) { - bluetoothDevices = new HashSet<>(); - mPendingReportConnectedDevices.put(userId, bluetoothDevices); - } - - bluetoothDevices.add(device); - } - } else { - onDeviceConnectivityChanged(device, true); - } + onDeviceConnectivityChanged(device, true); } /** @@ -132,8 +98,6 @@ public class BluetoothCompanionDeviceConnectionListener } final MacAddress macAddress = MacAddress.fromString(device.getAddress()); - final int userId = UserHandle.myUserId(); - if (mAllConnectedDevices.remove(macAddress) == null) { if (DEBUG) { Log.w(TAG, "The device wasn't tracked as connected " + btDeviceToString(device)); @@ -141,19 +105,6 @@ public class BluetoothCompanionDeviceConnectionListener return; } - // Do not need to report the connectivity since the user is not unlock the phone so - // that cdm is not bind with the app yet. - if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { - synchronized (mPendingReportConnectedDevices) { - Set<BluetoothDevice> bluetoothDevices = mPendingReportConnectedDevices.get(userId); - if (bluetoothDevices != null) { - bluetoothDevices.remove(device); - } - } - - return; - } - onDeviceConnectivityChanged(device, false); } diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java index 6c565004d948..4010be922b2c 100644 --- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java +++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java @@ -23,16 +23,13 @@ import android.annotation.NonNull; import android.annotation.SuppressLint; import android.annotation.TestApi; import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; import android.companion.AssociationInfo; import android.content.Context; import android.os.Binder; import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.os.UserManager; import android.util.Log; -import android.util.SparseArray; import com.android.server.companion.AssociationStore; @@ -89,12 +86,13 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange private final SimulatedDevicePresenceSchedulerHelper mSchedulerHelper = new SimulatedDevicePresenceSchedulerHelper(); - public CompanionDevicePresenceMonitor(UserManager userManager, - @NonNull AssociationStore associationStore, @NonNull Callback callback) { + public CompanionDevicePresenceMonitor(@NonNull AssociationStore associationStore, + @NonNull Callback callback) { mAssociationStore = associationStore; mCallback = callback; - mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(userManager, - associationStore, /* BluetoothCompanionDeviceConnectionListener.Callback */ this); + + mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(associationStore, + /* BluetoothCompanionDeviceConnectionListener.Callback */ this); mBleScanner = new BleCompanionDeviceScanner(associationStore, /* BleCompanionDeviceScanner.Callback */ this); } @@ -300,15 +298,6 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange // what's needed. } - /** - * Return a set of devices that pending to report connectivity - */ - public SparseArray<Set<BluetoothDevice>> getPendingReportConnectedDevices() { - synchronized (mBtConnectionListener.mPendingReportConnectedDevices) { - return mBtConnectionListener.mPendingReportConnectedDevices; - } - } - private static void enforceCallerShellOrRoot() { final int callingUid = Binder.getCallingUid(); if (callingUid == SHELL_UID || callingUid == ROOT_UID) return; diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 678d58232f15..cf09fde9ec2c 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -762,6 +762,15 @@ public final class ActiveServices { } } + private static void traceInstant(@NonNull String message, @NonNull ServiceRecord service) { + if (!Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { + return; + } + final String serviceName = (service.getComponentName() != null) + ? service.getComponentName().toShortString() : "(?)"; + Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, message + serviceName); + } + ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType, int callingPid, int callingUid, boolean fgRequired, String callingPackage, @Nullable String callingFeatureId, final int userId, boolean isSdkSandboxService, @@ -818,6 +827,9 @@ public final class ActiveServices { } ServiceRecord r = res.record; + + traceInstant("startService(): ", r); + // Note, when startService() or startForegroundService() is called on an already // running SHORT_SERVICE FGS, the call will succeed (i.e. we won't throw // ForegroundServiceStartNotAllowedException), even when the service is already timed @@ -1407,6 +1419,7 @@ public final class ActiveServices { } private void stopServiceLocked(ServiceRecord service, boolean enqueueOomAdj) { + traceInstant("stopService(): ", service); try { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "stopServiceLocked()"); if (service.delayed) { @@ -1946,6 +1959,7 @@ public final class ActiveServices { if (notification == null) { throw new IllegalArgumentException("null notification"); } + traceInstant("startForeground(): ", r); final int foregroundServiceStartType = foregroundServiceType; // Instant apps need permission to create foreground services. if (r.appInfo.isInstantApp()) { @@ -2484,6 +2498,7 @@ public final class ActiveServices { } } else { if (r.isForeground) { + traceInstant("stopForeground(): ", r); final ServiceMap smap = getServiceMapLocked(r.userId); if (smap != null) { decActiveForegroundAppLocked(smap, r); @@ -3311,6 +3326,7 @@ public final class ActiveServices { Slog.i(TAG_SERVICE, "Short FGS started: " + sr); } } + traceInstant("short FGS start/extend: ", sr); sr.setShortFgsInfo(SystemClock.uptimeMillis()); // We'll restart the timeout. @@ -3356,10 +3372,11 @@ public final class ActiveServices { return; } Slog.e(TAG_SERVICE, "Short FGS timed out: " + sr); - final long now = SystemClock.uptimeMillis(); + traceInstant("short FGS timeout: ", sr); + logFGSStateChangeLocked(sr, FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT, - now > sr.mFgsEnterTime ? (int) (now - sr.mFgsEnterTime) : 0, + nowUptime > sr.mFgsEnterTime ? (int) (nowUptime - sr.mFgsEnterTime) : 0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN); try { @@ -3410,6 +3427,7 @@ public final class ActiveServices { } Slog.e(TAG_SERVICE, "Short FGS procstate demoted: " + sr); + traceInstant("short FGS demote: ", sr); mAm.updateOomAdjLocked(sr.app, OOM_ADJ_REASON_SHORT_FGS_TIMEOUT); } @@ -3440,6 +3458,9 @@ public final class ActiveServices { } else { Slog.e(TAG_SERVICE, message); } + + traceInstant("short FGS ANR: ", sr); + mAm.appNotResponding(sr.app, tr); // TODO: Can we close the ANR dialog here, if it's still shown? Currently, the ANR diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index a54e8e95b155..ea8745dc666d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -6915,7 +6915,7 @@ public class ActivityManagerService extends IActivityManager.Stub mActivityTaskManager.unhandledBack(); } - // TODO: Move to ContentProviderHelper? + // TODO: Replace this method with one that returns a bound IContentProvider. public ParcelFileDescriptor openContentUri(String uriString) throws RemoteException { enforceNotIsolatedCaller("openContentUri"); final int userId = UserHandle.getCallingUserId(); @@ -6944,6 +6944,16 @@ public class ActivityManagerService extends IActivityManager.Stub Log.e(TAG, "Cannot find package for uid: " + uid); return null; } + + final ApplicationInfo appInfo = mPackageManagerInt.getApplicationInfo( + androidPackage.getPackageName(), /*flags*/0, Process.SYSTEM_UID, + UserHandle.USER_SYSTEM); + if (!appInfo.isVendor() && !appInfo.isSystemApp() && !appInfo.isSystemExt() + && !appInfo.isProduct()) { + Log.e(TAG, "openContentUri may only be used by vendor/system/product."); + return null; + } + final AttributionSource attributionSource = new AttributionSource( Binder.getCallingUid(), androidPackage.getPackageName(), null); pfd = cph.provider.openFile(attributionSource, uri, "r", null); diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index a181402e2d9f..b2fdee7a6f89 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -3747,7 +3747,8 @@ class UserController implements Handler.Callback { synchronized (mUserSwitchingDialogLock) { dismissUserSwitchingDialog(null); mUserSwitchingDialog = new UserSwitchingDialog(mService.mContext, fromUser, toUser, - switchingFromSystemUserMessage, switchingToSystemUserMessage); + switchingFromSystemUserMessage, switchingToSystemUserMessage, + getWindowManager()); mUserSwitchingDialog.show(onShown); } } diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java index 649305f7ee01..6da6a6ecda52 100644 --- a/services/core/java/com/android/server/am/UserSwitchingDialog.java +++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java @@ -19,7 +19,6 @@ package com.android.server.am; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; -import android.app.ActivityManager; import android.app.Dialog; import android.content.Context; import android.content.pm.UserInfo; @@ -38,6 +37,7 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; import android.util.Slog; import android.util.TypedValue; import android.view.View; @@ -51,6 +51,7 @@ import android.widget.TextView; import com.android.internal.R; import com.android.internal.util.ObjectUtils; import com.android.internal.util.UserIcons; +import com.android.server.wm.WindowManagerService; /** * Dialog to show during the user switch. This dialog shows target user's name and their profile @@ -70,11 +71,14 @@ class UserSwitchingDialog extends Dialog { protected final UserInfo mNewUser; private final String mSwitchingFromSystemUserMessage; private final String mSwitchingToSystemUserMessage; + private final WindowManagerService mWindowManager; protected final Context mContext; private final int mTraceCookie; + private final boolean mNeedToFreezeScreen; UserSwitchingDialog(Context context, UserInfo oldUser, UserInfo newUser, - String switchingFromSystemUserMessage, String switchingToSystemUserMessage) { + String switchingFromSystemUserMessage, String switchingToSystemUserMessage, + WindowManagerService windowManager) { // TODO(b/278857848): Make full screen user switcher cover top part of the screen as well. // This problem is seen only on phones, it works fine on tablets. super(context, R.style.Theme_Material_NoActionBar_Fullscreen); @@ -84,8 +88,10 @@ class UserSwitchingDialog extends Dialog { mNewUser = newUser; mSwitchingFromSystemUserMessage = switchingFromSystemUserMessage; mSwitchingToSystemUserMessage = switchingToSystemUserMessage; - mDisableAnimations = ActivityManager.isLowRamDeviceStatic() || SystemProperties.getBoolean( + mDisableAnimations = SystemProperties.getBoolean( "debug.usercontroller.disable_user_switching_dialog_animations", false); + mWindowManager = windowManager; + mNeedToFreezeScreen = !mDisableAnimations && !isUserSetupComplete(newUser); mTraceCookie = UserHandle.MAX_SECONDARY_USER_ID * oldUser.id + newUser.id; inflateContent(); @@ -167,6 +173,11 @@ class UserSwitchingDialog extends Dialog { : res.getString(R.string.user_switching_message, mNewUser.name); } + private boolean isUserSetupComplete(UserInfo user) { + return Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, /* default= */ 0, user.id) == 1; + } + @Override public void show() { asyncTraceBegin("", 0); @@ -176,29 +187,24 @@ class UserSwitchingDialog extends Dialog { @Override public void dismiss() { super.dismiss(); + stopFreezingScreen(); asyncTraceEnd("", 0); } public void show(@NonNull Runnable onShown) { if (DEBUG) Slog.d(TAG, "show called"); show(); - - if (mDisableAnimations) { + startShowAnimation(() -> { + startFreezingScreen(); onShown.run(); - } else { - startShowAnimation(onShown); - } + }); } public void dismiss(@Nullable Runnable onDismissed) { if (DEBUG) Slog.d(TAG, "dismiss called"); - if (onDismissed == null) { // no animation needed dismiss(); - } else if (mDisableAnimations) { - dismiss(); - onDismissed.run(); } else { startDismissAnimation(() -> { dismiss(); @@ -207,7 +213,31 @@ class UserSwitchingDialog extends Dialog { } } + private void startFreezingScreen() { + if (!mNeedToFreezeScreen) { + return; + } + if (DEBUG) Slog.d(TAG, "startFreezingScreen"); + Trace.traceBegin(TRACE_TAG, "startFreezingScreen"); + mWindowManager.startFreezingScreen(0, 0); + Trace.traceEnd(TRACE_TAG); + } + + private void stopFreezingScreen() { + if (!mNeedToFreezeScreen) { + return; + } + if (DEBUG) Slog.d(TAG, "stopFreezingScreen"); + Trace.traceBegin(TRACE_TAG, "stopFreezingScreen"); + mWindowManager.stopFreezingScreen(); + Trace.traceEnd(TRACE_TAG); + } + private void startShowAnimation(Runnable onAnimationEnd) { + if (mDisableAnimations) { + onAnimationEnd.run(); + return; + } asyncTraceBegin("-showAnimation", 1); startDialogAnimation(new AlphaAnimation(0, 1), () -> { asyncTraceEnd("-showAnimation", 1); @@ -222,6 +252,11 @@ class UserSwitchingDialog extends Dialog { } private void startDismissAnimation(Runnable onAnimationEnd) { + if (mDisableAnimations || mNeedToFreezeScreen) { + // animations are disabled or screen is frozen, no need to play an animation + onAnimationEnd.run(); + return; + } asyncTraceBegin("-dismissAnimation", 3); startDialogAnimation(new AlphaAnimation(1, 0), () -> { asyncTraceEnd("-dismissAnimation", 3); @@ -231,6 +266,10 @@ class UserSwitchingDialog extends Dialog { } private void startProgressAnimation(Runnable onAnimationEnd) { + if (mDisableAnimations) { + onAnimationEnd.run(); + return; + } final ImageView progressCircular = findViewById(R.id.progress_circular); final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) progressCircular.getDrawable(); avd.registerAnimationCallback(new Animatable2.AnimationCallback() { diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java index 7cdea8d08c58..9429b4c129f5 100644 --- a/services/core/java/com/android/server/audio/SoundDoseHelper.java +++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java @@ -57,6 +57,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @@ -168,7 +169,7 @@ public class SoundDoseHelper { @NonNull private final AudioHandler mAudioHandler; @NonNull private final ISafeHearingVolumeController mVolumeController; - private final boolean mEnableCsd; + private final AtomicBoolean mEnableCsd = new AtomicBoolean(false); private final Object mCsdStateLock = new Object(); @@ -195,7 +196,7 @@ public class SoundDoseHelper { private final ISoundDoseCallback.Stub mSoundDoseCallback = new ISoundDoseCallback.Stub() { public void onMomentaryExposure(float currentMel, int deviceId) { - if (!mEnableCsd) { + if (!mEnableCsd.get()) { Log.w(TAG, "onMomentaryExposure: csd not supported, ignoring callback"); return; } @@ -222,7 +223,7 @@ public class SoundDoseHelper { } public void onNewCsdValue(float currentCsd, SoundDoseRecord[] records) { - if (!mEnableCsd) { + if (!mEnableCsd.get()) { Log.w(TAG, "onNewCsdValue: csd not supported, ignoring value"); return; } @@ -272,8 +273,6 @@ public class SoundDoseHelper { mContext = context; - mEnableCsd = mContext.getResources().getBoolean(R.bool.config_audio_csd_enabled_default); - initCsd(); initSafeVolumes(); mSafeMediaVolumeState = mSettings.getGlobalInt(audioService.getContentResolver(), @@ -285,6 +284,10 @@ public class SoundDoseHelper { mSafeMediaVolumeIndex = mContext.getResources().getInteger( R.integer.config_safe_media_volume_index) * 10; + mSoundDose.set(AudioSystem.getSoundDoseInterface(mSoundDoseCallback)); + // Csd will be initially disabled until the mcc is read in onConfigureSafeMedia() + initCsd(); + mAlarmManager = (AlarmManager) mContext.getSystemService( Context.ALARM_SERVICE); } @@ -310,7 +313,7 @@ public class SoundDoseHelper { } float getOutputRs2UpperBound() { - if (!mEnableCsd) { + if (!mEnableCsd.get()) { return 0.f; } @@ -329,7 +332,7 @@ public class SoundDoseHelper { } void setOutputRs2UpperBound(float rs2Value) { - if (!mEnableCsd) { + if (!mEnableCsd.get()) { return; } @@ -347,7 +350,7 @@ public class SoundDoseHelper { } float getCsd() { - if (!mEnableCsd) { + if (!mEnableCsd.get()) { return -1.f; } @@ -366,7 +369,7 @@ public class SoundDoseHelper { } void setCsd(float csd) { - if (!mEnableCsd) { + if (!mEnableCsd.get()) { return; } @@ -400,7 +403,7 @@ public class SoundDoseHelper { } void resetCsdTimeouts() { - if (!mEnableCsd) { + if (!mEnableCsd.get()) { return; } @@ -416,7 +419,7 @@ public class SoundDoseHelper { } void forceUseFrameworkMel(boolean useFrameworkMel) { - if (!mEnableCsd) { + if (!mEnableCsd.get()) { return; } @@ -434,7 +437,7 @@ public class SoundDoseHelper { } void forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices) { - if (!mEnableCsd) { + if (!mEnableCsd.get()) { return; } @@ -454,7 +457,7 @@ public class SoundDoseHelper { } boolean isCsdEnabled() { - if (!mEnableCsd) { + if (!mEnableCsd.get()) { return false; } @@ -697,8 +700,8 @@ public class SoundDoseHelper { } /*package*/ void dump(PrintWriter pw) { - pw.print(" mEnableCsd="); pw.println(mEnableCsd); - if (mEnableCsd) { + pw.print(" mEnableCsd="); pw.println(mEnableCsd.get()); + if (mEnableCsd.get()) { synchronized (mCsdStateLock) { pw.print(" mCurrentCsd="); pw.println(mCurrentCsd); } @@ -719,9 +722,11 @@ public class SoundDoseHelper { pw.println(); } - /*package*/void reset() { + /*package*/void reset() { Log.d(TAG, "Reset the sound dose helper"); - mSoundDose.set(AudioSystem.getSoundDoseInterface(mSoundDoseCallback)); + + mSoundDose.compareAndExchange(/*expectedValue=*/null, + AudioSystem.getSoundDoseInterface(mSoundDoseCallback)); synchronized (mCsdStateLock) { try { @@ -743,7 +748,7 @@ public class SoundDoseHelper { private void updateDoseAttenuation(int newIndex, int device, int streamType, boolean isAbsoluteVolume) { - if (!mEnableCsd) { + if (!mEnableCsd.get()) { return; } @@ -775,17 +780,19 @@ public class SoundDoseHelper { } private void initCsd() { - if (!mEnableCsd) { - final ISoundDose soundDose = AudioSystem.getSoundDoseInterface(mSoundDoseCallback); - if (soundDose == null) { - Log.w(TAG, "ISoundDose instance is null."); - return; - } - try { - soundDose.disableCsd(); - } catch (RemoteException e) { - Log.e(TAG, "Cannot disable CSD", e); - } + ISoundDose soundDose = mSoundDose.get(); + if (soundDose == null) { + Log.w(TAG, "ISoundDose instance is null."); + return; + } + + try { + soundDose.setCsdEnabled(mEnableCsd.get()); + } catch (RemoteException e) { + Log.e(TAG, "Cannot disable CSD", e); + } + + if (!mEnableCsd.get()) { return; } @@ -829,7 +836,6 @@ public class SoundDoseHelper { SystemProperties.getBoolean("audio.safemedia.force", false) || mContext.getResources().getBoolean( com.android.internal.R.bool.config_safe_media_volume_enabled); - boolean safeMediaVolumeBypass = SystemProperties.getBoolean("audio.safemedia.bypass", false); @@ -860,6 +866,13 @@ public class SoundDoseHelper { mAudioHandler.obtainMessage(MSG_PERSIST_SAFE_VOLUME_STATE, persistedState, /*arg2=*/0, /*obj=*/null), /*delay=*/0); + + boolean newEnableCsd = SystemProperties.getBoolean("audio.safemedia.force", false) + || mContext.getResources().getBoolean( + R.bool.config_safe_sound_dosage_enabled); + if (mEnableCsd.compareAndSet(!newEnableCsd, newEnableCsd)) { + initCsd(); + } } } } @@ -913,7 +926,7 @@ public class SoundDoseHelper { // legacy implementation uses mSafeMediaVolumeIndex for wired HS/HP // instead of computing it from the volume curves if ((deviceType == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE - || deviceType == AudioSystem.DEVICE_OUT_WIRED_HEADSET) && !mEnableCsd) { + || deviceType == AudioSystem.DEVICE_OUT_WIRED_HEADSET) && !mEnableCsd.get()) { return mSafeMediaVolumeIndex; } diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index fd94be9a2921..17f928af1c09 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -65,7 +65,6 @@ import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.display.BrightnessSynchronizer; -import com.android.internal.display.RefreshRateSettingsUtils; import com.android.internal.os.BackgroundThread; import com.android.server.LocalServices; import com.android.server.display.DisplayDeviceConfig; @@ -1067,10 +1066,10 @@ public class DisplayModeDirector { @VisibleForTesting final class SettingsObserver extends ContentObserver { - private final Uri mSmoothDisplaySetting = - Settings.System.getUriFor(Settings.System.SMOOTH_DISPLAY); - private final Uri mForcePeakRefreshRateSetting = - Settings.System.getUriFor(Settings.System.FORCE_PEAK_REFRESH_RATE); + private final Uri mPeakRefreshRateSetting = + Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE); + private final Uri mMinRefreshRateSetting = + Settings.System.getUriFor(Settings.System.MIN_REFRESH_RATE); private final Uri mLowPowerModeSetting = Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE); private final Uri mMatchContentFrameRateSetting = @@ -1106,8 +1105,9 @@ public class DisplayModeDirector { public void observe() { final ContentResolver cr = mContext.getContentResolver(); - mInjector.registerSmoothDisplayObserver(cr, this); - mInjector.registerForcePeakRefreshRateObserver(cr, this); + mInjector.registerPeakRefreshRateObserver(cr, this); + cr.registerContentObserver(mMinRefreshRateSetting, false /*notifyDescendants*/, this, + UserHandle.USER_SYSTEM); cr.registerContentObserver(mLowPowerModeSetting, false /*notifyDescendants*/, this, UserHandle.USER_SYSTEM); cr.registerContentObserver(mMatchContentFrameRateSetting, false /*notifyDescendants*/, @@ -1149,8 +1149,8 @@ public class DisplayModeDirector { @Override public void onChange(boolean selfChange, Uri uri, int userId) { synchronized (mLock) { - if (mSmoothDisplaySetting.equals(uri) - || mForcePeakRefreshRateSetting.equals(uri)) { + if (mPeakRefreshRateSetting.equals(uri) + || mMinRefreshRateSetting.equals(uri)) { updateRefreshRateSettingLocked(); } else if (mLowPowerModeSetting.equals(uri)) { updateLowPowerModeSettingLocked(); @@ -1205,9 +1205,12 @@ public class DisplayModeDirector { } private void updateRefreshRateSettingLocked() { - updateRefreshRateSettingLocked(RefreshRateSettingsUtils.getMinRefreshRate(mContext), - RefreshRateSettingsUtils.getPeakRefreshRate(mContext, mDefaultPeakRefreshRate), - mDefaultRefreshRate); + final ContentResolver cr = mContext.getContentResolver(); + float minRefreshRate = Settings.System.getFloatForUser(cr, + Settings.System.MIN_REFRESH_RATE, 0f, cr.getUserId()); + float peakRefreshRate = Settings.System.getFloatForUser(cr, + Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate, cr.getUserId()); + updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate); } private void updateRefreshRateSettingLocked( @@ -2840,17 +2843,12 @@ public class DisplayModeDirector { } interface Injector { - Uri SMOOTH_DISPLAY_URI = Settings.System.getUriFor(Settings.System.SMOOTH_DISPLAY); - Uri FORCE_PEAK_REFRESH_RATE_URI = - Settings.System.getUriFor(Settings.System.FORCE_PEAK_REFRESH_RATE); + Uri PEAK_REFRESH_RATE_URI = Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE); @NonNull DeviceConfigInterface getDeviceConfig(); - void registerSmoothDisplayObserver(@NonNull ContentResolver cr, - @NonNull ContentObserver observer); - - void registerForcePeakRefreshRateObserver(@NonNull ContentResolver cr, + void registerPeakRefreshRateObserver(@NonNull ContentResolver cr, @NonNull ContentObserver observer); void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener, @@ -2890,16 +2888,9 @@ public class DisplayModeDirector { } @Override - public void registerSmoothDisplayObserver(@NonNull ContentResolver cr, - @NonNull ContentObserver observer) { - cr.registerContentObserver(SMOOTH_DISPLAY_URI, false /*notifyDescendants*/, - observer, UserHandle.USER_SYSTEM); - } - - @Override - public void registerForcePeakRefreshRateObserver(@NonNull ContentResolver cr, + public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr, @NonNull ContentObserver observer) { - cr.registerContentObserver(FORCE_PEAK_REFRESH_RATE_URI, false /*notifyDescendants*/, + cr.registerContentObserver(PEAK_REFRESH_RATE_URI, false /*notifyDescendants*/, observer, UserHandle.USER_SYSTEM); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java index 220a438d55ee..b82129bdf82f 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java @@ -35,6 +35,7 @@ public class HdmiCecMessageValidator { ERROR_DESTINATION, ERROR_PARAMETER, ERROR_PARAMETER_SHORT, + ERROR_PARAMETER_LONG, }) public @interface ValidationResult {}; @@ -43,6 +44,7 @@ public class HdmiCecMessageValidator { static final int ERROR_DESTINATION = 2; static final int ERROR_PARAMETER = 3; static final int ERROR_PARAMETER_SHORT = 4; + static final int ERROR_PARAMETER_LONG = 5; interface ParameterValidator { /** @@ -159,11 +161,13 @@ public class HdmiCecMessageValidator { addValidationInfo(Constants.MESSAGE_SET_MENU_LANGUAGE, new AsciiValidator(3), DEST_BROADCAST); - ParameterValidator statusRequestValidator = new OneByteRangeValidator(0x01, 0x03); + ParameterValidator statusRequestValidator = new MinimumOneByteRangeValidator(0x01, 0x03); addValidationInfo( - Constants.MESSAGE_DECK_CONTROL, new OneByteRangeValidator(0x01, 0x04), DEST_DIRECT); + Constants.MESSAGE_DECK_CONTROL, + new MinimumOneByteRangeValidator(0x01, 0x04), DEST_DIRECT); addValidationInfo( - Constants.MESSAGE_DECK_STATUS, new OneByteRangeValidator(0x11, 0x1F), DEST_DIRECT); + Constants.MESSAGE_DECK_STATUS, + new MinimumOneByteRangeValidator(0x11, 0x1F), DEST_DIRECT); addValidationInfo(Constants.MESSAGE_GIVE_DECK_STATUS, statusRequestValidator, DEST_DIRECT); addValidationInfo(Constants.MESSAGE_PLAY, new PlayModeValidator(), DEST_DIRECT); @@ -201,9 +205,11 @@ public class HdmiCecMessageValidator { // Messages for the Device Menu Control. addValidationInfo( - Constants.MESSAGE_MENU_REQUEST, new OneByteRangeValidator(0x00, 0x02), DEST_DIRECT); + Constants.MESSAGE_MENU_REQUEST, + new MinimumOneByteRangeValidator(0x00, 0x02), DEST_DIRECT); addValidationInfo( - Constants.MESSAGE_MENU_STATUS, new OneByteRangeValidator(0x00, 0x01), DEST_DIRECT); + Constants.MESSAGE_MENU_STATUS, + new MinimumOneByteRangeValidator(0x00, 0x01), DEST_DIRECT); // Messages for the Remote Control Passthrough. addValidationInfo( @@ -214,7 +220,7 @@ public class HdmiCecMessageValidator { // Messages for the Power Status. addValidationInfo( Constants.MESSAGE_REPORT_POWER_STATUS, - new OneByteRangeValidator(0x00, 0x03), + new MinimumOneByteRangeValidator(0x00, 0x03), DEST_DIRECT | DEST_BROADCAST); // Messages for the General Protocol. @@ -229,17 +235,17 @@ public class HdmiCecMessageValidator { oneByteValidator, DEST_DIRECT); addValidationInfo( Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE, - new OneByteRangeValidator(0x00, 0x01), + new MinimumOneByteRangeValidator(0x00, 0x01), DEST_ALL); addValidationInfo( Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS, - new OneByteRangeValidator(0x00, 0x01), + new SingleByteRangeValidator(0x00, 0x01), DEST_DIRECT); // Messages for the Audio Rate Control. addValidationInfo( Constants.MESSAGE_SET_AUDIO_RATE, - new OneByteRangeValidator(0x00, 0x06), + new MinimumOneByteRangeValidator(0x00, 0x06), DEST_DIRECT); // Messages for Feature Discovery. @@ -900,11 +906,32 @@ public class HdmiCecMessageValidator { } } - /** Check if the given parameters are one byte parameters and within range. */ - private static class OneByteRangeValidator implements ParameterValidator { + /** + * Check if the given parameters are at least one byte parameters + * and the first byte is within range. + */ + private static class MinimumOneByteRangeValidator implements ParameterValidator { + private final int mMinValue, mMaxValue; + + MinimumOneByteRangeValidator(int minValue, int maxValue) { + mMinValue = minValue; + mMaxValue = maxValue; + } + + @Override + public int isValid(byte[] params) { + if (params.length < 1) { + return ERROR_PARAMETER_SHORT; + } + return toErrorCode(isWithinRange(params[0], mMinValue, mMaxValue)); + } + } + + /** Check if the given parameters are exactly one byte parameters and within range. */ + private static class SingleByteRangeValidator implements ParameterValidator { private final int mMinValue, mMaxValue; - OneByteRangeValidator(int minValue, int maxValue) { + SingleByteRangeValidator(int minValue, int maxValue) { mMinValue = minValue; mMaxValue = maxValue; } @@ -913,6 +940,8 @@ public class HdmiCecMessageValidator { public int isValid(byte[] params) { if (params.length < 1) { return ERROR_PARAMETER_SHORT; + } else if (params.length > 1) { + return ERROR_PARAMETER_LONG; } return toErrorCode(isWithinRange(params[0], mMinValue, mMaxValue)); } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 75fe63a66206..9cd5272b356b 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -1611,6 +1611,7 @@ public class HdmiControlService extends SystemService { @HdmiCecMessageValidator.ValidationResult int validationResult = message.getValidationResult(); if (validationResult == HdmiCecMessageValidator.ERROR_PARAMETER + || validationResult == HdmiCecMessageValidator.ERROR_PARAMETER_LONG || !verifyPhysicalAddresses(message)) { return Constants.ABORT_INVALID_OPERAND; } else if (validationResult != HdmiCecMessageValidator.OK diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java index 92f0011339a5..9c3b38ab51a3 100644 --- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java +++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java @@ -395,7 +395,7 @@ public final class SingleKeyGestureDetector { private class KeyHandler extends Handler { KeyHandler() { - super(Looper.getMainLooper()); + super(Looper.myLooper()); } @Override diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 98d2d3d4b997..9020cb3405a2 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -1353,9 +1353,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub void complete() { // Only changes from home+lock to just home or lock need attention - // If setting the wallpaper fails, this callback will be called - // when the wallpaper is detached, in which case wallpapers may have - // already changed. Make sure we're not overwriting a more recent wallpaper. if (mNewWallpaper.mSystemWasBoth) { if (DEBUG) { Slog.v(TAG, "Handling change from system+lock wallpaper"); @@ -1378,7 +1375,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub mOriginalSystem.wallpaperComponent; lockWp.connection = mOriginalSystem.connection; lockWp.connection.mWallpaper = lockWp; - updateEngineFlags(mOriginalSystem, FLAG_LOCK); + mOriginalSystem.mWhich = FLAG_LOCK; + updateEngineFlags(mOriginalSystem); notifyWallpaperColorsChanged(lockWp, FLAG_LOCK); } else { // Failed rename, use current system wp for both @@ -1387,7 +1385,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } WallpaperData currentSystem = mWallpaperMap.get(mNewWallpaper.userId); currentSystem.mWhich = FLAG_SYSTEM | FLAG_LOCK; - updateEngineFlags(currentSystem, FLAG_SYSTEM | FLAG_LOCK); + updateEngineFlags(currentSystem); mLockWallpaperMap.remove(mNewWallpaper.userId); } } else { @@ -1396,7 +1394,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub Slog.v(TAG, "live system+lock to system success"); } mOriginalSystem.mWhich = FLAG_LOCK; - updateEngineFlags(mOriginalSystem, FLAG_LOCK); + updateEngineFlags(mOriginalSystem); mLockWallpaperMap.put(mNewWallpaper.userId, mOriginalSystem); mLastLockWallpaper = mOriginalSystem; notifyWallpaperColorsChanged(mOriginalSystem, FLAG_LOCK); @@ -1409,7 +1407,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperData currentSystem = mWallpaperMap.get(mNewWallpaper.userId); if (currentSystem.wallpaperId == mOriginalSystem.wallpaperId) { currentSystem.mWhich = FLAG_SYSTEM; - updateEngineFlags(currentSystem, FLAG_SYSTEM); + updateEngineFlags(currentSystem); } } } @@ -1422,24 +1420,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub Slog.v(TAG, "new lastLockWp: " + mLastLockWallpaper); } } - - private void updateEngineFlags(WallpaperData wallpaper, @SetWallpaperFlags int which) { - if (wallpaper.connection == null) { - return; - } - wallpaper.connection.forEachDisplayConnector( - connector -> { - try { - if (connector.mEngine != null) { - connector.mEngine.setWallpaperFlags(which); - mWindowManagerInternal.setWallpaperShowWhenLocked( - connector.mToken, (which & FLAG_LOCK) != 0); - } - } catch (RemoteException e) { - Slog.e(TAG, "Failed to update wallpaper engine flags", e); - } - }); - } } class MyPackageMonitor extends PackageMonitor { @@ -3095,6 +3075,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub newWallpaper.userId); if (lockedWallpaper != null) { detachWallpaperLocked(lockedWallpaper); + if (same) { + updateEngineFlags(newWallpaper); + } } mLockWallpaperMap.remove(newWallpaper.userId); } @@ -3430,6 +3413,27 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } + // Updates the given wallpaper's Engine so that its destination flags are the same as those of + // the wallpaper, e.g., after a wallpaper has been changed from displaying on home+lock to home + // or lock only. + private void updateEngineFlags(WallpaperData wallpaper) { + if (wallpaper.connection == null) { + return; + } + wallpaper.connection.forEachDisplayConnector( + connector -> { + try { + if (connector.mEngine != null) { + connector.mEngine.setWallpaperFlags(wallpaper.mWhich); + mWindowManagerInternal.setWallpaperShowWhenLocked( + connector.mToken, (wallpaper.mWhich & FLAG_LOCK) != 0); + } + } catch (RemoteException e) { + Slog.e(TAG, "Failed to update wallpaper engine flags", e); + } + }); + } + private void clearWallpaperComponentLocked(WallpaperData wallpaper) { wallpaper.wallpaperComponent = null; detachWallpaperLocked(wallpaper); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 78c066bdc212..c3cd3eca84f2 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -5400,7 +5400,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } if (inFinishingTransition) { - // Let the finishing transition commit the visibility. + // Let the finishing transition commit the visibility, but let the controller know + // about it so that we can recover from degenerate cases. + mTransitionController.mValidateCommitVis.add(this); return; } // If we are preparing an app transition, then delay changing diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index f5079d37b324..e47787e97f20 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -33,6 +33,7 @@ import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.os.Process.SYSTEM_UID; +import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; @@ -209,7 +210,8 @@ class RecentTasks { private final PointerEventListener mListener = new PointerEventListener() { @Override public void onPointerEvent(MotionEvent ev) { - if (!mFreezeTaskListReordering || ev.getAction() != MotionEvent.ACTION_DOWN) { + if (!mFreezeTaskListReordering || ev.getAction() != MotionEvent.ACTION_DOWN + || ev.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE) { // Skip if we aren't freezing or starting a gesture return; } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 3f4296a5eee5..d3edeaebbf99 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -3239,7 +3239,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (task.getActivity(activity -> !activity.finishing && activity.mUserId == userId) != null) { mService.getTaskChangeNotificationController().notifyTaskProfileLocked( - task.getTaskInfo()); + task.getTaskInfo(), userId); } }, true /* traverseTopToBottom */); } diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java index 49d064f83345..9324e29daafb 100644 --- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java +++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java @@ -144,7 +144,7 @@ class TaskChangeNotificationController { }; private final TaskStackConsumer mNotifyTaskProfileLocked = (l, m) -> { - l.onTaskProfileLocked((RunningTaskInfo) m.obj); + l.onTaskProfileLocked((RunningTaskInfo) m.obj, m.arg1); }; private final TaskStackConsumer mNotifyTaskSnapshotChanged = (l, m) -> { @@ -467,9 +467,9 @@ class TaskChangeNotificationController { * Notify listeners that the task has been put in a locked state because one or more of the * activities inside it belong to a managed profile user that has been locked. */ - void notifyTaskProfileLocked(ActivityManager.RunningTaskInfo taskInfo) { + void notifyTaskProfileLocked(RunningTaskInfo taskInfo, int userId) { final Message msg = mHandler.obtainMessage(NOTIFY_TASK_PROFILE_LOCKED_LISTENERS_MSG, - taskInfo); + userId, 0, taskInfo); forAllLocalListeners(mNotifyTaskProfileLocked, msg); msg.sendToTarget(); } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index d531ad175f10..0f74aebc2fde 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -55,6 +55,7 @@ import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static android.window.TransitionInfo.FLAG_TASK_LAUNCHING_BEHIND; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; import static android.window.TransitionInfo.FLAG_WILL_IME_SHOWN; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT; import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM; @@ -65,12 +66,14 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.ActivityOptions; import android.app.IApplicationThread; import android.content.pm.ActivityInfo; import android.graphics.Point; import android.graphics.Rect; import android.hardware.HardwareBuffer; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; import android.os.IRemoteCallback; import android.os.RemoteException; @@ -85,6 +88,7 @@ import android.view.SurfaceControl; import android.view.WindowManager; import android.window.ScreenCapture; import android.window.TransitionInfo; +import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.ColorUtils; @@ -185,7 +189,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final ArraySet<WindowContainer> mParticipants = new ArraySet<>(); /** The final animation targets derived from participants after promotion. */ - private ArrayList<ChangeInfo> mTargets; + ArrayList<ChangeInfo> mTargets; /** The displays that this transition is running on. */ private final ArrayList<DisplayContent> mTargetDisplays = new ArrayList<>(); @@ -271,9 +275,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { /** Any 2 transitions of this type can run in parallel with each other. Used for testing. */ static final int PARALLEL_TYPE_MUTUAL = 1; + /** This is a recents transition. */ + static final int PARALLEL_TYPE_RECENTS = 2; + + @IntDef(prefix = { "PARALLEL_TYPE_" }, value = { PARALLEL_TYPE_NONE, - PARALLEL_TYPE_MUTUAL + PARALLEL_TYPE_MUTUAL, + PARALLEL_TYPE_RECENTS }) @Retention(RetentionPolicy.SOURCE) @interface ParallelType {} @@ -328,6 +337,21 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mFlags |= flag; } + void calcParallelCollectType(WindowContainerTransaction wct) { + for (int i = 0; i < wct.getHierarchyOps().size(); ++i) { + final WindowContainerTransaction.HierarchyOp hop = wct.getHierarchyOps().get(i); + if (hop.getType() != HIERARCHY_OP_TYPE_PENDING_INTENT) continue; + final Bundle b = hop.getLaunchOptions(); + if (b == null || b.isEmpty()) continue; + final boolean transientLaunch = b.getBoolean(ActivityOptions.KEY_TRANSIENT_LAUNCH); + if (transientLaunch) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, + "Starting a Recents transition which can be parallel."); + mParallelCollectType = PARALLEL_TYPE_RECENTS; + } + } + } + /** Records an activity as transient-launch. This activity must be already collected. */ void setTransientLaunch(@NonNull ActivityRecord activity, @Nullable Task restoreBelow) { if (mTransientLaunches == null) { @@ -380,6 +404,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { return false; } + boolean hasTransientLaunch() { + return mTransientLaunches != null && !mTransientLaunches.isEmpty(); + } + boolean isTransientLaunch(@NonNull ActivityRecord activity) { return mTransientLaunches != null && mTransientLaunches.containsKey(activity); } @@ -984,13 +1012,20 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // Record all the now-hiding activities so that they are committed. Just use // mParticipants because we can avoid a new list this way. for (int i = 0; i < mTransientHideTasks.size(); ++i) { - // Only worry about tasks that were actually hidden. Otherwise, we could end-up - // committing visibility for activity-level changes that aren't part of this - // transition. - if (mTransientHideTasks.get(i).isVisibleRequested()) continue; - mTransientHideTasks.get(i).forAllActivities(r -> { + final Task rootTask = mTransientHideTasks.get(i); + rootTask.forAllActivities(r -> { // Only check leaf-tasks that were collected if (!mParticipants.contains(r.getTask())) return; + if (rootTask.isVisibleRequested()) { + // This transient-hide didn't hide, so don't commit anything (otherwise we + // could prematurely commit invisible on unrelated activities). To be safe, + // though, notify the controller to prevent degenerate cases. + if (!r.isVisibleRequested()) { + mController.mValidateCommitVis.add(r); + } + return; + } + // This did hide: commit immediately so that other transitions know about it. mParticipants.add(r); }); } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 7950edaaa267..e1da91a6cb21 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -133,6 +133,13 @@ class TransitionController { final ArrayList<Runnable> mStateValidators = new ArrayList<>(); /** + * List of activity-records whose visibility changed outside the main/tracked part of a + * transition (eg. in the finish-transaction). These will be checked when idle to recover from + * degenerate states. + */ + final ArrayList<ActivityRecord> mValidateCommitVis = new ArrayList<>(); + + /** * Currently playing transitions (in the order they were started). When finished, records are * removed from this list. */ @@ -848,6 +855,15 @@ class TransitionController { } } mStateValidators.clear(); + for (int i = 0; i < mValidateCommitVis.size(); ++i) { + final ActivityRecord ar = mValidateCommitVis.get(i); + if (!ar.isVisibleRequested() && ar.isVisible()) { + Slog.e(TAG, "Uncommitted visibility change: " + ar); + ar.commitVisibility(ar.isVisibleRequested(), false /* layout */, + false /* fromTransition */); + } + } + mValidateCommitVis.clear(); } /** @@ -879,6 +895,8 @@ class TransitionController { // If it's a legacy sync, then it needs to wait until there is no collecting transition. if (queued.mTransition == null) return; if (!canStartCollectingNow(queued.mTransition)) return; + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Moving #%d from collecting" + + " to waiting.", mCollectingTransition.getSyncId()); mWaitingTransitions.add(mCollectingTransition); mCollectingTransition = null; } else if (mSyncEngine.hasActiveSync()) { @@ -930,10 +948,37 @@ class TransitionController { * in {@link #getIsIndependent} later. */ boolean getCanBeIndependent(Transition collecting, Transition queued) { + // For tests if (queued.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL && collecting.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL) { return true; } + // For recents + if (queued.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS) { + if (collecting.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS) { + // Must serialize with itself. + return false; + } + // allow this if `collecting` only has activities + for (int i = 0; i < collecting.mParticipants.size(); ++i) { + final WindowContainer wc = collecting.mParticipants.valueAt(i); + final ActivityRecord ar = wc.asActivityRecord(); + if (ar == null && wc.asWindowState() == null && wc.asWindowToken() == null) { + // Is task or above, so can't be independent + return false; + } + if (ar != null && ar.isActivityTypeHomeOrRecents()) { + // It's a recents or home type, so it conflicts. + return false; + } + } + return true; + } else if (collecting.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS) { + // We can collect simultaneously with recents if it is populated. This is because + // we know that recents will not collect/trampoline any more stuff. If anything in the + // queued transition overlaps, it will end up just waiting in sync-queue anyways. + return true; + } return false; } @@ -942,11 +987,47 @@ class TransitionController { * `running` is playing based on its current state. */ static boolean getIsIndependent(Transition running, Transition incoming) { + // For tests if (running.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL && incoming.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL) { return true; } - return false; + // For now there's only one mutually-independent pair: an all activity-level transition and + // a transient-launch where none of the activities are part of the transient-launch task, + // so the following logic is hard-coded specifically for this. + // Also, we currently restrict valid transient-launches to just recents. + final Transition recents; + final Transition other; + if (running.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS + && running.hasTransientLaunch()) { + if (incoming.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS) { + // Recents can't be independent from itself. + return false; + } + recents = running; + other = incoming; + } else if (incoming.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS + && incoming.hasTransientLaunch()) { + recents = incoming; + other = running; + } else { + return false; + } + // Check against *targets* because that is the post-promotion set of containers that are + // actually animating. + for (int i = 0; i < other.mTargets.size(); ++i) { + final WindowContainer wc = other.mTargets.get(i).mContainer; + final ActivityRecord ar = wc.asActivityRecord(); + if (ar == null && wc.asWindowState() == null && wc.asWindowToken() == null) { + // Is task or above, so for now don't let them be independent. + return false; + } + if (ar != null && recents.isTransientLaunch(ar)) { + // Change overlaps with recents, so serialize. + return false; + } + } + return true; } void assignTrack(Transition transition, TransitionInfo info) { @@ -970,9 +1051,15 @@ class TransitionController { if (track < 0) { // Didn't overlap with anything, so give it its own track track = mTrackCount; + if (track > 0) { + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Playing #%d in parallel on " + + "track #%d", transition.getSyncId(), track); + } } if (sync) { info.setFlags(info.getFlags() | TransitionInfo.FLAG_SYNC); + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Marking #%d animation as SYNC.", + transition.getSyncId()); } transition.mAnimationTrack = track; info.setTrack(track); @@ -1145,6 +1232,8 @@ class TransitionController { // Check if we can run in parallel here. if (canStartCollectingNow(transit)) { // start running in parallel. + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Moving #%d from" + + " collecting to waiting.", mCollectingTransition.getSyncId()); mWaitingTransitions.add(mCollectingTransition); mCollectingTransition = null; moveToCollecting(transit); diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index cd42528ad79b..d5aa520e1b6e 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -299,6 +299,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final boolean needsSetReady = t != null; final Transition nextTransition = new Transition(type, 0 /* flags */, mTransitionController, mService.mWindowManager.mSyncEngine); + nextTransition.calcParallelCollectType(wct); mTransitionController.startCollectOrQueue(nextTransition, (deferred) -> { nextTransition.start(); diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java index fc7fd1afe58f..b073ff400e44 100644 --- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java @@ -98,7 +98,9 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR mRequestId, mClientRequest, mClientAppInfo.getPackageName(), PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(), - Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)), + Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS), + // TODO(b/279480457): populate + /*defaultProviderId=*/new ArrayList<>()), providerDataList); mClientCallback.onPendingIntent(mPendingIntent); } catch (RemoteException e) { diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java index 50e5163cea55..4e82ee71ac1b 100644 --- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java +++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java @@ -35,6 +35,7 @@ import java.util.Map; /** * For all future metric additions, this will contain their names for local usage after importing * from {@link com.android.internal.util.FrameworkStatsLog}. + * TODO(b/271135048) - Emit all atoms, including all V4 atoms (specifically the rest of track 1). */ public class MetricUtilities { private static final boolean LOG_FLAG = true; diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java index a62d9e805a93..0c3d2a4c000a 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java @@ -40,7 +40,6 @@ import android.service.credentials.CredentialEntry; import android.service.credentials.CredentialProviderService; import android.service.credentials.GetCredentialRequest; import android.service.credentials.RemoteEntry; -import android.util.Log; import android.util.Pair; import android.util.Slog; @@ -413,11 +412,9 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential */ private boolean onAuthenticationEntrySelected( @Nullable ProviderPendingIntentResponse providerPendingIntentResponse) { - Log.i(TAG, "onAuthenticationEntrySelected"); // Authentication entry is expected to have a BeginGetCredentialResponse instance. If it // does not have it, we remove the authentication entry and do not add any more content. if (providerPendingIntentResponse == null) { - Log.i(TAG, "providerPendingIntentResponse is null"); // Nothing received. This is equivalent to no content received. return false; } diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java index c10f5640c466..ead86cefc5d4 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java @@ -266,7 +266,7 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption .collect(Collectors.toList()); updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED, /*source=*/ CredentialsSource.REGISTRY); - // TODO(use metric later) + // TODO(b/273353677) : metric should be emitted similarly to sibling classes } @Nullable diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java index d02a8c1ee510..73fdc1ce2635 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java @@ -268,12 +268,9 @@ public abstract class ProviderSession<T, R> /*pId=*/-1, appInfo.uid) == PackageManager.PERMISSION_GRANTED) { return true; } - } catch (SecurityException e) { + } catch (SecurityException | PackageManager.NameNotFoundException e) { Slog.e(TAG, "Error getting info for " + mComponentName.flattenToString(), e); return false; - } catch (PackageManager.NameNotFoundException e) { - Slog.i(TAG, "Error getting info for " + mComponentName.flattenToString(), e); - return false; } return false; } diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java index 721d3d782653..b212606b8271 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java @@ -30,7 +30,6 @@ import java.util.stream.Collectors; * Some types are redundant across these metric collectors, but that has debug use-cases as * these data-types are available at different moments of the flow (and typically, one can feed * into the next). - * TODO(b/270403549) - iterate on this in V3+ */ public class CandidatePhaseMetric { @@ -56,10 +55,7 @@ public class CandidatePhaseMetric { private int mProviderQueryStatus = -1; // Indicates if an exception was thrown by this provider, false by default private boolean mHasException = false; - // Indicates the number of total entries available. We can also locally store the entries, but - // cannot emit them in the current split form. TODO(b/271135048) - possibly readjust candidate - // entries. Also, it may be okay to remove this and instead aggregate from inner counts. - // Defaults to -1 + // Indicates the number of total entries available, defaults to -1 private int mNumEntriesTotal = -1; // The count of action entries from this provider, defaults to -1 private int mActionEntryCount = -1; diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java index c80cc24fa455..8f08bb02dfd0 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java @@ -29,11 +29,8 @@ import java.util.List; * Some types are redundant across these metric collectors, but that has debug use-cases as * these data-types are available at different moments of the flow (and typically, one can feed * into the next). - * TODO(b/270403549) - iterate on this in V3+ */ public class ChosenProviderFinalPhaseMetric { - - // TODO(b/270403549) - applies elsewhere, likely removed or replaced w/ some hashed/count index private static final String TAG = "ChosenFinalPhaseMetric"; // The session id associated with this API call, used to unite split emits private int mSessionId = -1; diff --git a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java index 0ecd9cc79e48..5cfb0e7d375b 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java @@ -26,7 +26,6 @@ import java.util.Map; * Some types are redundant across these metric collectors, but that has debug use-cases as * these data-types are available at different moments of the flow (and typically, one can feed * into the next). - * TODO(b/270403549) - iterate on this in V3+ */ public class InitialPhaseMetric { private static final String TAG = "InitialPhaseMetric"; @@ -47,7 +46,6 @@ public class InitialPhaseMetric { private long mCredentialServiceBeginQueryTimeNanoseconds = -1; // Indicates if the origin was specified when making this API request - // TODO(b/271135048) - Emit once metrics approved private boolean mOriginSpecified = false; // Stores the deduped request information, particularly {"req":5}. diff --git a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java index 547c09a625f6..4ecdfef401b6 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java @@ -337,10 +337,6 @@ public class RequestSessionMetric { */ public void logApiCalledAtFinish(int apiStatus) { try { - // TODO (b/270403549) - this browsing phase object is fine but also have a new emit - // For the returned types by authentication entries - i.e. a CandidatePhase During - // Browse - // Possibly think of adding in more atoms for other APIs as well. logApiCalledFinalPhase(mChosenProviderFinalPhaseMetric, mCandidateBrowsingPhaseMetric, apiStatus, ++mSequenceCounter); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 231fee358a2f..675ebd3ddd60 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -39,6 +39,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_DEFAULT_SMS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_DISPLAY; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_FACTORY_RESET; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_FUN; +import static android.Manifest.permission.MANAGE_DEVICE_POLICY_INPUT_METHODS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_KEYGUARD; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCALE; @@ -873,7 +874,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // TODO(b/265683382) remove the flag after rollout. private static final String KEEP_PROFILES_RUNNING_FLAG = "enable_keep_profiles_running"; - private static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = false; + private static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = true; private static final String ENABLE_WORK_PROFILE_TELEPHONY_FLAG = "enable_work_profile_telephony"; @@ -12195,7 +12196,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { + if (isPolicyEngineForFinanceFlagEnabled()) { caller = getCallerIdentity(who, callerPackageName); } else { caller = getCallerIdentity(who); @@ -12205,7 +12206,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { int userId = getProfileParentUserIfRequested( caller.getUserId(), calledOnParentInstance); if (calledOnParentInstance) { - if (!isPermissionCheckFlagEnabled()) { + if (!isPolicyEngineForFinanceFlagEnabled()) { Preconditions.checkCallAuthorization( isProfileOwnerOfOrganizationOwnedDevice(caller)); } @@ -12213,7 +12214,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { "Permitted input methods must allow all input methods or only " + "system input methods when called on the parent instance of an " + "organization-owned device"); - } else if (!isPermissionCheckFlagEnabled()) { + } else if (!isPolicyEngineForFinanceFlagEnabled()) { Preconditions.checkCallAuthorization( isDefaultDeviceOwner(caller) || isProfileOwner(caller)); } @@ -12241,7 +12242,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (getLockObject()) { if (isPolicyEngineForFinanceFlagEnabled()) { - EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackageName); + EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin( + who, MANAGE_DEVICE_POLICY_INPUT_METHODS, + caller.getPackageName(), userId); mDevicePolicyEngine.setLocalPolicy( PolicyDefinition.PERMITTED_INPUT_METHODS, admin, @@ -13436,6 +13439,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { public void setUserRestrictionGlobally(String callerPackage, String key) { final CallerIdentity caller = getCallerIdentity(callerPackage); + EnforcingAdmin admin = enforcePermissionForUserRestriction( + /* who= */ null, + key, + caller.getPackageName(), + UserHandle.USER_ALL + ); + checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_USER_RESTRICTION); if (!isPolicyEngineForFinanceFlagEnabled()) { @@ -13452,13 +13462,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { throw new IllegalArgumentException("Invalid restriction key: " + key); } - EnforcingAdmin admin = enforcePermissionForUserRestriction( - /* who= */ null, - key, - caller.getPackageName(), - UserHandle.USER_ALL - ); - setGlobalUserRestrictionInternal(admin, key, /* enabled= */ true); logUserRestrictionCall(key, /* enabled= */ true, /* parent= */ false, caller); @@ -16371,7 +16374,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // TODO(b/128928355): if this restriction is enforced by multiple DPCs, return // the admin for the calling user. Slogf.w(LOG_TAG, "getEnforcingAdminAndUserDetailsInternal(%d, %s): multiple " - + "sources for restriction %s on user %d", restriction, userId); + + "sources for restriction %s on user %d", + userId, restriction, restriction, userId); result = new Bundle(); result.putInt(Intent.EXTRA_USER_ID, userId); return result; @@ -22839,6 +22843,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { MANAGE_DEVICE_POLICY_DISPLAY, MANAGE_DEVICE_POLICY_FACTORY_RESET, MANAGE_DEVICE_POLICY_FUN, + MANAGE_DEVICE_POLICY_INPUT_METHODS, MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES, MANAGE_DEVICE_POLICY_KEYGUARD, MANAGE_DEVICE_POLICY_LOCALE, @@ -22914,9 +22919,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { MANAGE_DEVICE_POLICY_BLUETOOTH, MANAGE_DEVICE_POLICY_CALLS, MANAGE_DEVICE_POLICY_CAMERA, + MANAGE_DEVICE_POLICY_CERTIFICATES, MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES, MANAGE_DEVICE_POLICY_DISPLAY, MANAGE_DEVICE_POLICY_FACTORY_RESET, + MANAGE_DEVICE_POLICY_INPUT_METHODS, MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES, MANAGE_DEVICE_POLICY_KEYGUARD, MANAGE_DEVICE_POLICY_LOCALE, @@ -22949,7 +22956,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { MANAGE_DEVICE_POLICY_ACROSS_USERS, MANAGE_DEVICE_POLICY_AIRPLANE_MODE, MANAGE_DEVICE_POLICY_APPS_CONTROL, - MANAGE_DEVICE_POLICY_CERTIFICATES, MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE, MANAGE_DEVICE_POLICY_DEFAULT_SMS, MANAGE_DEVICE_POLICY_LOCALE, @@ -23074,11 +23080,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { //Map of Permission to Delegate Scope. private static final HashMap<String, String> DELEGATE_SCOPES = new HashMap<>(); { - DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, DELEGATION_PERMISSION_GRANT); DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_APP_RESTRICTIONS, DELEGATION_APP_RESTRICTIONS); DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL, DELEGATION_BLOCK_UNINSTALL); - DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, DELEGATION_SECURITY_LOGGING); + DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_CERTIFICATES, DELEGATION_CERT_INSTALL); DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_PACKAGE_STATE, DELEGATION_PACKAGE_ACCESS); + DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, DELEGATION_PERMISSION_GRANT); + DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, DELEGATION_SECURITY_LOGGING); } private static final HashMap<String, String> CROSS_USER_PERMISSIONS = diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java index 3a47b476a131..2b57c5927678 100644 --- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java @@ -154,6 +154,7 @@ public class DeviceIdleControllerTest { // Freeze time for testing. long nowElapsed; boolean useMotionSensor = true; + boolean isLocationPrefetchEnabled = true; InjectorForTest(Context ctx) { super(ctx); @@ -223,6 +224,11 @@ public class DeviceIdleControllerTest { } @Override + boolean isLocationPrefetchEnabled() { + return isLocationPrefetchEnabled; + } + + @Override PowerManager getPowerManager() { return mPowerManager; } @@ -991,6 +997,43 @@ public class DeviceIdleControllerTest { } @Test + public void testStepIdleStateLocked_ValidStates_LocationPrefetchDisabled() { + mInjector.locationManager = mLocationManager; + mInjector.isLocationPrefetchEnabled = false; + cleanupDeviceIdleController(); + setupDeviceIdleController(); + doReturn(mock(LocationProvider.class)).when(mLocationManager).getProvider(anyString()); + // Make sure the controller doesn't think there's a wake-from-idle alarm coming soon. + setAlarmSoon(false); + // Set state to INACTIVE. + mDeviceIdleController.becomeActiveLocked("testing", 0); + setChargingOn(false); + setScreenOn(false); + verifyStateConditions(STATE_INACTIVE); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE_PENDING); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_SENSING); + + mDeviceIdleController.stepIdleStateLocked("testing"); + // Prefetch location is off, so SENSING should go straight through to IDLE. + verifyStateConditions(STATE_IDLE); + + // Should just alternate between IDLE and IDLE_MAINTENANCE now. + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE_MAINTENANCE); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE_MAINTENANCE); + } + + @Test public void testStepIdleStateLocked_ValidStates_WithLocationManager_NoProviders() { // Make sure the controller doesn't think there's a wake-from-idle alarm coming soon. setAlarmSoon(false); @@ -1024,6 +1067,46 @@ public class DeviceIdleControllerTest { } @Test + public void testStepIdleStateLocked_ValidStates_WithLocationManager_MissingProviders() { + mInjector.locationManager = mLocationManager; + doReturn(null).when(mLocationManager) + .getProvider(eq(LocationManager.FUSED_PROVIDER)); + doReturn(null).when(mLocationManager) + .getProvider(eq(LocationManager.GPS_PROVIDER)); + doReturn(mock(LocationProvider.class)).when(mLocationManager) + .getProvider(eq(LocationManager.NETWORK_PROVIDER)); + // Make sure the controller doesn't think there's a wake-from-idle alarm coming soon. + setAlarmSoon(false); + // Set state to INACTIVE. + mDeviceIdleController.becomeActiveLocked("testing", 0); + setChargingOn(false); + setScreenOn(false); + verifyStateConditions(STATE_INACTIVE); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE_PENDING); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_SENSING); + + mDeviceIdleController.stepIdleStateLocked("testing"); + // Location manager exists, but the required providers don't exist, + // so SENSING should go straight through to IDLE. + verifyStateConditions(STATE_IDLE); + + // Should just alternate between IDLE and IDLE_MAINTENANCE now. + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE_MAINTENANCE); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE); + + mDeviceIdleController.stepIdleStateLocked("testing"); + verifyStateConditions(STATE_IDLE_MAINTENANCE); + } + + @Test public void testStepIdleStateLocked_ValidStates_WithLocationManager_WithProviders() { mInjector.locationManager = mLocationManager; doReturn(mock(LocationProvider.class)).when(mLocationManager).getProvider(anyString()); diff --git a/services/tests/mockingservicestests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java deleted file mode 100644 index 17fba9f43707..000000000000 --- a/services/tests/mockingservicestests/src/com/android/server/display/RefreshRateSettingsUtilsTest.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.display; - -import static com.android.internal.display.RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.when; - -import android.hardware.display.DisplayManager; -import android.provider.Settings; -import android.testing.TestableContext; -import android.view.Display; - -import androidx.test.filters.SmallTest; -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; - -import com.android.internal.display.RefreshRateSettingsUtils; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class RefreshRateSettingsUtilsTest { - - @Rule - public final TestableContext mContext = new TestableContext( - InstrumentationRegistry.getInstrumentation().getContext()); - - @Mock - private DisplayManager mDisplayManagerMock; - @Mock - private Display mDisplayMock; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mContext.addMockSystemService(DisplayManager.class, mDisplayManagerMock); - - Display.Mode[] modes = new Display.Mode[] { - new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600, - /* refreshRate= */ 60), - new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600, - /* refreshRate= */ 120), - new Display.Mode(/* modeId= */ 0, /* width= */ 800, /* height= */ 600, - /* refreshRate= */ 90) - }; - - when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock); - when(mDisplayMock.getSupportedModes()).thenReturn(modes); - } - - @Test - public void testFindHighestRefreshRateForDefaultDisplay() { - when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(null); - assertEquals(DEFAULT_REFRESH_RATE, - RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext), - /* delta= */ 0); - - when(mDisplayManagerMock.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mDisplayMock); - assertEquals(120, - RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext), - /* delta= */ 0); - } - - @Test - public void testGetMinRefreshRate() { - Settings.System.putInt(mContext.getContentResolver(), - Settings.System.FORCE_PEAK_REFRESH_RATE, -1); - assertEquals(0, RefreshRateSettingsUtils.getMinRefreshRate(mContext), /* delta= */ 0); - - Settings.System.putInt(mContext.getContentResolver(), - Settings.System.FORCE_PEAK_REFRESH_RATE, 0); - assertEquals(0, RefreshRateSettingsUtils.getMinRefreshRate(mContext), /* delta= */ 0); - - Settings.System.putInt(mContext.getContentResolver(), - Settings.System.FORCE_PEAK_REFRESH_RATE, 1); - assertEquals(120, RefreshRateSettingsUtils.getMinRefreshRate(mContext), /* delta= */ 0); - } - - @Test - public void testGetPeakRefreshRate() { - float defaultPeakRefreshRate = 100; - - Settings.System.putInt(mContext.getContentResolver(), Settings.System.SMOOTH_DISPLAY, -1); - assertEquals(defaultPeakRefreshRate, - RefreshRateSettingsUtils.getPeakRefreshRate(mContext, defaultPeakRefreshRate), - /* delta= */ 0); - - Settings.System.putInt(mContext.getContentResolver(), Settings.System.SMOOTH_DISPLAY, 0); - assertEquals(DEFAULT_REFRESH_RATE, - RefreshRateSettingsUtils.getPeakRefreshRate(mContext, defaultPeakRefreshRate), - /* delta= */ 0); - - Settings.System.putInt(mContext.getContentResolver(), Settings.System.SMOOTH_DISPLAY, 1); - assertEquals(120, - RefreshRateSettingsUtils.getPeakRefreshRate(mContext, defaultPeakRefreshRate), - /* delta= */ 0); - } -} diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java new file mode 100644 index 000000000000..eec026ccfc8a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.datatransfer.contextsync; + +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.when; + +import android.platform.test.annotations.Presubmit; +import android.testing.AndroidTestingRunner; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.companion.transport.CompanionTransportManager; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Presubmit +@RunWith(AndroidTestingRunner.class) +public class CrossDeviceSyncControllerTest { + + private CrossDeviceSyncController mCrossDeviceSyncController; + @Mock + private CompanionTransportManager mMockCompanionTransportManager; + @Mock + private CrossDeviceCall mMockCrossDeviceCall; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mCrossDeviceSyncController = new CrossDeviceSyncController( + InstrumentationRegistry.getInstrumentation().getContext(), + mMockCompanionTransportManager); + } + + @Test + public void processTelecomDataFromSync_createCallUpdateMessage_emptyCallsAndRequests() { + final byte[] data = mCrossDeviceSyncController.createCallUpdateMessage(new HashSet<>(), + InstrumentationRegistry.getInstrumentation().getContext().getUserId()); + final CallMetadataSyncData callMetadataSyncData = + mCrossDeviceSyncController.processTelecomDataFromSync(data); + assertWithMessage("Unexpectedly found a call").that( + callMetadataSyncData.getCalls()).isEmpty(); + assertWithMessage("Unexpectedly found a request").that( + callMetadataSyncData.getRequests()).isEmpty(); + } + + @Test + public void processTelecomDataFromSync_createEmptyMessage_emptyCallsAndRequests() { + final byte[] data = CrossDeviceSyncController.createEmptyMessage(); + final CallMetadataSyncData callMetadataSyncData = + mCrossDeviceSyncController.processTelecomDataFromSync(data); + assertWithMessage("Unexpectedly found a call").that( + callMetadataSyncData.getCalls()).isEmpty(); + assertWithMessage("Unexpectedly found a request").that( + callMetadataSyncData.getRequests()).isEmpty(); + } + + @Test + public void processTelecomDataFromSync_createCallUpdateMessage_hasCalls() { + when(mMockCrossDeviceCall.getId()).thenReturn(5L); + final String callerId = "Firstname Lastname"; + when(mMockCrossDeviceCall.getReadableCallerId(anyBoolean())).thenReturn(callerId); + final String appName = "AppName"; + when(mMockCrossDeviceCall.getCallingAppName()).thenReturn(appName); + final String appIcon = "ABCD"; + when(mMockCrossDeviceCall.getCallingAppIcon()).thenReturn(appIcon.getBytes()); + when(mMockCrossDeviceCall.getStatus()).thenReturn(android.companion.Telecom.Call.RINGING); + final Set<Integer> controls = Set.of( + android.companion.Telecom.Call.ACCEPT, + android.companion.Telecom.Call.REJECT, + android.companion.Telecom.Call.SILENCE); + when(mMockCrossDeviceCall.getControls()).thenReturn(controls); + final byte[] data = mCrossDeviceSyncController.createCallUpdateMessage( + new HashSet<>(List.of(mMockCrossDeviceCall)), + InstrumentationRegistry.getInstrumentation().getContext().getUserId()); + final CallMetadataSyncData callMetadataSyncData = + mCrossDeviceSyncController.processTelecomDataFromSync(data); + assertWithMessage("Wrong number of active calls").that( + callMetadataSyncData.getCalls()).hasSize(1); + final CallMetadataSyncData.Call call = + callMetadataSyncData.getCalls().stream().findAny().orElseThrow(); + assertWithMessage("Wrong id").that(call.getId()).isEqualTo(5L); + assertWithMessage("Wrong app icon").that(new String(call.getAppIcon())).isEqualTo(appIcon); + assertWithMessage("Wrong app name").that(call.getAppName()).isEqualTo(appName); + assertWithMessage("Wrong caller id").that(call.getCallerId()).isEqualTo(callerId); + assertWithMessage("Wrong status").that(call.getStatus()) + .isEqualTo(android.companion.Telecom.Call.RINGING); + assertWithMessage("Wrong controls").that(call.getControls()).isEqualTo(controls); + assertWithMessage("Unexpectedly has requests").that( + callMetadataSyncData.getRequests()).isEmpty(); + } + + @Test + public void processTelecomDataFromMessage_createCallControlMessage_hasCallControlRequest() { + final byte[] data = CrossDeviceSyncController.createCallControlMessage( + /* callId= */ 5L, /* status= */ android.companion.Telecom.Call.ACCEPT); + final CallMetadataSyncData callMetadataSyncData = + mCrossDeviceSyncController.processTelecomDataFromSync(data); + assertWithMessage("Wrong number of requests").that( + callMetadataSyncData.getRequests()).hasSize(1); + final CallMetadataSyncData.Call call = + callMetadataSyncData.getRequests().stream().findAny().orElseThrow(); + assertWithMessage("Wrong id").that(call.getId()).isEqualTo(5L); + assertWithMessage("Wrong app icon").that(call.getAppIcon()).isNull(); + assertWithMessage("Wrong app name").that(call.getAppName()).isNull(); + assertWithMessage("Wrong caller id").that(call.getCallerId()).isNull(); + assertWithMessage("Wrong status").that(call.getStatus()) + .isEqualTo(android.companion.Telecom.Call.UNKNOWN_STATUS); + assertWithMessage("Wrong controls").that(call.getControls()) + .isEqualTo(Set.of(android.companion.Telecom.Call.ACCEPT)); + assertWithMessage("Unexpectedly has active calls").that( + callMetadataSyncData.getCalls()).isEmpty(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index e49225216b6b..b2a3a57cccf8 100644 --- a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -84,7 +84,6 @@ import androidx.test.filters.SmallTest; import com.android.internal.R; import com.android.internal.display.BrightnessSynchronizer; -import com.android.internal.display.RefreshRateSettingsUtils; import com.android.internal.os.BackgroundThread; import com.android.internal.util.Preconditions; import com.android.internal.util.test.FakeSettingsProvider; @@ -159,9 +158,6 @@ public class DisplayModeDirectorTest { LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock); LocalServices.removeServiceForTest(DisplayManagerInternal.class); LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); - - clearSmoothDisplaySetting(); - clearForcePeakRefreshRateSetting(); } private DisplayModeDirector createDirectorFromRefreshRateArray( @@ -922,6 +918,7 @@ public class DisplayModeDirectorTest { public void testLockFpsForLowZone() throws Exception { DisplayModeDirector director = createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0); + setPeakRefreshRate(90); director.getSettingsObserver().setDefaultRefreshRate(90); director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); @@ -929,7 +926,6 @@ public class DisplayModeDirectorTest { config.setRefreshRateInLowZone(90); config.setLowDisplayBrightnessThresholds(new int[] { 10 }); config.setLowAmbientBrightnessThresholds(new int[] { 20 }); - config.setDefaultPeakRefreshRate(90); Sensor lightSensor = createLightSensor(); SensorManager sensorManager = createMockSensorManager(lightSensor); @@ -980,6 +976,7 @@ public class DisplayModeDirectorTest { public void testLockFpsForHighZone() throws Exception { DisplayModeDirector director = createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0); + setPeakRefreshRate(90 /*fps*/); director.getSettingsObserver().setDefaultRefreshRate(90); director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); @@ -987,7 +984,6 @@ public class DisplayModeDirectorTest { config.setRefreshRateInHighZone(60); config.setHighDisplayBrightnessThresholds(new int[] { 255 }); config.setHighAmbientBrightnessThresholds(new int[] { 8000 }); - config.setDefaultPeakRefreshRate(90); Sensor lightSensor = createLightSensor(); SensorManager sensorManager = createMockSensorManager(lightSensor); @@ -1035,123 +1031,16 @@ public class DisplayModeDirectorTest { } @Test - public void testSmoothDisplay() { - float defaultRefreshRate = 60; - int defaultPeakRefreshRate = 100; - DisplayModeDirector director = - createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0); - director.getSettingsObserver().setDefaultRefreshRate(defaultRefreshRate); - director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); - - final FakeDeviceConfig config = mInjector.getDeviceConfig(); - config.setDefaultPeakRefreshRate(defaultPeakRefreshRate); - - Sensor lightSensor = createLightSensor(); - SensorManager sensorManager = createMockSensorManager(lightSensor); - director.start(sensorManager); - - // Default value of the setting - - Vote vote = director.getVote(Display.DEFAULT_DISPLAY, - Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ - defaultPeakRefreshRate); - vote = director.getVote(Display.DEFAULT_DISPLAY, - Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ - Float.POSITIVE_INFINITY); - vote = director.getVote(Display.DEFAULT_DISPLAY, - Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ - defaultRefreshRate); - - setSmoothDisplayEnabled(false); - - vote = director.getVote(Display.DEFAULT_DISPLAY, - Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ - RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE); - vote = director.getVote(Display.DEFAULT_DISPLAY, - Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ - Float.POSITIVE_INFINITY); - vote = director.getVote(Display.DEFAULT_DISPLAY, - Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ - defaultRefreshRate); - - setSmoothDisplayEnabled(true); - - vote = director.getVote(Display.DEFAULT_DISPLAY, - Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ - RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext)); - vote = director.getVote(Display.DEFAULT_DISPLAY, - Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ - Float.POSITIVE_INFINITY); - vote = director.getVote(Display.DEFAULT_DISPLAY, - Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ - defaultRefreshRate); - } - - @Test - public void testForcePeakRefreshRate() { - float defaultRefreshRate = 60; - DisplayModeDirector director = - createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0); - director.getSettingsObserver().setDefaultRefreshRate(defaultRefreshRate); - director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); - - Sensor lightSensor = createLightSensor(); - SensorManager sensorManager = createMockSensorManager(lightSensor); - director.start(sensorManager); - - setForcePeakRefreshRateEnabled(false); - setSmoothDisplayEnabled(false); - - Vote vote = director.getVote(Display.DEFAULT_DISPLAY, - Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ - RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE); - vote = director.getVote(Display.DEFAULT_DISPLAY, - Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, - /* frameRateHigh= */ Float.POSITIVE_INFINITY); - vote = director.getVote(Display.DEFAULT_DISPLAY, - Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ - defaultRefreshRate); - - setForcePeakRefreshRateEnabled(true); - - vote = director.getVote(Display.DEFAULT_DISPLAY, - Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ - RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext)); - vote = director.getVote(Display.DEFAULT_DISPLAY, - Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ - RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay(mContext), - /* frameRateHigh= */ Float.POSITIVE_INFINITY); - vote = director.getVote(Display.DEFAULT_DISPLAY, - Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE); - assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0, /* frameRateHigh= */ - defaultRefreshRate); - } - - @Test public void testSensorRegistration() { // First, configure brightness zones or DMD won't register for sensor data. final FakeDeviceConfig config = mInjector.getDeviceConfig(); config.setRefreshRateInHighZone(60); config.setHighDisplayBrightnessThresholds(new int[] { 255 }); config.setHighAmbientBrightnessThresholds(new int[] { 8000 }); - config.setDefaultPeakRefreshRate(90); DisplayModeDirector director = createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0); + setPeakRefreshRate(90 /*fps*/); director.getSettingsObserver().setDefaultRefreshRate(90); director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); @@ -2527,10 +2416,10 @@ public class DisplayModeDirectorTest { config.setRefreshRateInHighZone(60); config.setHighDisplayBrightnessThresholds(new int[] { 255 }); config.setHighAmbientBrightnessThresholds(new int[] { 8000 }); - config.setDefaultPeakRefreshRate(90); DisplayModeDirector director = createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0); + setPeakRefreshRate(90 /*fps*/); director.getSettingsObserver().setDefaultRefreshRate(90); director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); @@ -2828,30 +2717,10 @@ public class DisplayModeDirectorTest { listener.onDisplayChanged(DISPLAY_ID); } - private void setSmoothDisplayEnabled(boolean enabled) { - Settings.System.putInt(mContext.getContentResolver(), Settings.System.SMOOTH_DISPLAY, - enabled ? 1 : 0); - mInjector.notifySmoothDisplaySettingChanged(); - waitForIdleSync(); - } - - private void clearSmoothDisplaySetting() { - Settings.System.putInt(mContext.getContentResolver(), Settings.System.SMOOTH_DISPLAY, -1); - mInjector.notifySmoothDisplaySettingChanged(); - waitForIdleSync(); - } - - private void setForcePeakRefreshRateEnabled(boolean enabled) { - Settings.System.putInt(mContext.getContentResolver(), - Settings.System.FORCE_PEAK_REFRESH_RATE, enabled ? 1 : 0); - mInjector.notifyForcePeakRefreshRateSettingChanged(); - waitForIdleSync(); - } - - private void clearForcePeakRefreshRateSetting() { - Settings.System.putInt(mContext.getContentResolver(), - Settings.System.FORCE_PEAK_REFRESH_RATE, -1); - mInjector.notifySmoothDisplaySettingChanged(); + private void setPeakRefreshRate(float fps) { + Settings.System.putFloat(mContext.getContentResolver(), Settings.System.PEAK_REFRESH_RATE, + fps); + mInjector.notifyPeakRefreshRateChanged(); waitForIdleSync(); } @@ -2900,8 +2769,7 @@ public class DisplayModeDirectorTest { private final Display mDisplay; private boolean mDisplayInfoValid = true; private ContentObserver mBrightnessObserver; - private ContentObserver mSmoothDisplaySettingObserver; - private ContentObserver mForcePeakRefreshRateSettingObserver; + private ContentObserver mPeakRefreshRateObserver; FakesInjector() { mDeviceConfig = new FakeDeviceConfig(); @@ -2918,15 +2786,9 @@ public class DisplayModeDirectorTest { } @Override - public void registerSmoothDisplayObserver(@NonNull ContentResolver cr, - @NonNull ContentObserver observer) { - mSmoothDisplaySettingObserver = observer; - } - - @Override - public void registerForcePeakRefreshRateObserver(@NonNull ContentResolver cr, + public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr, @NonNull ContentObserver observer) { - mForcePeakRefreshRateSettingObserver = observer; + mPeakRefreshRateObserver = observer; } @Override @@ -2976,17 +2838,10 @@ public class DisplayModeDirectorTest { ApplicationProvider.getApplicationContext().getResources()); } - void notifySmoothDisplaySettingChanged() { - if (mSmoothDisplaySettingObserver != null) { - mSmoothDisplaySettingObserver.dispatchChange(false /*selfChange*/, - SMOOTH_DISPLAY_URI); - } - } - - void notifyForcePeakRefreshRateSettingChanged() { - if (mForcePeakRefreshRateSettingObserver != null) { - mForcePeakRefreshRateSettingObserver.dispatchChange(false /*selfChange*/, - FORCE_PEAK_REFRESH_RATE_URI); + void notifyPeakRefreshRateChanged() { + if (mPeakRefreshRateObserver != null) { + mPeakRefreshRateObserver.dispatchChange(false /*selfChange*/, + PEAK_REFRESH_RATE_URI); } } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java index a446e109c921..c53a7a708cfd 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java @@ -18,6 +18,7 @@ package com.android.server.hdmi; import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_DESTINATION; import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER; +import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER_LONG; import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER_SHORT; import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_SOURCE; import static com.android.server.hdmi.HdmiCecMessageValidator.OK; @@ -145,11 +146,12 @@ public class HdmiCecMessageValidatorTest { @Test public void isValid_systemAudioModeStatus() { assertMessageValidity("40:7E:00").isEqualTo(OK); - assertMessageValidity("40:7E:01:01").isEqualTo(OK); + assertMessageValidity("40:7E:01").isEqualTo(OK); assertMessageValidity("0F:7E:00").isEqualTo(ERROR_DESTINATION); assertMessageValidity("F0:7E").isEqualTo(ERROR_SOURCE); assertMessageValidity("40:7E").isEqualTo(ERROR_PARAMETER_SHORT); + assertMessageValidity("40:7E:01:1F:28").isEqualTo(ERROR_PARAMETER_LONG); assertMessageValidity("40:7E:02").isEqualTo(ERROR_PARAMETER); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index 4dd5e94541e4..fd6eb9286651 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -761,6 +761,13 @@ public class HdmiControlServiceTest { assertThat(mHdmiControlServiceSpy.handleCecCommand(message)) .isEqualTo(Constants.ABORT_INVALID_OPERAND); + + // Validating ERROR_PARAMETER_LONG will generate ABORT_INVALID_OPERAND. + // Taken from HdmiCecMessageValidatorTest#isValid_systemAudioModeStatus + HdmiCecMessage systemAudioModeStatus = HdmiUtils.buildMessage("40:7E:01:1F:28"); + + assertThat(mHdmiControlServiceSpy.handleCecCommand(systemAudioModeStatus)) + .isEqualTo(Constants.ABORT_INVALID_OPERAND); } @Test diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index 5636795520e9..3513557d8374 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -202,9 +202,11 @@ class TestPhoneWindowManager { .canonicalToCurrentPackageNames(any()); } catch (PackageManager.NameNotFoundException ignored) { } - doReturn(mTelecomManager).when(mContext).getSystemService(eq(Context.TELECOM_SERVICE)); - doReturn(mNotificationManager).when(mContext) - .getSystemService(eq(NotificationManager.class)); + doReturn(false).when(mTelecomManager).isInCall(); + doReturn(false).when(mTelecomManager).isRinging(); + doReturn(mTelecomManager).when(mPhoneWindowManager).getTelecommService(); + doNothing().when(mNotificationManager).silenceNotificationSound(); + doReturn(mNotificationManager).when(mPhoneWindowManager).getNotificationService(); doReturn(mVibrator).when(mContext).getSystemService(eq(Context.VIBRATOR_SERVICE)); final PowerManager.WakeLock wakeLock = mock(PowerManager.WakeLock.class); @@ -235,6 +237,7 @@ class TestPhoneWindowManager { doNothing().when(mPhoneWindowManager).screenTurnedOn(anyInt()); doNothing().when(mPhoneWindowManager).startedWakingUp(anyInt(), anyInt()); doNothing().when(mPhoneWindowManager).finishedWakingUp(anyInt(), anyInt()); + doNothing().when(mPhoneWindowManager).lockNow(any()); mPhoneWindowManager.init(new TestInjector(mContext, mWindowManagerFuncsImpl)); mPhoneWindowManager.systemReady(); @@ -249,6 +252,7 @@ class TestPhoneWindowManager { void tearDown() { mHandlerThread.quitSafely(); LocalServices.removeServiceForTest(InputMethodManagerInternal.class); + Mockito.reset(mPhoneWindowManager); mMockitoSession.finishMocking(); } @@ -322,6 +326,7 @@ class TestPhoneWindowManager { void overrideDisplayState(int state) { doReturn(state).when(mDisplay).getState(); + doReturn(state == STATE_ON).when(mDisplayPolicy).isAwake(); Mockito.reset(mPowerManager); } @@ -388,6 +393,7 @@ class TestPhoneWindowManager { } void assertDreamRequest() { + waitForIdle(); verify(mDreamManagerInternal).requestDream(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java index dfc453f0f1b6..d173ce9d522a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -1134,7 +1134,7 @@ public class RootWindowContainerTests extends WindowTestsBase { TaskChangeNotificationController controller = mAtm.getTaskChangeNotificationController(); spyOn(controller); mWm.mRoot.lockAllProfileTasks(profileUserId); - verify(controller).notifyTaskProfileLocked(any()); + verify(controller).notifyTaskProfileLocked(any(), eq(profileUserId)); // Create the work lock activity on top of the task final ActivityRecord workLockActivity = new ActivityBuilder(mAtm).setTask(task).build(); @@ -1144,7 +1144,7 @@ public class RootWindowContainerTests extends WindowTestsBase { // Make sure the listener won't be notified again. clearInvocations(controller); mWm.mRoot.lockAllProfileTasks(profileUserId); - verify(controller, never()).notifyTaskProfileLocked(any()); + verify(controller, never()).notifyTaskProfileLocked(any(), anyInt()); } /** diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index b062e6b08e00..913535e06a21 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -296,7 +296,10 @@ public class SoundTriggerService extends SystemService { // Helper to add session logger to the capacity limited detached list. // If we are at capacity, remove the oldest, and retry - private void addDetachedSessionLogger(EventLogger logger) { + private void detachSessionLogger(EventLogger logger) { + if (!mSessionEventLoggers.remove(logger)) { + return; + } // Attempt to push to the top of the queue while (!mDetachedSessionEventLoggers.offerFirst(logger)) { // Remove the oldest element, if one still exists @@ -872,8 +875,7 @@ public class SoundTriggerService extends SystemService { private void detach() { mSoundTriggerHelper.detach(); - mSessionEventLoggers.remove(mEventLogger); - addDetachedSessionLogger(mEventLogger); + detachSessionLogger(mEventLogger); } private void enforceCallingPermission(String permission) { @@ -1659,8 +1661,7 @@ public class SoundTriggerService extends SystemService { private void detachInternal() { mEventLogger.enqueue(new SessionEvent(Type.DETACH, null)); - mSessionEventLoggers.remove(mEventLogger); - addDetachedSessionLogger(mEventLogger); + detachSessionLogger(mEventLogger); mSoundTriggerHelper.detach(); } } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java index 13fe14caa1f9..00cedd77414e 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java @@ -78,7 +78,7 @@ public class SoundTriggerMiddlewarePermission implements ISoundTriggerMiddleware public @NonNull SoundTriggerModuleDescriptor[] listModules() { Identity identity = getIdentity(); - enforcePermissionsForPreflight(identity); + enforcePermissionForPreflight(mContext, identity, CAPTURE_AUDIO_HOTWORD); return mDelegate.listModules(); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 486945d9159e..edaaf3fbbccc 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -913,8 +913,10 @@ final class HotwordDetectionConnection { } // Handle case where all hotword detector sessions are destroyed with only the visual // detector session left - if (mDetectorSessions.size() == 1 - && mDetectorSessions.get(0) instanceof VisualQueryDetectorSession) { + boolean allHotwordDetectionServiceSessionsRemoved = mDetectorSessions.size() == 0 + || (mDetectorSessions.size() == 1 && mDetectorSessions.get(0) + instanceof VisualQueryDetectorSession); + if (allHotwordDetectionServiceSessionsRemoved) { unbindHotwordDetectionService(); } } |