Merge "Use app info to load app data instead of component info" into udc-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index f779b4d..b0b3b1f 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 @@
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 @@
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 @@
mAppStateTracker = mInjector.getAppStateTracker(context,
AppSchedulingModuleThread.get().getLooper());
LocalServices.addService(AppStateTracker.class, mAppStateTracker);
+ mIsLocationPrefetchEnabled = mInjector.isLocationPrefetchEnabled();
mUseMotionSensor = mInjector.useMotionSensor();
}
@@ -2602,8 +2610,7 @@
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 @@
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;
+ 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 {
- 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;
+ 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 @@
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/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 577260e..8a4b464 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1560,7 +1560,8 @@
jobStatus.getJob().getMinLatencyMillis(),
jobStatus.getEstimatedNetworkDownloadBytes(),
jobStatus.getEstimatedNetworkUploadBytes(),
- jobStatus.getWorkCount());
+ jobStatus.getWorkCount(),
+ ActivityManager.processStateAmToProto(mUidProcStates.get(jobStatus.getUid())));
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -1935,6 +1936,7 @@
* {@code incomingJob} is non-null, it replaces {@code cancelled} in the store of
* currently scheduled jobs.
*/
+ @GuardedBy("mLock")
private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob,
@JobParameters.StopReason int reason, int internalReasonCode, String debugReason) {
if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
@@ -1986,7 +1988,8 @@
cancelled.getJob().getMinLatencyMillis(),
cancelled.getEstimatedNetworkDownloadBytes(),
cancelled.getEstimatedNetworkUploadBytes(),
- cancelled.getWorkCount());
+ cancelled.getWorkCount(),
+ ActivityManager.processStateAmToProto(mUidProcStates.get(cancelled.getUid())));
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index fb36cde..f95df44 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -25,6 +25,7 @@
import android.annotation.BytesLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.Notification;
import android.app.compat.CompatChanges;
@@ -476,7 +477,8 @@
job.getJob().getMinLatencyMillis(),
job.getEstimatedNetworkDownloadBytes(),
job.getEstimatedNetworkUploadBytes(),
- job.getWorkCount());
+ job.getWorkCount(),
+ ActivityManager.processStateAmToProto(mService.getUidProcState(job.getUid())));
final String sourcePackage = job.getSourcePackageName();
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
final String componentPackage = job.getServiceComponent().getPackageName();
@@ -1447,7 +1449,9 @@
completedJob.getJob().getMinLatencyMillis(),
completedJob.getEstimatedNetworkDownloadBytes(),
completedJob.getEstimatedNetworkUploadBytes(),
- completedJob.getWorkCount());
+ completedJob.getWorkCount(),
+ ActivityManager
+ .processStateAmToProto(mService.getUidProcState(completedJob.getUid())));
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
getId());
diff --git a/config/dirty-image-objects b/config/dirty-image-objects
index dfd091c..2584610e 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/text/format/DateFormat;
-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/telephony/TelephonyRegistryManager;
+Landroid/text/DynamicLayout;
Landroid/text/TextUtils;
-Landroid/text/TextWatcher;
-Landroid/transition/ChangeClipBounds;
-Landroid/transition/ChangeImageTransform;
-Landroid/transition/ChangeTransform;
+Landroid/text/format/DateFormat;
+Landroid/text/format/DateUtils;
+Landroid/text/method/ArrowKeyMovementMethod;
+Landroid/text/method/LinkMovementMethod;
+Landroid/text/method/SingleLineTransformationMethod;
+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/api/test-current.txt b/core/api/test-current.txt
index 332c53c..d97f718 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3632,18 +3632,12 @@
}
public final class AutofillManager {
- method public void clearAutofillRequestCallback();
- method @RequiresPermission(android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS) public void setAutofillRequestCallback(@NonNull java.util.concurrent.Executor, @NonNull android.view.autofill.AutofillRequestCallback);
field public static final String ANY_HINT = "any";
field public static final int FLAG_SMART_SUGGESTION_OFF = 0; // 0x0
field public static final int FLAG_SMART_SUGGESTION_SYSTEM = 1; // 0x1
field public static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 120000; // 0x1d4c0
}
- public interface AutofillRequestCallback {
- method public void onFillRequest(@Nullable android.view.inputmethod.InlineSuggestionsRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback);
- }
-
}
package android.view.contentcapture {
@@ -3767,11 +3761,6 @@
method @NonNull public static android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(@NonNull android.widget.inline.InlinePresentationSpec, @NonNull String, @Nullable String[], @NonNull String, boolean);
}
- public static final class InlineSuggestionsRequest.Builder {
- method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setClientSupported(boolean);
- method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setServiceSupported(boolean);
- }
-
public final class InlineSuggestionsResponse implements android.os.Parcelable {
method @NonNull public static android.view.inputmethod.InlineSuggestionsResponse newInlineSuggestionsResponse(@NonNull java.util.List<android.view.inputmethod.InlineSuggestion>);
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 521bf05..e2ef005 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -771,6 +771,7 @@
/**
* The set of flags for process capability.
+ * Keep it in sync with ProcessCapability in atoms.proto.
* @hide
*/
@IntDef(flag = true, prefix = { "PROCESS_CAPABILITY_" }, value = {
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 61d6787..2ae7216 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -403,8 +403,11 @@
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 36e5762..3c6ff28 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -137,7 +137,7 @@
* 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/IUidObserver.aidl b/core/java/android/app/IUidObserver.aidl
index 0c920f1..60c2eed 100644
--- a/core/java/android/app/IUidObserver.aidl
+++ b/core/java/android/app/IUidObserver.aidl
@@ -58,8 +58,9 @@
* Report a proc oom adj change associated with a uid.
*
* @param uid The uid for which the state change is being reported.
+ * @param adj The minimum OOM adj among all processes with this uid.
*/
- void onUidProcAdjChanged(int uid);
+ void onUidProcAdjChanged(int uid, int adj);
// =============== End of transactions used on native side as well ============================
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 99a7fa2..705b5ee 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -407,7 +407,7 @@
}
private static void checkPendingIntent(int flags, @NonNull Intent intent,
- @NonNull Context context) {
+ @NonNull Context context, boolean isActivityResultType) {
final boolean isFlagImmutableSet = (flags & PendingIntent.FLAG_IMMUTABLE) != 0;
final boolean isFlagMutableSet = (flags & PendingIntent.FLAG_MUTABLE) != 0;
final String packageName = context.getPackageName();
@@ -428,11 +428,12 @@
throw new IllegalArgumentException(msg);
}
- // For apps with target SDK < U, warn that creation or retrieval of a mutable
- // implicit PendingIntent will be blocked from target SDK U onwards for security
- // reasons. The block itself happens on the server side, but this warning has to
- // stay here to preserve the client side stack trace for app developers.
- if (isNewMutableDisallowedImplicitPendingIntent(flags, intent)
+ // For apps with target SDK < U, warn that creation or retrieval of a mutable implicit
+ // PendingIntent that is not of type {@link ActivityManager#INTENT_SENDER_ACTIVITY_RESULT}
+ // will be blocked from target SDK U onwards for security reasons. The block itself
+ // happens on the server side, but this warning has to stay here to preserve the client
+ // side stack trace for app developers.
+ if (isNewMutableDisallowedImplicitPendingIntent(flags, intent, isActivityResultType)
&& !Compatibility.isChangeEnabled(BLOCK_MUTABLE_IMPLICIT_PENDING_INTENT)) {
String msg = "New mutable implicit PendingIntent: pkg=" + packageName
+ ", action=" + intent.getAction()
@@ -445,7 +446,13 @@
/** @hide */
public static boolean isNewMutableDisallowedImplicitPendingIntent(int flags,
- @NonNull Intent intent) {
+ @NonNull Intent intent, boolean isActivityResultType) {
+ if (isActivityResultType) {
+ // Pending intents of type {@link ActivityManager#INTENT_SENDER_ACTIVITY_RESULT}
+ // should be ignored as they are intrinsically tied to a target which means they
+ // are already explicit.
+ return false;
+ }
boolean isFlagNoCreateSet = (flags & PendingIntent.FLAG_NO_CREATE) != 0;
boolean isFlagMutableSet = (flags & PendingIntent.FLAG_MUTABLE) != 0;
boolean isImplicit = (intent.getComponent() == null) && (intent.getPackage() == null);
@@ -534,7 +541,7 @@
@NonNull Intent intent, int flags, Bundle options, UserHandle user) {
String packageName = context.getPackageName();
String resolvedType = intent.resolveTypeIfNeeded(context.getContentResolver());
- checkPendingIntent(flags, intent, context);
+ checkPendingIntent(flags, intent, context, /* isActivityResultType */ false);
try {
intent.migrateExtraStreamToClipData(context);
intent.prepareToLeaveProcess(context);
@@ -668,7 +675,7 @@
intents[i].migrateExtraStreamToClipData(context);
intents[i].prepareToLeaveProcess(context);
resolvedTypes[i] = intents[i].resolveTypeIfNeeded(context.getContentResolver());
- checkPendingIntent(flags, intents[i], context);
+ checkPendingIntent(flags, intents[i], context, /* isActivityResultType */ false);
}
try {
IIntentSender target =
@@ -721,7 +728,7 @@
Intent intent, int flags, UserHandle userHandle) {
String packageName = context.getPackageName();
String resolvedType = intent.resolveTypeIfNeeded(context.getContentResolver());
- checkPendingIntent(flags, intent, context);
+ checkPendingIntent(flags, intent, context, /* isActivityResultType */ false);
try {
intent.prepareToLeaveProcess(context);
IIntentSender target =
@@ -800,7 +807,7 @@
Intent intent, int flags, int serviceKind) {
String packageName = context.getPackageName();
String resolvedType = intent.resolveTypeIfNeeded(context.getContentResolver());
- checkPendingIntent(flags, intent, context);
+ checkPendingIntent(flags, intent, context, /* isActivityResultType */ false);
try {
intent.prepareToLeaveProcess(context);
IIntentSender target =
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index 774bc06..0290cee 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -154,8 +154,18 @@
}
@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/app/UidObserver.java b/core/java/android/app/UidObserver.java
index 9e92807..5196624 100644
--- a/core/java/android/app/UidObserver.java
+++ b/core/java/android/app/UidObserver.java
@@ -41,7 +41,7 @@
}
@Override
- public void onUidProcAdjChanged(int uid) {
+ public void onUidProcAdjChanged(int uid, int adj) {
}
@Override
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index a34a50c..be1d8b8 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -213,9 +213,17 @@
.resizeBitmapArea(MAX_WALLPAPER_EXTRACTION_AREA)
.generate();
} else {
+ // in any case, always use between 5 and 128 clusters
+ int minClusters = 5;
+ int maxClusters = 128;
+
+ // if the bitmap is very small, use bitmapArea/16 clusters instead of 128
+ int minPixelsPerCluster = 16;
+ int numberOfColors = Math.max(minClusters,
+ Math.min(maxClusters, bitmapArea / minPixelsPerCluster));
palette = Palette
.from(bitmap, new CelebiQuantizer())
- .maximumColorCount(128)
+ .maximumColorCount(numberOfColors)
.resizeBitmapArea(MAX_WALLPAPER_EXTRACTION_AREA)
.generate();
}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 6592019..f673304 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -822,6 +822,10 @@
*/
@TestApi
public boolean isLockscreenLiveWallpaperEnabled() {
+ return isLockscreenLiveWallpaperEnabledHelper();
+ }
+
+ private static boolean isLockscreenLiveWallpaperEnabledHelper() {
if (sGlobals == null) {
sIsLockscreenLiveWallpaperEnabled = SystemProperties.getBoolean(
"persist.wm.debug.lockscreen_live_wallpaper", false);
@@ -2757,7 +2761,7 @@
public static InputStream openDefaultWallpaper(Context context, @SetWallpaperFlags int which) {
final String whichProp;
final int defaultResId;
- if (which == FLAG_LOCK && !sIsLockscreenLiveWallpaperEnabled) {
+ if (which == FLAG_LOCK && !isLockscreenLiveWallpaperEnabledHelper()) {
/* Factory-default lock wallpapers are not yet supported
whichProp = PROP_LOCK_WALLPAPER;
defaultResId = com.android.internal.R.drawable.default_lock_wallpaper;
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 1ba84c5..d802b46 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -502,6 +502,8 @@
boolean hasSystemFeature(String name, int version);
+ List<String> getInitialNonStoppedSystemPackages();
+
void enterSafeMode();
@UnsupportedAppUsage
boolean isSafeMode();
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index de66f05..56f6f82 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2072,6 +2072,25 @@
return new InstallInfo(result);
}
+ /**
+ * Parse a single APK file passed as an FD to get install relevant information about
+ * the package wrapped in {@link InstallInfo}.
+ * @throws PackageParsingException if the package source file(s) provided is(are) not valid,
+ * or the parser isn't able to parse the supplied source(s).
+ * @hide
+ */
+ @NonNull
+ public InstallInfo readInstallInfo(@NonNull ParcelFileDescriptor pfd,
+ @Nullable String debugPathName, int flags) throws PackageParsingException {
+ final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+ final ParseResult<PackageLite> result = ApkLiteParseUtils.parseMonolithicPackageLite(input,
+ pfd.getFileDescriptor(), debugPathName, flags);
+ if (result.isError()) {
+ throw new PackageParsingException(result.getErrorCode(), result.getErrorMessage());
+ }
+ return new InstallInfo(result);
+ }
+
// (b/239722738) This class serves as a bridge between the PackageLite class, which
// is a hidden class, and the consumers of this class. (e.g. InstallInstalling.java)
// This is a part of an effort to remove dependency on hidden APIs and use SystemAPIs or
@@ -2125,6 +2144,21 @@
public long calculateInstalledSize(@NonNull SessionParams params) throws IOException {
return InstallLocationUtils.calculateInstalledSize(mPkg, params.abiOverride);
}
+
+ /**
+ * @param params {@link SessionParams} of the installation
+ * @param pfd of an APK opened for read
+ * @return Total disk space occupied by an application after installation.
+ * Includes the size of the raw APKs, possibly unpacked resources, raw dex metadata files,
+ * and all relevant native code.
+ * @throws IOException when size of native binaries cannot be calculated.
+ * @hide
+ */
+ public long calculateInstalledSize(@NonNull SessionParams params,
+ @NonNull ParcelFileDescriptor pfd) throws IOException {
+ return InstallLocationUtils.calculateInstalledSize(mPkg, params.abiOverride,
+ pfd.getFileDescriptor());
+ }
}
/**
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 295df5c..be40143 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -50,7 +50,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
-import java.lang.IllegalArgumentException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -285,6 +284,12 @@
*/
public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103;
+ /**
+ * The maximum length of Shortcut ID. IDs will be truncated at this limit.
+ * @hide
+ */
+ public static final int MAX_ID_LENGTH = 1000;
+
/** @hide */
@IntDef(prefix = { "DISABLED_REASON_" }, value = {
DISABLED_REASON_NOT_DISABLED,
@@ -477,8 +482,7 @@
private ShortcutInfo(Builder b) {
mUserId = b.mContext.getUserId();
-
- mId = Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided");
+ mId = getSafeId(Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided"));
// Note we can't do other null checks here because SM.updateShortcuts() takes partial
// information.
@@ -584,6 +588,14 @@
return ret;
}
+ @NonNull
+ private static String getSafeId(@NonNull String id) {
+ if (id.length() > MAX_ID_LENGTH) {
+ return id.substring(0, MAX_ID_LENGTH);
+ }
+ return id;
+ }
+
/**
* Throws if any of the mandatory fields is not set.
*
@@ -2342,7 +2354,8 @@
final ClassLoader cl = getClass().getClassLoader();
mUserId = source.readInt();
- mId = source.readString8();
+ mId = getSafeId(Preconditions.checkStringNotEmpty(source.readString8(),
+ "Shortcut ID must be provided"));
mPackageName = source.readString8();
mActivity = source.readParcelable(cl, android.content.ComponentName.class);
mFlags = source.readInt();
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index a4339d4..d209b35 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -109,7 +109,7 @@
}
/**
- * Parse lightweight details about a single APK files.
+ * Parse lightweight details about a single APK file.
*/
public static ParseResult<PackageLite> parseMonolithicPackageLite(ParseInput input,
File packageFile, int flags) {
@@ -135,6 +135,33 @@
}
/**
+ * Parse lightweight details about a single APK file passed as an FD.
+ */
+ public static ParseResult<PackageLite> parseMonolithicPackageLite(ParseInput input,
+ FileDescriptor packageFd, String debugPathName, int flags) {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
+ try {
+ final ParseResult<ApkLite> result = parseApkLite(input, packageFd, debugPathName,
+ flags);
+ if (result.isError()) {
+ return input.error(result);
+ }
+
+ final ApkLite baseApk = result.getResult();
+ final String packagePath = debugPathName;
+ return input.success(
+ new PackageLite(packagePath, baseApk.getPath(), baseApk, null /* splitNames */,
+ null /* isFeatureSplits */, null /* usesSplitNames */,
+ null /* configForSplit */, null /* splitApkPaths */,
+ null /* splitRevisionCodes */, baseApk.getTargetSdkVersion(),
+ null /* requiredSplitTypes */, null, /* splitTypes */
+ baseApk.isAllowUpdateOwnership()));
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+ }
+
+ /**
* Parse lightweight details about a directory of APKs.
*
* @param packageDir is the folder that contains split apks for a regular app
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
index 09d2db8..9ebb058 100644
--- a/core/java/android/credentials/ui/RequestInfo.java
+++ b/core/java/android/credentials/ui/RequestInfo.java
@@ -30,6 +30,8 @@
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 @@
@Nullable
private final CreateCredentialRequest mCreateCredentialRequest;
+ @NonNull
+ private final List<String> mDefaultProviderIds;
+
@Nullable
private final GetCredentialRequest mGetCredentialRequest;
@@ -83,7 +88,8 @@
@NonNull String appPackageName) {
return new RequestInfo(
token, TYPE_CREATE, appPackageName, createCredentialRequest, null,
- /*hasPermissionToOverrideDefault=*/ false);
+ /*hasPermissionToOverrideDefault=*/ false,
+ /*defaultProviderIds=*/ new ArrayList<>());
}
/**
@@ -94,10 +100,11 @@
@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 @@
@NonNull String appPackageName) {
return new RequestInfo(
token, TYPE_GET, appPackageName, null, getCredentialRequest,
- /*hasPermissionToOverrideDefault=*/ false);
+ /*hasPermissionToOverrideDefault=*/ false,
+ /*defaultProviderIds=*/ new ArrayList<>());
}
@@ -149,6 +157,20 @@
}
/**
+ * 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 @@
@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 @@
mCreateCredentialRequest = createCredentialRequest;
mGetCredentialRequest = getCredentialRequest;
mHasPermissionToOverrideDefault = in.readBoolean();
+ mDefaultProviderIds = in.createStringArrayList();
}
@Override
@@ -198,6 +223,7 @@
dest.writeTypedObject(mCreateCredentialRequest, flags);
dest.writeTypedObject(mGetCredentialRequest, flags);
dest.writeBoolean(mHasPermissionToOverrideDefault);
+ dest.writeStringList(mDefaultProviderIds);
}
@Override
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 888047d..21fe686 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -32,10 +32,12 @@
import android.media.soundtrigger.RecognitionEvent;
import android.media.soundtrigger.RecognitionMode;
import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
import android.os.ParcelFileDescriptor;
-import android.system.ErrnoException;
import android.os.SharedMemory;
+import android.system.ErrnoException;
import java.nio.ByteBuffer;
import java.util.Arrays;
@@ -219,36 +221,40 @@
return new SoundTrigger.ConfidenceLevel(apiLevel.userId, apiLevel.levelPercent);
}
- public static SoundTrigger.RecognitionEvent aidl2apiRecognitionEvent(
- int modelHandle, int captureSession, RecognitionEvent aidlEvent) {
+ public static SoundTrigger.RecognitionEvent aidl2apiRecognitionEvent(int modelHandle,
+ int captureSession, RecognitionEventSys aidlEvent) {
+ RecognitionEvent recognitionEvent = aidlEvent.recognitionEvent;
// The API recognition event doesn't allow for a null audio format, even though it doesn't
// always make sense. We thus replace it with a default.
- AudioFormat audioFormat = aidl2apiAudioFormatWithDefault(aidlEvent.audioConfig,
+ AudioFormat audioFormat = aidl2apiAudioFormatWithDefault(recognitionEvent.audioConfig,
true /*isInput*/);
- // TODO(b/265852186) propagate a timestamp from aidl interfaces
- return new SoundTrigger.GenericRecognitionEvent(aidlEvent.status, modelHandle,
- aidlEvent.captureAvailable, captureSession, aidlEvent.captureDelayMs,
- aidlEvent.capturePreambleMs, aidlEvent.triggerInData, audioFormat, aidlEvent.data,
- aidlEvent.recognitionStillActive, -1 /* halEventReceivedMillis */);
+ return new SoundTrigger.GenericRecognitionEvent(recognitionEvent.status, modelHandle,
+ recognitionEvent.captureAvailable, captureSession, recognitionEvent.captureDelayMs,
+ recognitionEvent.capturePreambleMs, recognitionEvent.triggerInData, audioFormat,
+ recognitionEvent.data,
+ recognitionEvent.recognitionStillActive, aidlEvent.halEventReceivedMillis);
}
public static SoundTrigger.RecognitionEvent aidl2apiPhraseRecognitionEvent(
- int modelHandle, int captureSession,
- PhraseRecognitionEvent aidlEvent) {
+ int modelHandle, int captureSession, PhraseRecognitionEventSys aidlEvent) {
+ PhraseRecognitionEvent recognitionEvent = aidlEvent.phraseRecognitionEvent;
SoundTrigger.KeyphraseRecognitionExtra[] apiExtras =
- new SoundTrigger.KeyphraseRecognitionExtra[aidlEvent.phraseExtras.length];
- for (int i = 0; i < aidlEvent.phraseExtras.length; ++i) {
- apiExtras[i] = aidl2apiPhraseRecognitionExtra(aidlEvent.phraseExtras[i]);
+ new SoundTrigger.KeyphraseRecognitionExtra[recognitionEvent.phraseExtras.length];
+ for (int i = 0; i < recognitionEvent.phraseExtras.length; ++i) {
+ apiExtras[i] = aidl2apiPhraseRecognitionExtra(recognitionEvent.phraseExtras[i]);
}
// The API recognition event doesn't allow for a null audio format, even though it doesn't
// always make sense. We thus replace it with a default.
- AudioFormat audioFormat = aidl2apiAudioFormatWithDefault(aidlEvent.common.audioConfig,
+ AudioFormat audioFormat = aidl2apiAudioFormatWithDefault(
+ recognitionEvent.common.audioConfig,
true /*isInput*/);
- // TODO(b/265852186) propagate a timestamp from aidl interfaces
- return new SoundTrigger.KeyphraseRecognitionEvent(aidlEvent.common.status, modelHandle,
- aidlEvent.common.captureAvailable, captureSession, aidlEvent.common.captureDelayMs,
- aidlEvent.common.capturePreambleMs, aidlEvent.common.triggerInData, audioFormat,
- aidlEvent.common.data, apiExtras, -1 /* halEventReceivedMillis */);
+ return new SoundTrigger.KeyphraseRecognitionEvent(recognitionEvent.common.status,
+ modelHandle,
+ recognitionEvent.common.captureAvailable, captureSession,
+ recognitionEvent.common.captureDelayMs,
+ recognitionEvent.common.capturePreambleMs, recognitionEvent.common.triggerInData,
+ audioFormat,
+ recognitionEvent.common.data, apiExtras, aidlEvent.halEventReceivedMillis);
}
// In case of a null input returns a non-null valid output.
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
index 37c5213..5cdbe23 100644
--- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
@@ -22,13 +22,13 @@
import android.media.permission.ClearCallingIdentityContext;
import android.media.permission.Identity;
import android.media.permission.SafeCloseable;
-import android.media.soundtrigger.PhraseRecognitionEvent;
import android.media.soundtrigger.PhraseSoundModel;
-import android.media.soundtrigger.RecognitionEvent;
import android.media.soundtrigger.SoundModel;
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
@@ -398,7 +398,7 @@
}
@Override
- public synchronized void onRecognition(int handle, RecognitionEvent event,
+ public synchronized void onRecognition(int handle, RecognitionEventSys event,
int captureSession)
throws RemoteException {
Message m = mHandler.obtainMessage(EVENT_RECOGNITION,
@@ -407,7 +407,7 @@
}
@Override
- public synchronized void onPhraseRecognition(int handle, PhraseRecognitionEvent event,
+ public synchronized void onPhraseRecognition(int handle, PhraseRecognitionEventSys event,
int captureSession)
throws RemoteException {
Message m = mHandler.obtainMessage(EVENT_RECOGNITION,
diff --git a/core/java/android/net/metrics/WakeupStats.java b/core/java/android/net/metrics/WakeupStats.java
index bb36536..fac747c 100644
--- a/core/java/android/net/metrics/WakeupStats.java
+++ b/core/java/android/net/metrics/WakeupStats.java
@@ -80,18 +80,20 @@
break;
}
- switch (ev.dstHwAddr.getAddressType()) {
- case MacAddress.TYPE_UNICAST:
- l2UnicastCount++;
- break;
- case MacAddress.TYPE_MULTICAST:
- l2MulticastCount++;
- break;
- case MacAddress.TYPE_BROADCAST:
- l2BroadcastCount++;
- break;
- default:
- break;
+ if (ev.dstHwAddr != null) {
+ switch (ev.dstHwAddr.getAddressType()) {
+ case MacAddress.TYPE_UNICAST:
+ l2UnicastCount++;
+ break;
+ case MacAddress.TYPE_MULTICAST:
+ l2MulticastCount++;
+ break;
+ case MacAddress.TYPE_BROADCAST:
+ l2BroadcastCount++;
+ break;
+ default:
+ break;
+ }
}
increment(ethertypes, ev.ethertype);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 24e28e9..5bcbaa1 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -65,7 +65,6 @@
import android.view.WindowManager.LayoutParams;
import com.android.internal.R;
-import com.android.internal.util.FrameworkStatsLog;
import java.io.IOException;
import java.lang.annotation.Retention;
@@ -2533,38 +2532,6 @@
}
/**
- * Returns the enum defined in the statsd UserLifecycleJourneyReported atom corresponding to the
- * user type.
- * @hide
- */
- public static int getUserTypeForStatsd(@NonNull String userType) {
- switch (userType) {
- case USER_TYPE_FULL_SYSTEM:
- return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SYSTEM;
- case USER_TYPE_FULL_SECONDARY:
- return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY;
- case USER_TYPE_FULL_GUEST:
- return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_GUEST;
- case USER_TYPE_FULL_DEMO:
- return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_DEMO;
- case USER_TYPE_FULL_RESTRICTED:
- return FrameworkStatsLog
- .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_RESTRICTED;
- case USER_TYPE_PROFILE_MANAGED:
- return FrameworkStatsLog
- .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__PROFILE_MANAGED;
- case USER_TYPE_SYSTEM_HEADLESS:
- return FrameworkStatsLog
- .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__SYSTEM_HEADLESS;
- case USER_TYPE_PROFILE_CLONE:
- return FrameworkStatsLog
- .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__PROFILE_CLONE;
- default:
- return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
- }
- }
-
- /**
* @hide
* @deprecated Use {@link #isRestrictedProfile()}
*/
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 79e7574..329a2fa5 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4677,16 +4677,22 @@
"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
@@ -7169,6 +7175,13 @@
public static final String CREDENTIAL_SERVICE = "credential_service";
/**
+ * The currently selected primary credential service flattened ComponentName.
+ *
+ * @hide
+ */
+ public static final String CREDENTIAL_SERVICE_PRIMARY = "credential_service_primary";
+
+ /**
* The currently selected autofill service flattened ComponentName.
* @hide
*/
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index 4a848dd..8afae74 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -97,8 +97,6 @@
*/
public static final @RequestFlags int FLAG_VIEW_NOT_FOCUSED = 0x10;
- // The flag value 0x20 has been defined in AutofillManager.
-
/**
* Indicates the request supports fill dialog presentation for the fields, the
* system will send the request when the activity just started.
diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index 1a1df6f..751c675 100644
--- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -75,12 +75,13 @@
/**
* 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 @@
/**
* 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 @@
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 53a5fd5..cf2e6a6 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -18,7 +18,6 @@
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.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 @@
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 @@
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 @@
}
));
}
- 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 @@
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 6061a0f..7822dde 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -1063,21 +1063,6 @@
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/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index a69af24..470c280 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -62,9 +62,7 @@
*/
public static final int SOURCE_ARBITRARY_RECTANGLE = 3;
- private final IBinder mOwner;
- private final int mIndex;
- private final @InsetsType int mType;
+ private final int mId;
/**
* The selection of the starting rectangle to be converted into source frame.
@@ -122,30 +120,30 @@
* @param type the {@link InsetsType}.
* @see InsetsSource#createId(Object, int, int)
*/
- public InsetsFrameProvider(IBinder owner, @IntRange(from = 0, to = 2047) int index,
+ public InsetsFrameProvider(Object owner, @IntRange(from = 0, to = 2047) int index,
@InsetsType int type) {
- if (index < 0 || index >= 2048) {
- throw new IllegalArgumentException();
- }
-
- // This throws IllegalArgumentException if the type is not valid.
- WindowInsets.Type.indexOf(type);
-
- mOwner = owner;
- mIndex = index;
- mType = type;
+ mId = InsetsSource.createId(owner, index, type);
}
- public IBinder getOwner() {
- return mOwner;
+ /**
+ * Returns an unique integer which identifies the insets source.
+ */
+ public int getId() {
+ return mId;
}
+ /**
+ * Returns the index specified in {@link #InsetsFrameProvider(IBinder, int, int)}.
+ */
public int getIndex() {
- return mIndex;
+ return InsetsSource.getIndex(mId);
}
+ /**
+ * Returns the {@link InsetsType} specified in {@link #InsetsFrameProvider(IBinder, int, int)}.
+ */
public int getType() {
- return mType;
+ return InsetsSource.getType(mId);
}
public InsetsFrameProvider setSource(int source) {
@@ -211,9 +209,9 @@
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("InsetsFrameProvider: {");
- sb.append("owner=").append(mOwner);
- sb.append(", index=").append(mIndex);
- sb.append(", type=").append(WindowInsets.Type.toString(mType));
+ sb.append("id=#").append(Integer.toHexString(mId));
+ sb.append(", index=").append(getIndex());
+ sb.append(", type=").append(WindowInsets.Type.toString(getType()));
sb.append(", source=").append(sourceToString(mSource));
sb.append(", flags=[").append(InsetsSource.flagsToString(mFlags)).append("]");
if (mInsetsSize != null) {
@@ -244,9 +242,7 @@
}
public InsetsFrameProvider(Parcel in) {
- mOwner = in.readStrongBinder();
- mIndex = in.readInt();
- mType = in.readInt();
+ mId = in.readInt();
mSource = in.readInt();
mFlags = in.readInt();
mInsetsSize = in.readTypedObject(Insets.CREATOR);
@@ -256,9 +252,7 @@
@Override
public void writeToParcel(Parcel out, int flags) {
- out.writeStrongBinder(mOwner);
- out.writeInt(mIndex);
- out.writeInt(mType);
+ out.writeInt(mId);
out.writeInt(mSource);
out.writeInt(mFlags);
out.writeTypedObject(mInsetsSize, flags);
@@ -267,7 +261,7 @@
}
public boolean idEquals(InsetsFrameProvider o) {
- return Objects.equals(mOwner, o.mOwner) && mIndex == o.mIndex && mType == o.mType;
+ return mId == o.mId;
}
@Override
@@ -279,8 +273,7 @@
return false;
}
final InsetsFrameProvider other = (InsetsFrameProvider) o;
- return Objects.equals(mOwner, other.mOwner) && mIndex == other.mIndex
- && mType == other.mType && mSource == other.mSource && mFlags == other.mFlags
+ return mId == other.mId && mSource == other.mSource && mFlags == other.mFlags
&& Objects.equals(mInsetsSize, other.mInsetsSize)
&& Arrays.equals(mInsetsSizeOverrides, other.mInsetsSizeOverrides)
&& Objects.equals(mArbitraryRectangle, other.mArbitraryRectangle);
@@ -288,7 +281,7 @@
@Override
public int hashCode() {
- return Objects.hash(mOwner, mIndex, mType, mSource, mFlags, mInsetsSize,
+ return Objects.hash(mId, mSource, mFlags, mInsetsSize,
Arrays.hashCode(mInsetsSizeOverrides), mArbitraryRectangle);
}
@@ -319,7 +312,7 @@
protected InsetsSizeOverride(Parcel in) {
mWindowType = in.readInt();
- mInsetsSize = in.readParcelable(null, Insets.class);
+ mInsetsSize = in.readTypedObject(Insets.CREATOR);
}
public InsetsSizeOverride(int windowType, Insets insetsSize) {
@@ -354,7 +347,7 @@
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mWindowType);
- out.writeParcelable(mInsetsSize, flags);
+ out.writeTypedObject(mInsetsSize, flags);
}
@Override
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index bd48771..114f4ed 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -271,7 +271,7 @@
* @param index An owner may have multiple sources with the same type. For example, the system
* server might have multiple display cutout sources. This is used to identify
* which one is which. The value must be in a range of [0, 2047].
- * @param type The {@link WindowInsets.Type.InsetsType type} of the source.
+ * @param type The {@link InsetsType type} of the source.
* @return a unique integer as the identifier.
*/
public static int createId(Object owner, @IntRange(from = 0, to = 2047) int index,
@@ -282,11 +282,29 @@
// owner takes top 16 bits;
// index takes 11 bits since the 6th bit;
// type takes bottom 5 bits.
- return (((owner != null ? owner.hashCode() : 1) % (1 << 16)) << 16)
+ return ((System.identityHashCode(owner) % (1 << 16)) << 16)
+ (index << 5)
+ WindowInsets.Type.indexOf(type);
}
+ /**
+ * Gets the index from the ID.
+ *
+ * @see #createId(Object, int, int)
+ */
+ public static int getIndex(int id) {
+ return (id % (1 << 16)) >> 5;
+ }
+
+ /**
+ * Gets the {@link InsetsType} from the ID.
+ *
+ * @see #createId(Object, int, int)
+ */
+ public static int getType(int id) {
+ return 1 << (id % 32);
+ }
+
public static String flagsToString(@Flags int flags) {
final StringJoiner joiner = new StringJoiner(" ");
if ((flags & FLAG_SUPPRESS_SCRIM) != 0) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8f20e2d..153bfde 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -11325,7 +11325,7 @@
// to sync the same frame in the same BBQ. That shouldn't be possible, but
// if it did happen, invoke markSyncReady so the active SSG doesn't get
// stuck.
- Log.e(mTag, "Unable to syncNextTransaction. Possibly something else is"
+ Log.w(mTag, "Unable to syncNextTransaction. Possibly something else is"
+ " trying to sync?");
surfaceSyncGroup.markSyncReady();
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index f7b7d33..6ff4b74 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -16,7 +16,6 @@
package android.view.autofill;
-import static android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS;
import static android.service.autofill.FillRequest.FLAG_IME_SHOWING;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
@@ -30,7 +29,6 @@
import static android.view.autofill.Helper.toList;
import android.accessibilityservice.AccessibilityServiceInfo;
-import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -52,21 +50,16 @@
import android.content.pm.ResolveInfo;
import android.graphics.Rect;
import android.metrics.LogMaker;
-import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
-import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
-import android.os.ICancellationSignal;
import android.os.Looper;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.SystemClock;
import android.service.autofill.AutofillService;
-import android.service.autofill.FillCallback;
import android.service.autofill.FillEventHistory;
-import android.service.autofill.IFillCallback;
import android.service.autofill.UserData;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -87,7 +80,6 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;
import android.view.accessibility.AccessibilityWindowInfo;
-import android.view.inputmethod.InlineSuggestionsRequest;
import android.view.inputmethod.InputMethodManager;
import android.widget.CheckBox;
import android.widget.DatePicker;
@@ -187,12 +179,6 @@
* shows an autofill save UI if the value of savable views have changed. If the user selects the
* option to Save, the current value of the views is then sent to the autofill service.
*
- * <p>There is another choice for the application to provide it's datasets to the Autofill framework
- * by setting an {@link AutofillRequestCallback} through
- * {@link #setAutofillRequestCallback(Executor, AutofillRequestCallback)}. The application can use
- * its callback instead of the default {@link AutofillService}. See
- * {@link AutofillRequestCallback} for more details.
- *
* <h3 id="additional-notes">Additional notes</h3>
*
* <p>It is safe to call <code>AutofillManager</code> methods from any thread.
@@ -326,7 +312,6 @@
/** @hide */ public static final int FLAG_ADD_CLIENT_DEBUG = 0x2;
/** @hide */ public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4;
/** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY = 0x8;
- /** @hide */ public static final int FLAG_ENABLED_CLIENT_SUGGESTIONS = 0x20;
// NOTE: flag below is used by the session start receiver only, hence it can have values above
/** @hide */ public static final int RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY = 0x1;
@@ -653,11 +638,6 @@
@GuardedBy("mLock")
private boolean mEnabledForAugmentedAutofillOnly;
- @GuardedBy("mLock")
- @Nullable private AutofillRequestCallback mAutofillRequestCallback;
- @GuardedBy("mLock")
- @Nullable private Executor mRequestCallbackExecutor;
-
/**
* Indicates whether there is already a field to do a fill request after
* the activity started.
@@ -1497,14 +1477,22 @@
// to PCC classification service.
if (AutofillFeatureFlags.isAutofillPccClassificationEnabled()) {
synchronized (mLock) {
- final boolean clientAdded = tryAddServiceClientIfNeededLocked();
- if (clientAdded){
- startSessionLocked(/* id= */ AutofillId.NO_AUTOFILL_ID,
- /* bounds= */ null, /* value= */ null, /* flags= */ FLAG_PCC_DETECTION);
- } else {
- if (sVerbose) {
- Log.v(TAG, "not starting session: no service client");
+ // If session has already been created, that'd mean we already have issued the
+ // detection request previously. It is possible in cases like autofocus that this
+ // method isn't invoked, so the server should still handle such cases where fill
+ // request comes in but PCC Detection hasn't been triggered. There is no benefit to
+ // trigger PCC Detection separately in those cases.
+ if (!isActiveLocked()) {
+ final boolean clientAdded = tryAddServiceClientIfNeededLocked();
+ if (clientAdded) {
+ startSessionLocked(/* id= */ AutofillId.NO_AUTOFILL_ID, /* bounds= */ null,
+ /* value= */ null, /* flags= */ FLAG_PCC_DETECTION);
+ } else {
+ if (sVerbose) {
+ Log.v(TAG, "not starting session: no service client");
+ }
}
+
}
}
}
@@ -2338,44 +2326,6 @@
return new AutofillId(parent.getAutofillViewId(), virtualId);
}
- /**
- * Sets the client's suggestions callback for autofill.
- *
- * @see AutofillRequestCallback
- *
- * @param executor specifies the thread upon which the callbacks will be invoked.
- * @param callback which handles autofill request to provide client's suggestions.
- *
- * @hide
- */
- @TestApi
- @RequiresPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS)
- public void setAutofillRequestCallback(@NonNull @CallbackExecutor Executor executor,
- @NonNull AutofillRequestCallback callback) {
- if (mContext.checkSelfPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Requires PROVIDE_OWN_AUTOFILL_SUGGESTIONS permission!");
- }
-
- synchronized (mLock) {
- mRequestCallbackExecutor = executor;
- mAutofillRequestCallback = callback;
- }
- }
-
- /**
- * clears the client's suggestions callback for autofill.
- *
- * @hide
- */
- @TestApi
- public void clearAutofillRequestCallback() {
- synchronized (mLock) {
- mRequestCallbackExecutor = null;
- mAutofillRequestCallback = null;
- }
- }
-
@GuardedBy("mLock")
private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds,
@NonNull AutofillValue value, int flags) {
@@ -2436,13 +2386,6 @@
}
}
- if (mAutofillRequestCallback != null) {
- if (sDebug) {
- Log.d(TAG, "startSession with the client suggestions provider");
- }
- flags |= FLAG_ENABLED_CLIENT_SUGGESTIONS;
- }
-
mService.startSession(client.autofillClientGetActivityToken(),
mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
mCallback != null, flags, clientActivity,
@@ -2796,28 +2739,6 @@
}
}
- private void onFillRequest(InlineSuggestionsRequest request,
- CancellationSignal cancellationSignal, FillCallback callback) {
- final AutofillRequestCallback autofillRequestCallback;
- final Executor executor;
- synchronized (mLock) {
- autofillRequestCallback = mAutofillRequestCallback;
- executor = mRequestCallbackExecutor;
- }
- if (autofillRequestCallback != null && executor != null) {
- final long ident = Binder.clearCallingIdentity();
- try {
- executor.execute(() ->
- autofillRequestCallback.onFillRequest(
- request, cancellationSignal, callback));
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- } else {
- callback.onSuccess(null);
- }
- }
-
/** @hide */
public static final int SET_STATE_FLAG_ENABLED = 0x01;
/** @hide */
@@ -4374,23 +4295,6 @@
}
@Override
- public void requestFillFromClient(int id, InlineSuggestionsRequest request,
- IFillCallback callback) {
- final AutofillManager afm = mAfm.get();
- if (afm != null) {
- ICancellationSignal transport = CancellationSignal.createTransport();
- try {
- callback.onCancellable(transport);
- } catch (RemoteException e) {
- Slog.w(TAG, "Error requesting a cancellation", e);
- }
-
- afm.onFillRequest(request, CancellationSignal.fromTransport(transport),
- new FillCallback(callback, id));
- }
- }
-
- @Override
public void notifyFillDialogTriggerIds(List<AutofillId> ids) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
diff --git a/core/java/android/view/autofill/AutofillRequestCallback.java b/core/java/android/view/autofill/AutofillRequestCallback.java
deleted file mode 100644
index 10a088b..0000000
--- a/core/java/android/view/autofill/AutofillRequestCallback.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.autofill;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.TestApi;
-import android.os.CancellationSignal;
-import android.service.autofill.FillCallback;
-import android.view.inputmethod.InlineSuggestionsRequest;
-
-/**
- * <p>This class is used to provide some input suggestions to the Autofill framework.
- *
- * <P>When the user is requested to input something, Autofill will try to query input suggestions
- * for the user choosing. If the application want to provide some internal input suggestions,
- * implements this callback and register via
- * {@link AutofillManager#setAutofillRequestCallback(java.util.concurrent.Executor,
- * AutofillRequestCallback)}. Autofill will callback the
- * {@link #onFillRequest(InlineSuggestionsRequest, CancellationSignal, FillCallback)} to request
- * input suggestions.
- *
- * <P>To make sure the callback to take effect, must register before the autofill session starts.
- * If the autofill session is started, calls {@link AutofillManager#cancel()} to finish current
- * session, and then the callback will be used at the next restarted session.
- *
- * <P>To create a {@link android.service.autofill.FillResponse}, application should fetch
- * {@link AutofillId}s from its view structure. Below is an example:
- * <pre class="prettyprint">
- * AutofillId usernameId = findViewById(R.id.username).getAutofillId();
- * AutofillId passwordId = findViewById(R.id.password).getAutofillId();
- * </pre>
- * To learn more about creating a {@link android.service.autofill.FillResponse}, read
- * <a href="/guide/topics/text/autofill-services#fill">Fill out client views</a>.
- *
- * <P>To fallback to the default {@link android.service.autofill.AutofillService}, just respond
- * a null of the {@link android.service.autofill.FillResponse}. And then Autofill will do a fill
- * request with the default {@link android.service.autofill.AutofillService}. Or clear the callback
- * from {@link AutofillManager} via {@link AutofillManager#clearAutofillRequestCallback()}. If the
- * client would like to keep no suggestions for the field, respond with an empty
- * {@link android.service.autofill.FillResponse} which has no dataset.
- *
- * <P>IMPORTANT: This should not be used for displaying anything other than input suggestions, or
- * the keyboard may choose to block your app from the inline strip.
- *
- * @hide
- */
-@TestApi
-public interface AutofillRequestCallback {
- /**
- * Called by the Android system to decide if a screen can be autofilled by the callback.
- *
- * @param inlineSuggestionsRequest the {@link InlineSuggestionsRequest request} to handle if
- * currently inline suggestions are supported and can be displayed.
- * @param cancellationSignal signal for observing cancellation requests. The system will use
- * this to notify you that the fill result is no longer needed and you should stop
- * handling this fill request in order to save resources.
- * @param callback object used to notify the result of the request.
- */
- void onFillRequest(@Nullable InlineSuggestionsRequest inlineSuggestionsRequest,
- @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback);
-}
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index 2e5967c..51afe4c 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -24,11 +24,9 @@
import android.content.IntentSender;
import android.graphics.Rect;
import android.os.IBinder;
-import android.service.autofill.IFillCallback;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutofillWindowPresenter;
-import android.view.inputmethod.InlineSuggestionsRequest;
import android.view.KeyEvent;
import com.android.internal.os.IResultReceiver;
@@ -144,12 +142,6 @@
void requestShowSoftInput(in AutofillId id);
/**
- * Requests to determine if a screen can be autofilled by the client app.
- */
- void requestFillFromClient(int id, in InlineSuggestionsRequest request,
- in IFillCallback callback);
-
- /**
* Notifies autofill ids that require to show the fill dialog.
*/
void notifyFillDialogTriggerIds(in List<AutofillId> ids);
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
index 77a2b5b..581feca 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
@@ -112,22 +112,6 @@
private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec;
/**
- * Whether the IME supports inline suggestions from the default Autofill service that
- * provides the input view.
- *
- * Note: The default value is {@code true}.
- */
- private boolean mServiceSupported;
-
- /**
- * Whether the IME supports inline suggestions from the application that provides the
- * input view.
- *
- * Note: The default value is {@code true}.
- */
- private boolean mClientSupported;
-
- /**
* @hide
* @see {@link #mHostInputToken}.
*/
@@ -221,15 +205,9 @@
return Bundle.EMPTY;
}
- private static boolean defaultServiceSupported() {
- return true;
- }
-
- private static boolean defaultClientSupported() {
- return true;
- }
-
- /** @hide */
+ /**
+ * @hide
+ */
abstract static class BaseBuilder {
abstract Builder setInlinePresentationSpecs(
@NonNull List<android.widget.inline.InlinePresentationSpec> specs);
@@ -241,25 +219,14 @@
abstract Builder setHostDisplayId(int value);
}
- /** @hide */
- public boolean isServiceSupported() {
- return mServiceSupported;
- }
- /** @hide */
- public boolean isClientSupported() {
- return mClientSupported;
- }
-
-
-
- // Code below generated by codegen v1.0.22.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+ // $ codegen $ANDROID_BUILD_TOP/./frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
@@ -275,9 +242,7 @@
@NonNull Bundle extras,
@Nullable IBinder hostInputToken,
int hostDisplayId,
- @Nullable InlinePresentationSpec inlineTooltipPresentationSpec,
- boolean serviceSupported,
- boolean clientSupported) {
+ @Nullable InlinePresentationSpec inlineTooltipPresentationSpec) {
this.mMaxSuggestionCount = maxSuggestionCount;
this.mInlinePresentationSpecs = inlinePresentationSpecs;
com.android.internal.util.AnnotationValidations.validate(
@@ -294,8 +259,6 @@
this.mHostInputToken = hostInputToken;
this.mHostDisplayId = hostDisplayId;
this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec;
- this.mServiceSupported = serviceSupported;
- this.mClientSupported = clientSupported;
onConstructed();
}
@@ -379,9 +342,7 @@
}
/**
- * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response.
- *
- * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec)
+ * Specifies the UI specification for the inline suggestion tooltip in the response.
*/
@DataClass.Generated.Member
public @Nullable InlinePresentationSpec getInlineTooltipPresentationSpec() {
@@ -402,9 +363,7 @@
"extras = " + mExtras + ", " +
"hostInputToken = " + mHostInputToken + ", " +
"hostDisplayId = " + mHostDisplayId + ", " +
- "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec + ", " +
- "serviceSupported = " + mServiceSupported + ", " +
- "clientSupported = " + mClientSupported +
+ "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec +
" }";
}
@@ -428,9 +387,7 @@
&& extrasEquals(that.mExtras)
&& java.util.Objects.equals(mHostInputToken, that.mHostInputToken)
&& mHostDisplayId == that.mHostDisplayId
- && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec)
- && mServiceSupported == that.mServiceSupported
- && mClientSupported == that.mClientSupported;
+ && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec);
}
@Override
@@ -448,8 +405,6 @@
_hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken);
_hash = 31 * _hash + mHostDisplayId;
_hash = 31 * _hash + java.util.Objects.hashCode(mInlineTooltipPresentationSpec);
- _hash = 31 * _hash + Boolean.hashCode(mServiceSupported);
- _hash = 31 * _hash + Boolean.hashCode(mClientSupported);
return _hash;
}
@@ -460,8 +415,6 @@
// void parcelFieldName(Parcel dest, int flags) { ... }
int flg = 0;
- if (mServiceSupported) flg |= 0x100;
- if (mClientSupported) flg |= 0x200;
if (mHostInputToken != null) flg |= 0x20;
if (mInlineTooltipPresentationSpec != null) flg |= 0x80;
dest.writeInt(flg);
@@ -487,11 +440,9 @@
// static FieldType unparcelFieldName(Parcel in) { ... }
int flg = in.readInt();
- boolean serviceSupported = (flg & 0x100) != 0;
- boolean clientSupported = (flg & 0x200) != 0;
int maxSuggestionCount = in.readInt();
List<InlinePresentationSpec> inlinePresentationSpecs = new ArrayList<>();
- in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader(), android.widget.inline.InlinePresentationSpec.class);
+ in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader());
String hostPackageName = in.readString();
LocaleList supportedLocales = (LocaleList) in.readTypedObject(LocaleList.CREATOR);
Bundle extras = in.readBundle();
@@ -515,8 +466,6 @@
this.mHostInputToken = hostInputToken;
this.mHostDisplayId = hostDisplayId;
this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec;
- this.mServiceSupported = serviceSupported;
- this.mClientSupported = clientSupported;
onConstructed();
}
@@ -550,8 +499,6 @@
private @Nullable IBinder mHostInputToken;
private int mHostDisplayId;
private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec;
- private boolean mServiceSupported;
- private boolean mClientSupported;
private long mBuilderFieldsSet = 0L;
@@ -684,9 +631,7 @@
}
/**
- * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response.
- *
- * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec)s
+ * Specifies the UI specification for the inline suggestion tooltip in the response.
*/
@DataClass.Generated.Member
public @NonNull Builder setInlineTooltipPresentationSpec(@NonNull InlinePresentationSpec value) {
@@ -696,44 +641,10 @@
return this;
}
- /**
- * Whether the IME supports inline suggestions from the default Autofill service that
- * provides the input view.
- *
- * Note: The default value is {@code true}.
- *
- * @hide
- */
- @TestApi
- @DataClass.Generated.Member
- public @NonNull Builder setServiceSupported(boolean value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x100;
- mServiceSupported = value;
- return this;
- }
-
- /**
- * Whether the IME supports inline suggestions from the application that provides the
- * input view.
- *
- * Note: The default value is {@code true}.
- *
- * @hide
- */
- @TestApi
- @DataClass.Generated.Member
- public @NonNull Builder setClientSupported(boolean value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x200;
- mClientSupported = value;
- return this;
- }
-
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull InlineSuggestionsRequest build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x400; // Mark builder used
+ mBuilderFieldsSet |= 0x100; // Mark builder used
if ((mBuilderFieldsSet & 0x1) == 0) {
mMaxSuggestionCount = defaultMaxSuggestionCount();
@@ -756,12 +667,6 @@
if ((mBuilderFieldsSet & 0x80) == 0) {
mInlineTooltipPresentationSpec = defaultInlineTooltipPresentationSpec();
}
- if ((mBuilderFieldsSet & 0x100) == 0) {
- mServiceSupported = defaultServiceSupported();
- }
- if ((mBuilderFieldsSet & 0x200) == 0) {
- mClientSupported = defaultClientSupported();
- }
InlineSuggestionsRequest o = new InlineSuggestionsRequest(
mMaxSuggestionCount,
mInlinePresentationSpecs,
@@ -770,14 +675,12 @@
mExtras,
mHostInputToken,
mHostDisplayId,
- mInlineTooltipPresentationSpec,
- mServiceSupported,
- mClientSupported);
+ mInlineTooltipPresentationSpec);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x400) != 0) {
+ if ((mBuilderFieldsSet & 0x100) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -785,10 +688,10 @@
}
@DataClass.Generated(
- time = 1615798784918L,
- codegenVersion = "1.0.22",
+ time = 1682382296877L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java",
- inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate boolean mServiceSupported\nprivate boolean mClientSupported\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic void setHostInputToken(android.os.IBinder)\nprivate boolean extrasEquals(android.os.Bundle)\nprivate void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic void setHostDisplayId(int)\nprivate void onConstructed()\npublic void filterContentTypes()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nprivate static android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nprivate static boolean defaultServiceSupported()\nprivate static boolean defaultClientSupported()\npublic boolean isServiceSupported()\npublic boolean isClientSupported()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
+ inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic void setHostInputToken(android.os.IBinder)\nprivate boolean extrasEquals(android.os.Bundle)\nprivate void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic void setHostDisplayId(int)\nprivate void onConstructed()\npublic void filterContentTypes()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nprivate static android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 8f270f5..62f3c90 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -412,4 +412,32 @@
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/ScreenCapture.java b/core/java/android/window/ScreenCapture.java
index 95451a9..fa7f577 100644
--- a/core/java/android/window/ScreenCapture.java
+++ b/core/java/android/window/ScreenCapture.java
@@ -198,17 +198,21 @@
* Create ScreenshotHardwareBuffer from an existing HardwareBuffer object.
*
* @param hardwareBuffer The existing HardwareBuffer object
- * @param namedColorSpace Integer value of a named color space {@link ColorSpace.Named}
+ * @param dataspace Dataspace describing the content.
+ * {@see android.hardware.DataSpace}
* @param containsSecureLayers Indicates whether this graphic buffer contains captured
* contents of secure layers, in which case the screenshot
* should not be persisted.
* @param containsHdrLayers Indicates whether this graphic buffer contains HDR content.
*/
private static ScreenshotHardwareBuffer createFromNative(HardwareBuffer hardwareBuffer,
- int namedColorSpace, boolean containsSecureLayers, boolean containsHdrLayers) {
- ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.values()[namedColorSpace]);
+ int dataspace, boolean containsSecureLayers, boolean containsHdrLayers) {
+ ColorSpace colorSpace = ColorSpace.getFromDataSpace(dataspace);
return new ScreenshotHardwareBuffer(
- hardwareBuffer, colorSpace, containsSecureLayers, containsHdrLayers);
+ hardwareBuffer,
+ colorSpace != null ? colorSpace : ColorSpace.get(ColorSpace.Named.SRGB),
+ containsSecureLayers,
+ containsHdrLayers);
}
public ColorSpace getColorSpace() {
@@ -271,8 +275,8 @@
public final boolean mAllowProtected;
public final long mUid;
public final boolean mGrayscale;
-
final SurfaceControl[] mExcludeLayers;
+ public final boolean mHintForSeamlessTransition;
private CaptureArgs(CaptureArgs.Builder<? extends CaptureArgs.Builder<?>> builder) {
mPixelFormat = builder.mPixelFormat;
@@ -284,6 +288,7 @@
mUid = builder.mUid;
mGrayscale = builder.mGrayscale;
mExcludeLayers = builder.mExcludeLayers;
+ mHintForSeamlessTransition = builder.mHintForSeamlessTransition;
}
private CaptureArgs(Parcel in) {
@@ -305,6 +310,7 @@
} else {
mExcludeLayers = null;
}
+ mHintForSeamlessTransition = in.readBoolean();
}
/** Release any layers if set using {@link Builder#setExcludeLayers(SurfaceControl[])}. */
@@ -352,6 +358,7 @@
private long mUid = -1;
private boolean mGrayscale;
private SurfaceControl[] mExcludeLayers;
+ private boolean mHintForSeamlessTransition;
/**
* Construct a new {@link CaptureArgs} with the set parameters. The builder remains
@@ -449,6 +456,21 @@
}
/**
+ * Set whether the screenshot will be used in a system animation.
+ * This hint is used for picking the "best" colorspace for the screenshot, in particular
+ * for mixing HDR and SDR content.
+ * E.g., hintForSeamlessTransition is false, then a colorspace suitable for file
+ * encoding, such as BT2100, may be chosen. Otherwise, then the display's color space
+ * would be chosen, with the possibility of having an extended brightness range. This
+ * is important for screenshots that are directly re-routed to a SurfaceControl in
+ * order to preserve accurate colors.
+ */
+ public T setHintForSeamlessTransition(boolean hintForSeamlessTransition) {
+ mHintForSeamlessTransition = hintForSeamlessTransition;
+ return getThis();
+ }
+
+ /**
* Each sub class should return itself to allow the builder to chain properly
*/
T getThis() {
@@ -471,7 +493,6 @@
dest.writeBoolean(mAllowProtected);
dest.writeLong(mUid);
dest.writeBoolean(mGrayscale);
-
if (mExcludeLayers != null) {
dest.writeInt(mExcludeLayers.length);
for (SurfaceControl excludeLayer : mExcludeLayers) {
@@ -480,6 +501,7 @@
} else {
dest.writeInt(0);
}
+ dest.writeBoolean(mHintForSeamlessTransition);
}
public static final Parcelable.Creator<CaptureArgs> CREATOR =
@@ -627,6 +649,7 @@
setUid(args.mUid);
setGrayscale(args.mGrayscale);
setExcludeLayers(args.mExcludeLayers);
+ setHintForSeamlessTransition(args.mHintForSeamlessTransition);
}
public Builder(SurfaceControl layer) {
diff --git a/core/java/android/window/StartingWindowRemovalInfo.java b/core/java/android/window/StartingWindowRemovalInfo.java
index 5181236..6999e5b 100644
--- a/core/java/android/window/StartingWindowRemovalInfo.java
+++ b/core/java/android/window/StartingWindowRemovalInfo.java
@@ -16,6 +16,7 @@
package android.window;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Rect;
@@ -23,6 +24,9 @@
import android.os.Parcelable;
import android.view.SurfaceControl;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Information when removing a starting window of a particular task.
* @hide
@@ -55,11 +59,28 @@
*/
public boolean playRevealAnimation;
+ /** The mode is no need to defer removing the starting window for IME */
+ public static final int DEFER_MODE_NONE = 0;
+
+ /** The mode to defer removing the starting window until IME has drawn */
+ public static final int DEFER_MODE_NORMAL = 1;
+
+ /** The mode to defer the starting window removal until IME drawn and finished the rotation */
+ public static final int DEFER_MODE_ROTATION = 2;
+
+ @IntDef(prefix = { "DEFER_MODE_" }, value = {
+ DEFER_MODE_NONE,
+ DEFER_MODE_NORMAL,
+ DEFER_MODE_ROTATION,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DeferMode {}
+
/**
* Whether need to defer removing the starting window for IME.
* @hide
*/
- public boolean deferRemoveForIme;
+ public @DeferMode int deferRemoveForImeMode;
/**
* The rounded corner radius
@@ -95,7 +116,7 @@
windowAnimationLeash = source.readTypedObject(SurfaceControl.CREATOR);
mainFrame = source.readTypedObject(Rect.CREATOR);
playRevealAnimation = source.readBoolean();
- deferRemoveForIme = source.readBoolean();
+ deferRemoveForImeMode = source.readInt();
roundedCornerRadius = source.readFloat();
windowlessSurface = source.readBoolean();
removeImmediately = source.readBoolean();
@@ -107,7 +128,7 @@
dest.writeTypedObject(windowAnimationLeash, flags);
dest.writeTypedObject(mainFrame, flags);
dest.writeBoolean(playRevealAnimation);
- dest.writeBoolean(deferRemoveForIme);
+ dest.writeInt(deferRemoveForImeMode);
dest.writeFloat(roundedCornerRadius);
dest.writeBoolean(windowlessSurface);
dest.writeBoolean(removeImmediately);
@@ -119,7 +140,7 @@
+ " frame=" + mainFrame
+ " playRevealAnimation=" + playRevealAnimation
+ " roundedCornerRadius=" + roundedCornerRadius
- + " deferRemoveForIme=" + deferRemoveForIme
+ + " deferRemoveForImeMode=" + deferRemoveForImeMode
+ " windowlessSurface=" + windowlessSurface
+ " removeImmediately=" + removeImmediately + "}";
}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 628fc31..c0370cc 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -21,6 +21,7 @@
import static android.app.ActivityOptions.ANIM_FROM_STYLE;
import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
import static android.app.ActivityOptions.ANIM_SCALE_UP;
+import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
@@ -1067,6 +1068,11 @@
return options;
}
+ public static AnimationOptions makeSceneTransitionAnimOptions() {
+ AnimationOptions options = new AnimationOptions(ANIM_SCENE_TRANSITION);
+ return options;
+ }
+
public int getType() {
return mType;
}
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 51382a4..4d0132e 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -21,6 +21,8 @@
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 @@
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();
- } else {
- requestsPredictiveBack =
- context.getApplicationInfo().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));
+ if (DEBUG) {
+ Log.d(TAG, TextUtils.formatSimple(
+ "Activity: %s isPredictiveBackEnabled=%s",
+ activity.getComponentName(),
+ requestsPredictiveBack));
+ }
+ }
+ } else {
+ 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.");
}
- } 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/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 853fe2f..86c2893 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -76,7 +76,7 @@
/** Gating the removal of sorting-notifications-by-interruptiveness. */
public static final Flag NO_SORT_BY_INTERRUPTIVENESS =
- devFlag("persist.sysui.notification.no_sort_by_interruptiveness");
+ releasedFlag("persist.sysui.notification.no_sort_by_interruptiveness");
/** Gating the logging of DND state change events. */
public static final Flag LOG_DND_STATE_EVENTS =
@@ -115,7 +115,7 @@
}
/**
- * Creates a flag that is enabled by default in debuggable builds.
+ * Creates a flag that is disabled by default in debuggable builds.
* It can be enabled by setting this flag's SystemProperty to 1.
*
* This flag is ALWAYS disabled in release builds.
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 39d8380..0000000
--- 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/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index fbad4b9..a554d0e 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -731,7 +731,7 @@
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_AUTO_PIN_CONFIRMATION,
FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
- /* defaultValue= */ false);
+ /* defaultValue= */ true);
}
/** Returns if the given quality maps to an alphabetic password */
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 6fcff99..0f41229 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -374,7 +374,7 @@
}
inputEventObj =
android_view_KeyEvent_fromNative(env,
- static_cast<KeyEvent*>(inputEvent));
+ static_cast<KeyEvent&>(*inputEvent));
break;
case InputEventType::MOTION: {
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index ad54004..15270ef 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -353,8 +353,7 @@
jint seq, jobject eventObj) {
sp<NativeInputEventSender> sender =
reinterpret_cast<NativeInputEventSender*>(senderPtr);
- KeyEvent event;
- android_view_KeyEvent_toNative(env, eventObj, &event);
+ const KeyEvent event = android_view_KeyEvent_toNative(env, eventObj);
status_t status = sender->sendKeyEvent(seq, &event);
return !status;
}
diff --git a/core/jni/android_view_InputQueue.cpp b/core/jni/android_view_InputQueue.cpp
index 2c4966e..21db37e 100644
--- a/core/jni/android_view_InputQueue.cpp
+++ b/core/jni/android_view_InputQueue.cpp
@@ -221,12 +221,7 @@
jboolean predispatch) {
InputQueue* queue = reinterpret_cast<InputQueue*>(ptr);
KeyEvent* event = queue->createKeyEvent();
- status_t status = android_view_KeyEvent_toNative(env, eventObj, event);
- if (status) {
- queue->recycleInputEvent(event);
- jniThrowRuntimeException(env, "Could not read contents of KeyEvent object.");
- return -1;
- }
+ *event = android_view_KeyEvent_toNative(env, eventObj);
if (predispatch) {
event->setFlags(event->getFlags() | AKEY_EVENT_FLAG_PREDISPATCH);
diff --git a/core/jni/android_view_KeyCharacterMap.cpp b/core/jni/android_view_KeyCharacterMap.cpp
index 469e577..8fa03cf 100644
--- a/core/jni/android_view_KeyCharacterMap.cpp
+++ b/core/jni/android_view_KeyCharacterMap.cpp
@@ -217,7 +217,7 @@
result = env->NewObjectArray(jsize(events.size()), gKeyEventClassInfo.clazz, NULL);
if (result) {
for (size_t i = 0; i < events.size(); i++) {
- jobject keyEventObj = android_view_KeyEvent_fromNative(env, &events.itemAt(i));
+ jobject keyEventObj = android_view_KeyEvent_fromNative(env, events.itemAt(i));
if (!keyEventObj) break; // threw OOM exception
env->SetObjectArrayElement(result, jsize(i), keyEventObj);
env->DeleteLocalRef(keyEventObj);
diff --git a/core/jni/android_view_KeyEvent.cpp b/core/jni/android_view_KeyEvent.cpp
index d5568df..a9c9919 100644
--- a/core/jni/android_view_KeyEvent.cpp
+++ b/core/jni/android_view_KeyEvent.cpp
@@ -94,16 +94,16 @@
// ----------------------------------------------------------------------------
-jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent* event) {
- ScopedLocalRef<jbyteArray> hmac = toJbyteArray(env, event->getHmac());
+jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent& event) {
+ ScopedLocalRef<jbyteArray> hmac = toJbyteArray(env, event.getHmac());
jobject eventObj =
env->CallStaticObjectMethod(gKeyEventClassInfo.clazz, gKeyEventClassInfo.obtain,
- event->getId(), event->getDownTime(), event->getEventTime(),
- event->getAction(), event->getKeyCode(),
- event->getRepeatCount(), event->getMetaState(),
- event->getDeviceId(), event->getScanCode(),
- event->getFlags(), event->getSource(),
- event->getDisplayId(), hmac.get(), nullptr);
+ event.getId(), event.getDownTime(), event.getEventTime(),
+ event.getAction(), event.getKeyCode(),
+ event.getRepeatCount(), event.getMetaState(),
+ event.getDeviceId(), event.getScanCode(), event.getFlags(),
+ event.getSource(), event.getDisplayId(), hmac.get(),
+ nullptr);
if (env->ExceptionCheck()) {
ALOGE("An exception occurred while obtaining a key event.");
LOGE_EX(env);
@@ -113,8 +113,7 @@
return eventObj;
}
-status_t android_view_KeyEvent_toNative(JNIEnv* env, jobject eventObj,
- KeyEvent* event) {
+KeyEvent android_view_KeyEvent_toNative(JNIEnv* env, jobject eventObj) {
jint id = env->GetIntField(eventObj, gKeyEventClassInfo.mId);
jint deviceId = env->GetIntField(eventObj, gKeyEventClassInfo.mDeviceId);
jint source = env->GetIntField(eventObj, gKeyEventClassInfo.mSource);
@@ -133,9 +132,10 @@
jlong downTime = env->GetLongField(eventObj, gKeyEventClassInfo.mDownTime);
jlong eventTime = env->GetLongField(eventObj, gKeyEventClassInfo.mEventTime);
- event->initialize(id, deviceId, source, displayId, *hmac, action, flags, keyCode, scanCode,
- metaState, repeatCount, downTime, eventTime);
- return OK;
+ KeyEvent event;
+ event.initialize(id, deviceId, source, displayId, *hmac, action, flags, keyCode, scanCode,
+ metaState, repeatCount, downTime, eventTime);
+ return event;
}
status_t android_view_KeyEvent_recycle(JNIEnv* env, jobject eventObj) {
diff --git a/core/jni/android_view_KeyEvent.h b/core/jni/android_view_KeyEvent.h
index dab6bb7..bc4876a 100644
--- a/core/jni/android_view_KeyEvent.h
+++ b/core/jni/android_view_KeyEvent.h
@@ -27,12 +27,11 @@
/* Obtains an instance of a DVM KeyEvent object as a copy of a native KeyEvent instance.
* Returns NULL on error. */
-extern jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent* event);
+extern jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent& event);
/* Copies the contents of a DVM KeyEvent object to a native KeyEvent instance.
* Returns non-zero on error. */
-extern status_t android_view_KeyEvent_toNative(JNIEnv* env, jobject eventObj,
- KeyEvent* event);
+extern KeyEvent android_view_KeyEvent_toNative(JNIEnv* env, jobject eventObj);
/* Recycles a DVM KeyEvent object.
* Key events should only be recycled if they are owned by the system since user
diff --git a/core/jni/android_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp
index 1b67a0d..986dbe9 100644
--- a/core/jni/android_window_ScreenCapture.cpp
+++ b/core/jni/android_window_ScreenCapture.cpp
@@ -46,6 +46,7 @@
jfieldID uid;
jfieldID grayscale;
jmethodID getNativeExcludeLayers;
+ jfieldID hintForSeamlessTransition;
} gCaptureArgsClassInfo;
static struct {
@@ -69,23 +70,6 @@
jmethodID builder;
} gScreenshotHardwareBufferClassInfo;
-enum JNamedColorSpace : jint {
- // ColorSpace.Named.SRGB.ordinal() = 0;
- SRGB = 0,
-
- // ColorSpace.Named.DISPLAY_P3.ordinal() = 7;
- DISPLAY_P3 = 7,
-};
-
-constexpr jint fromDataspaceToNamedColorSpaceValue(const ui::Dataspace dataspace) {
- switch (dataspace) {
- case ui::Dataspace::DISPLAY_P3:
- return JNamedColorSpace::DISPLAY_P3;
- default:
- return JNamedColorSpace::SRGB;
- }
-}
-
static void checkAndClearException(JNIEnv* env, const char* methodName) {
if (env->ExceptionCheck()) {
ALOGE("An exception was thrown by callback '%s'.", methodName);
@@ -119,12 +103,11 @@
captureResults.fenceResult.value()->waitForever(LOG_TAG);
jobject jhardwareBuffer = android_hardware_HardwareBuffer_createFromAHardwareBuffer(
env, captureResults.buffer->toAHardwareBuffer());
- const jint namedColorSpace =
- fromDataspaceToNamedColorSpaceValue(captureResults.capturedDataspace);
jobject screenshotHardwareBuffer =
env->CallStaticObjectMethod(gScreenshotHardwareBufferClassInfo.clazz,
gScreenshotHardwareBufferClassInfo.builder,
- jhardwareBuffer, namedColorSpace,
+ jhardwareBuffer,
+ static_cast<jint>(captureResults.capturedDataspace),
captureResults.capturedSecureLayers,
captureResults.capturedHdrLayers);
checkAndClearException(env, "builder");
@@ -185,6 +168,9 @@
captureArgs.excludeHandles.emplace(excludeObject->getHandle());
}
}
+ captureArgs.hintForSeamlessTransition =
+ env->GetBooleanField(captureArgsObject,
+ gCaptureArgsClassInfo.hintForSeamlessTransition);
}
static DisplayCaptureArgs displayCaptureArgsFromObject(JNIEnv* env,
@@ -318,9 +304,10 @@
GetFieldIDOrDie(env, captureArgsClazz, "mAllowProtected", "Z");
gCaptureArgsClassInfo.uid = GetFieldIDOrDie(env, captureArgsClazz, "mUid", "J");
gCaptureArgsClassInfo.grayscale = GetFieldIDOrDie(env, captureArgsClazz, "mGrayscale", "Z");
-
gCaptureArgsClassInfo.getNativeExcludeLayers =
GetMethodIDOrDie(env, captureArgsClazz, "getNativeExcludeLayers", "()[J");
+ gCaptureArgsClassInfo.hintForSeamlessTransition =
+ GetFieldIDOrDie(env, captureArgsClazz, "mHintForSeamlessTransition", "Z");
jclass displayCaptureArgsClazz =
FindClassOrDie(env, "android/window/ScreenCapture$DisplayCaptureArgs");
diff --git a/core/proto/android/companion/telecom.proto b/core/proto/android/companion/telecom.proto
index 9ccadbf..02ba7c5 100644
--- a/core/proto/android/companion/telecom.proto
+++ b/core/proto/android/companion/telecom.proto
@@ -20,12 +20,12 @@
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;
+ string id = 1;
message Origin {
// Caller's name and/or phone number; what a user would see displayed when receiving an
@@ -34,6 +34,8 @@
// 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;
@@ -46,22 +48,25 @@
}
Status status = 3;
- enum Control {
- UNKNOWN_CONTROL = 0;
- ACCEPT = 1;
- REJECT = 2;
- SILENCE = 3;
- MUTE = 4;
- UNMUTE = 5;
- END = 6;
- PUT_ON_HOLD = 7;
- TAKE_OFF_HOLD = 8;
- REJECT_AND_BLOCK = 9;
- IGNORE = 10;
- }
- repeated Control controls_available = 4;
+ repeated Control controls = 4;
+ }
+
+ enum Control {
+ UNKNOWN_CONTROL = 0;
+ ACCEPT = 1;
+ REJECT = 2;
+ SILENCE = 3;
+ MUTE = 4;
+ UNMUTE = 5;
+ END = 6;
+ PUT_ON_HOLD = 7;
+ TAKE_OFF_HOLD = 8;
+ REJECT_AND_BLOCK = 9;
+ IGNORE = 10;
}
// 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/drawable-hdpi/pointer_all_scroll.png b/core/res/res/drawable-hdpi/pointer_all_scroll.png
index 095aadc..4af84c3 100644
--- a/core/res/res/drawable-hdpi/pointer_all_scroll.png
+++ b/core/res/res/drawable-hdpi/pointer_all_scroll.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_context_menu.png b/core/res/res/drawable-hdpi/pointer_context_menu.png
index c45d29b..60d37c4 100644
--- a/core/res/res/drawable-hdpi/pointer_context_menu.png
+++ b/core/res/res/drawable-hdpi/pointer_context_menu.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_help.png b/core/res/res/drawable-hdpi/pointer_help.png
index a3afdb6..2d9d20c 100644
--- a/core/res/res/drawable-hdpi/pointer_help.png
+++ b/core/res/res/drawable-hdpi/pointer_help.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-hdpi/pointer_horizontal_double_arrow.png
index 9388f16..c4018c8 100644
--- a/core/res/res/drawable-hdpi/pointer_horizontal_double_arrow.png
+++ b/core/res/res/drawable-hdpi/pointer_horizontal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-hdpi/pointer_top_left_diagonal_double_arrow.png
index ab52bff..58bb0d4 100644
--- a/core/res/res/drawable-hdpi/pointer_top_left_diagonal_double_arrow.png
+++ b/core/res/res/drawable-hdpi/pointer_top_left_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-hdpi/pointer_top_right_diagonal_double_arrow.png
index 1250d35..1981d41 100644
--- a/core/res/res/drawable-hdpi/pointer_top_right_diagonal_double_arrow.png
+++ b/core/res/res/drawable-hdpi/pointer_top_right_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-hdpi/pointer_vertical_double_arrow.png
index 6730c7b..d4ba79a 100644
--- a/core/res/res/drawable-hdpi/pointer_vertical_double_arrow.png
+++ b/core/res/res/drawable-hdpi/pointer_vertical_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_all_scroll.png b/core/res/res/drawable-mdpi/pointer_all_scroll.png
index 3db456e..1b81d0a 100644
--- a/core/res/res/drawable-mdpi/pointer_all_scroll.png
+++ b/core/res/res/drawable-mdpi/pointer_all_scroll.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_all_scroll_large.png b/core/res/res/drawable-mdpi/pointer_all_scroll_large.png
index 120e1d7..9e1f5c9 100644
--- a/core/res/res/drawable-mdpi/pointer_all_scroll_large.png
+++ b/core/res/res/drawable-mdpi/pointer_all_scroll_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_context_menu.png b/core/res/res/drawable-mdpi/pointer_context_menu.png
index e0e849d..d87d040 100644
--- a/core/res/res/drawable-mdpi/pointer_context_menu.png
+++ b/core/res/res/drawable-mdpi/pointer_context_menu.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_context_menu_large.png b/core/res/res/drawable-mdpi/pointer_context_menu_large.png
index e8c9be4..15266a6 100644
--- a/core/res/res/drawable-mdpi/pointer_context_menu_large.png
+++ b/core/res/res/drawable-mdpi/pointer_context_menu_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_help.png b/core/res/res/drawable-mdpi/pointer_help.png
index 286242c..bd04bbd 100644
--- a/core/res/res/drawable-mdpi/pointer_help.png
+++ b/core/res/res/drawable-mdpi/pointer_help.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_help_large.png b/core/res/res/drawable-mdpi/pointer_help_large.png
index 27f4a84..f9bd2b7 100644
--- a/core/res/res/drawable-mdpi/pointer_help_large.png
+++ b/core/res/res/drawable-mdpi/pointer_help_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png
index 20f319a..d1b3441 100644
--- a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png
+++ b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow_large.png b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow_large.png
index 33ef5c9..4e26371 100644
--- a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow_large.png
+++ b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png
index fe7d496..34c0c6a 100644
--- a/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png
+++ b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow_large.png b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow_large.png
index 7b2e20c..87ec184 100644
--- a/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow_large.png
+++ b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png
index 95a6620..40b9c7e 100644
--- a/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png
+++ b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow_large.png b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow_large.png
index 2e2904b..6a85b49 100644
--- a/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow_large.png
+++ b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png
index ae6bfed..9bd89bf 100644
--- a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png
+++ b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow_large.png b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow_large.png
index 3beb1d1..5a69bbc 100644
--- a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow_large.png
+++ b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_all_scroll.png b/core/res/res/drawable-xhdpi/pointer_all_scroll.png
index e9d05d5..85aa022 100644
--- a/core/res/res/drawable-xhdpi/pointer_all_scroll.png
+++ b/core/res/res/drawable-xhdpi/pointer_all_scroll.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png b/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png
index 1fd54fb..7448339 100644
--- a/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_all_scroll_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_context_menu.png b/core/res/res/drawable-xhdpi/pointer_context_menu.png
index d4b2bde..15d1e33 100644
--- a/core/res/res/drawable-xhdpi/pointer_context_menu.png
+++ b/core/res/res/drawable-xhdpi/pointer_context_menu.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_context_menu_large.png b/core/res/res/drawable-xhdpi/pointer_context_menu_large.png
index 977df10..e4ff7a6 100644
--- a/core/res/res/drawable-xhdpi/pointer_context_menu_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_context_menu_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_help.png b/core/res/res/drawable-xhdpi/pointer_help.png
index 5a6805c..952a4ee 100644
--- a/core/res/res/drawable-xhdpi/pointer_help.png
+++ b/core/res/res/drawable-xhdpi/pointer_help.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_help_large.png b/core/res/res/drawable-xhdpi/pointer_help_large.png
index 4bdc3d1..1d68437 100644
--- a/core/res/res/drawable-xhdpi/pointer_help_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_help_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png
index caf2a97..dd37f92 100644
--- a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png
+++ b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow_large.png b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow_large.png
index 2f22640..9e031e8 100644
--- a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png
index a36deb3..150d80d 100644
--- a/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png
+++ b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow_large.png b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow_large.png
index 6870e23..bae907a 100644
--- a/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png
index c8d6d1f..3b23143 100644
--- a/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png
+++ b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow_large.png b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow_large.png
index 5bfb771..a90b286 100644
--- a/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png
index 720df91..3e7f850 100644
--- a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png
+++ b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow_large.png b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow_large.png
index 82b30d1..090e3ca 100644
--- a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow_large.png
+++ b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow_large.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_all_scroll.png b/core/res/res/drawable-xxhdpi/pointer_all_scroll.png
index 808143a..92aae72 100644
--- a/core/res/res/drawable-xxhdpi/pointer_all_scroll.png
+++ b/core/res/res/drawable-xxhdpi/pointer_all_scroll.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_context_menu.png b/core/res/res/drawable-xxhdpi/pointer_context_menu.png
index 6ebfaab..4cd20f5 100644
--- a/core/res/res/drawable-xxhdpi/pointer_context_menu.png
+++ b/core/res/res/drawable-xxhdpi/pointer_context_menu.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_help.png b/core/res/res/drawable-xxhdpi/pointer_help.png
index 96b2a71..0c7a264 100644
--- a/core/res/res/drawable-xxhdpi/pointer_help.png
+++ b/core/res/res/drawable-xxhdpi/pointer_help.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-xxhdpi/pointer_horizontal_double_arrow.png
index 677ccad..b1e2509 100644
--- a/core/res/res/drawable-xxhdpi/pointer_horizontal_double_arrow.png
+++ b/core/res/res/drawable-xxhdpi/pointer_horizontal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-xxhdpi/pointer_top_left_diagonal_double_arrow.png
index e01aa64..2d1217c 100644
--- a/core/res/res/drawable-xxhdpi/pointer_top_left_diagonal_double_arrow.png
+++ b/core/res/res/drawable-xxhdpi/pointer_top_left_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-xxhdpi/pointer_top_right_diagonal_double_arrow.png
index e947e0e..a99fb24 100644
--- a/core/res/res/drawable-xxhdpi/pointer_top_right_diagonal_double_arrow.png
+++ b/core/res/res/drawable-xxhdpi/pointer_top_right_diagonal_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-xxhdpi/pointer_vertical_double_arrow.png
index c867247..1f065fa 100644
--- a/core/res/res/drawable-xxhdpi/pointer_vertical_double_arrow.png
+++ b/core/res/res/drawable-xxhdpi/pointer_vertical_double_arrow.png
Binary files differ
diff --git a/core/res/res/drawable/pointer_context_menu_large_icon.xml b/core/res/res/drawable/pointer_context_menu_large_icon.xml
index e07e5b6..325ea669 100644
--- a/core/res/res/drawable/pointer_context_menu_large_icon.xml
+++ b/core/res/res/drawable/pointer_context_menu_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_context_menu_large"
- android:hotSpotX="13.5dp"
- android:hotSpotY="10.5dp" />
+ android:hotSpotX="10.5dp"
+ android:hotSpotY="8dp" />
diff --git a/core/res/res/drawable/pointer_help_large_icon.xml b/core/res/res/drawable/pointer_help_large_icon.xml
index 43a1261..20f0c5336 100644
--- a/core/res/res/drawable/pointer_help_large_icon.xml
+++ b/core/res/res/drawable/pointer_help_large_icon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
android:bitmap="@drawable/pointer_help_large"
- android:hotSpotX="13.5dp"
- android:hotSpotY="10.5dp" />
+ android:hotSpotX="10.5dp"
+ android:hotSpotY="8dp" />
diff --git a/core/res/res/layout-television/user_switching_dialog.xml b/core/res/res/layout-television/user_switching_dialog.xml
deleted file mode 100644
index 72150e3..0000000
--- a/core/res/res/layout-television/user_switching_dialog.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2015 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.
--->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/message"
- style="?attr/textAppearanceListItem"
- android:layout_width="match_parent"
- android:background="@color/background_leanback_dark"
- android:textColor="@color/primary_text_leanback_dark"
- android:layout_height="match_parent"
- android:gravity="center"
- android:paddingStart="?attr/dialogPreferredPadding"
- android:paddingEnd="?attr/dialogPreferredPadding"
- android:paddingTop="24dp"
- android:paddingBottom="24dp" />
diff --git a/core/res/res/values-mcc310/config.xml b/core/res/res/values-mcc310/config.xml
index df398f9..76abcee 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 df398f9..6e0b678 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 df398f9..6e0b678 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 df398f9..6e0b678 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 df398f9..6e0b678 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 df398f9..6e0b678 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 df398f9..6e0b678 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 12dad7e..50aec83 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -991,6 +991,25 @@
<!-- Nominal White Z --> <item>1.089058</item>
</string-array>
+ <!-- The CCT closest to the white coordinates (primary) above and in SurfaceControl. -->
+ <integer name="config_displayWhiteBalanceDisplayNominalWhiteCct">6500</integer>
+
+ <!-- Range minimums corresponding to config_displayWhiteBalanceDisplaySteps. For example, if the
+ range minimums are [0, 3000] and the steps are [10, 20] then between 0 and 3000, exclusive,
+ the step between them will be 10 (i.e. 0, 10, 20, etc.) and the step between 3000 and the
+ maximum value is 20 (i.e. 3000, 3020, 3040, etc.). -->
+ <integer-array name="config_displayWhiteBalanceDisplayRangeMinimums">
+ <item>0</item>
+ </integer-array>
+
+ <!-- Steps corresponding to config_displayWhiteBalanceDisplayRangeMinimums. For example, if the
+ range minimums are [0, 3000] and the steps are [10, 20] then between 0 and 3000, exclusive,
+ the step between them will be 10 (i.e. 0, 10, 20, etc.) and the step between 3000 and the
+ maximum value is 20 (i.e. 3000, 3020, 3040, etc.). -->
+ <integer-array name="config_displayWhiteBalanceDisplaySteps">
+ <item>1</item>
+ </integer-array>
+
<!-- Boolean indicating whether light mode is allowed when DWB is turned on. -->
<bool name="config_displayWhiteBalanceLightModeAllowed">true</bool>
@@ -2105,9 +2124,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 +2955,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>
@@ -6396,7 +6415,7 @@
Packages can be added by OEMs in an allowlist, to prevent them from being scanned as
"stopped" during initial boot of a device, or after an OTA update. Stopped state of
an app is not changed during subsequent reboots. -->
- <bool name="config_stopSystemPackagesByDefault">false</bool>
+ <bool name="config_stopSystemPackagesByDefault">true</bool>
<!-- Whether to show weather on the lock screen by default. -->
<bool name="config_lockscreenWeatherEnabledByDefault">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index bb10f7a..218bbc2 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" />
@@ -3423,6 +3423,9 @@
<java-symbol type="integer" name="config_displayWhiteBalanceColorTemperatureDefault" />
<java-symbol type="array" name="config_displayWhiteBalanceDisplayPrimaries" />
<java-symbol type="array" name="config_displayWhiteBalanceDisplayNominalWhite" />
+ <java-symbol type="integer" name="config_displayWhiteBalanceDisplayNominalWhiteCct" />
+ <java-symbol type="array" name="config_displayWhiteBalanceDisplayRangeMinimums" />
+ <java-symbol type="array" name="config_displayWhiteBalanceDisplaySteps" />
<java-symbol type="bool" name="config_displayWhiteBalanceLightModeAllowed" />
<java-symbol type="integer" name="config_displayWhiteBalanceTransitionTime" />
diff --git a/core/tests/coretests/jni/NativeWorkSourceParcelTest.cpp b/core/tests/coretests/jni/NativeWorkSourceParcelTest.cpp
index db1f7bd..187e2c1 100644
--- a/core/tests/coretests/jni/NativeWorkSourceParcelTest.cpp
+++ b/core/tests/coretests/jni/NativeWorkSourceParcelTest.cpp
@@ -77,15 +77,18 @@
Parcel* parcel = nativeGetParcelData(env, wsParcel);
int32_t endMarker;
- // read WorkSource and if no error read end marker
- status_t err = ws.readFromParcel(parcel) ?: parcel->readInt32(&endMarker);
- int32_t dataAvailable = parcel->dataAvail();
-
+ status_t err = ws.readFromParcel(parcel);
if (err != OK) {
- ALOGE("WorkSource readFromParcel failed %d", err);
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ StringPrintf("WorkSource readFromParcel failed: %d", err).c_str());
}
-
+ err = parcel->readInt32(&endMarker);
+ if (err != OK) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ StringPrintf("Failed to read endMarker: %d", err).c_str());
+ }
// Now we have a native WorkSource object, verify it.
+ int32_t dataAvailable = parcel->dataAvail();
if (dataAvailable > 0) { // not all data read from the parcel
jniThrowException(env, "java/lang/IllegalArgumentException",
StringPrintf("WorkSource contains more data than native read (%d)",
diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index 6fa8f11..55680ab 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -227,5 +227,25 @@
assertEquals(numTotalSources, sources.size());
}
+ @Test
+ public void testGetIndex() {
+ for (int index = 0; index < 2048; index++) {
+ for (int type = FIRST; type <= LAST; type = type << 1) {
+ final int id = InsetsSource.createId(null, index, type);
+ assertEquals(index, InsetsSource.getIndex(id));
+ }
+ }
+ }
+
+ @Test
+ public void testGetType() {
+ for (int index = 0; index < 2048; index++) {
+ for (int type = FIRST; type <= LAST; type = type << 1) {
+ final int id = InsetsSource.createId(null, index, type);
+ assertEquals(type, InsetsSource.getType(id));
+ }
+ }
+ }
+
// Parcel and equals already tested via InsetsStateTest
}
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index 922dbb5..43683ff 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -31,6 +31,7 @@
<permission name="android.permission.DUMP"/>
<permission name="android.permission.GET_APP_OPS_STATS"/>
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
+ <permission name="android.permission.LOCATION_HARDWARE"/>
<permission name="android.permission.MANAGE_DEBUGGING"/>
<permission name="android.permission.MANAGE_GAME_MODE" />
<permission name="android.permission.MANAGE_SENSOR_PRIVACY"/>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 40cb7f2..1f1239e 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -520,6 +520,10 @@
<permission name="android.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER"/>
<!-- Permission required for CTS test - SatelliteManagerTest -->
<permission name="android.permission.SATELLITE_COMMUNICATION"/>
+ <!-- Permission required for GTS test - GtsAttestationVerificationDeviceSideTestCases -->
+ <permission name="android.permission.USE_ATTESTATION_VERIFICATION_SERVICE" />
+ <!-- Permission required for GTS test - GtsCredentialsTestCases -->
+ <permission name="android.permission.LAUNCH_CREDENTIAL_SELECTOR"/>
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 596f351..7c2759a 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/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 302c72e..dd4b58e 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -40,6 +40,7 @@
import android.graphics.drawable.NinePatchDrawable;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
+import android.media.MediaFormat;
import android.net.Uri;
import android.os.Build;
import android.os.Trace;
@@ -914,8 +915,6 @@
case "image/jpeg":
case "image/webp":
case "image/gif":
- case "image/heif":
- case "image/heic":
case "image/bmp":
case "image/x-ico":
case "image/vnd.wap.wbmp":
@@ -930,6 +929,9 @@
case "image/x-pentax-pef":
case "image/x-samsung-srw":
return true;
+ case "image/heif":
+ case "image/heic":
+ return isHevcDecoderSupported();
case "image/avif":
return isP010SupportedForAV1();
default:
@@ -2067,6 +2069,28 @@
return decodeBitmapImpl(src, null);
}
+ private static boolean sIsHevcDecoderSupported = false;
+ private static boolean sIsHevcDecoderSupportedInitialized = false;
+ private static final Object sIsHevcDecoderSupportedLock = new Object();
+
+ /*
+ * Check if HEVC decoder is supported by the device.
+ */
+ @SuppressWarnings("AndroidFrameworkCompatChange")
+ private static boolean isHevcDecoderSupported() {
+ synchronized (sIsHevcDecoderSupportedLock) {
+ if (sIsHevcDecoderSupportedInitialized) {
+ return sIsHevcDecoderSupported;
+ }
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_HEVC);
+ MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+ sIsHevcDecoderSupported = mcl.findDecoderForFormat(format) != null;
+ sIsHevcDecoderSupportedInitialized = true;
+ return sIsHevcDecoderSupported;
+ }
+ }
+
private static boolean sIsP010SupportedForAV1 = false;
private static boolean sIsP010SupportedForHEVC = false;
private static boolean sIsP010SupportedFlagsInitialized = false;
@@ -2105,20 +2129,20 @@
* Checks if the device supports decoding 10-bit for the given mime type.
*/
private static void checkP010SupportforAV1HEVC() {
- MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
+ MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
for (MediaCodecInfo mediaCodecInfo : codecList.getCodecInfos()) {
if (mediaCodecInfo.isEncoder()) {
continue;
}
for (String mediaType : mediaCodecInfo.getSupportedTypes()) {
- if (mediaType.equalsIgnoreCase("video/av01")
- || mediaType.equalsIgnoreCase("video/hevc")) {
+ if (mediaType.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1)
+ || mediaType.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
MediaCodecInfo.CodecCapabilities codecCapabilities =
mediaCodecInfo.getCapabilitiesForType(mediaType);
for (int i = 0; i < codecCapabilities.colorFormats.length; ++i) {
if (codecCapabilities.colorFormats[i]
== MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010) {
- if (mediaType.equalsIgnoreCase("video/av01")) {
+ if (mediaType.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1)) {
sIsP010SupportedForAV1 = true;
} else {
sIsP010SupportedForHEVC = true;
diff --git a/graphics/java/android/graphics/MeshSpecification.java b/graphics/java/android/graphics/MeshSpecification.java
index 70311fd..b1aae7f 100644
--- a/graphics/java/android/graphics/MeshSpecification.java
+++ b/graphics/java/android/graphics/MeshSpecification.java
@@ -28,11 +28,40 @@
import java.lang.annotation.RetentionPolicy;
/**
- * Class responsible for holding specifications for {@link Mesh} creations. This class
- * generates a {@link MeshSpecification} via the Make method, where multiple parameters to set up
- * the mesh are supplied, including attributes, vertex stride, varyings, and
- * vertex/fragment shaders. There are also additional methods to provide an optional
- * {@link ColorSpace} as well as an alpha type.
+ * Class responsible for holding specifications for {@link Mesh} creations. This class generates a
+ * {@link MeshSpecification} via the
+ * {@link MeshSpecification#make(Attribute[], int, Varying[], String, String)} method,
+ * where multiple parameters to set up the mesh are supplied, including attributes, vertex stride,
+ * {@link Varying}, and vertex/fragment shaders. There are also additional methods to provide an
+ * optional {@link ColorSpace} as well as an alpha type.
+ *
+ * For example a vertex shader that leverages a {@link Varying} may look like the following:
+ *
+ * <pre>
+ * Varyings main(const Attributes attributes) {
+ * Varyings varyings;
+ * varyings.position = attributes.position;
+ * return varyings;
+ * }
+ * </pre>
+ *
+ * The corresponding fragment shader that may consume the varying look like the following:
+ *
+ * <pre>
+ * float2 main(const Varyings varyings, out float4 color) {
+ * color = vec4(1.0, 0.0, 0.0, 1.0);
+ * return varyings.position;
+ * }
+ * </pre>
+ *
+ * The color returned from this fragment shader is blended with the other parameters that are
+ * configured on the Paint object (ex. {@link Paint#setBlendMode(BlendMode)} used to draw the mesh.
+ *
+ * The position returned in the fragment shader can be consumed by any following fragment shaders in
+ * the shader chain.
+ *
+ * See https://developer.android.com/develop/ui/views/graphics/agsl for more information
+ * regarding Android Graphics Shader Language.
*
* Note that there are several limitations on various mesh specifications:
* 1. The max amount of attributes allowed is 8.
@@ -118,7 +147,11 @@
public static final int TYPE_UBYTE4 = 4;
/**
- * Data class to represent a single attribute in a shader.
+ * Data class to represent a single attribute in a shader. An attribute is a variable that
+ * accompanies a vertex, this can be a color or texture coordinates.
+ *
+ * See https://developer.android.com/develop/ui/views/graphics/agsl for more information
+ * regarding Android Graphics Shader Language.
*
* Note that offset is the offset in number of bytes. For example, if we had two attributes
*
@@ -128,6 +161,10 @@
* </pre>
*
* att1 would have an offset of 0, while att2 would have an offset of 12 bytes.
+ *
+ * This is consumed as part of
+ * {@link MeshSpecification#make(Attribute[], int, Varying[], String, String, ColorSpace, int)}
+ * to create a {@link MeshSpecification} instance.
*/
public static class Attribute {
@Type
@@ -175,7 +212,15 @@
}
/**
- * Data class to represent a single varying variable.
+ * Data class to represent a single varying variable. A Varying variable can be altered by the
+ * vertex shader defined on the mesh but not by the fragment shader defined by AGSL.
+ *
+ * See https://developer.android.com/develop/ui/views/graphics/agsl for more information
+ * regarding Android Graphics Shader Language.
+ *
+ * This is consumed as part of
+ * {@link MeshSpecification#make(Attribute[], int, Varying[], String, String, ColorSpace, int)}
+ * to create a {@link MeshSpecification} instance.
*/
public static class Varying {
@Type
@@ -220,7 +265,7 @@
/**
* Creates a {@link MeshSpecification} object for use within {@link Mesh}. This uses a default
- * color space of {@link ColorSpace.Named#SRGB} and {@link AlphaType} of
+ * color space of {@link ColorSpace.Named#SRGB} and alphaType of
* {@link #ALPHA_TYPE_PREMULTIPLIED}.
*
* @param attributes list of attributes represented by {@link Attribute}. Can hold a max of
@@ -233,7 +278,11 @@
* the 6 varyings allowed.
* @param vertexShader vertex shader to be supplied to the mesh. Ensure that the position
* varying is set within the shader to get proper results.
+ * See {@link MeshSpecification} for an example vertex shader
+ * implementation
* @param fragmentShader fragment shader to be supplied to the mesh.
+ * See {@link MeshSpecification} for an example fragment shader
+ * implementation
* @return {@link MeshSpecification} object for use when creating {@link Mesh}
*/
@NonNull
@@ -253,7 +302,7 @@
}
/**
- * Creates a {@link MeshSpecification} object. This uses a default {@link AlphaType} of
+ * Creates a {@link MeshSpecification} object. This uses a default alphaType of
* {@link #ALPHA_TYPE_PREMULTIPLIED}.
*
* @param attributes list of attributes represented by {@link Attribute}. Can hold a max of
@@ -266,7 +315,11 @@
* the 6 varyings allowed.
* @param vertexShader vertex shader to be supplied to the mesh. Ensure that the position
* varying is set within the shader to get proper results.
+ * See {@link MeshSpecification} for an example vertex shader
+ * implementation
* @param fragmentShader fragment shader to be supplied to the mesh.
+ * See {@link MeshSpecification} for an example fragment shader
+ * implementation
* @param colorSpace {@link ColorSpace} to tell what color space to work in.
* @return {@link MeshSpecification} object for use when creating {@link Mesh}
*/
@@ -301,7 +354,11 @@
* the 6 varyings allowed.
* @param vertexShader vertex shader to be supplied to the mesh. Ensure that the position
* varying is set within the shader to get proper results.
+ * See {@link MeshSpecification} for an example vertex shader
+ * implementation
* @param fragmentShader fragment shader to be supplied to the mesh.
+ * See {@link MeshSpecification} for an example fragment shader
+ * implementation
* @param colorSpace {@link ColorSpace} to tell what color space to work in.
* @param alphaType Describes how to interpret the alpha component for a pixel. Must be
* one of
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 6a79bc1..54978bd 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -125,34 +125,6 @@
// End ProtoLog
-gensrcs {
- name: "wm-shell-protos",
-
- tools: [
- "aprotoc",
- "protoc-gen-javastream",
- "soong_zip",
- ],
-
- tool_files: [
- ":libprotobuf-internal-protos",
- ],
-
- cmd: "mkdir -p $(genDir)/$(in) " +
- "&& $(location aprotoc) " +
- " --plugin=$(location protoc-gen-javastream) " +
- " --javastream_out=$(genDir)/$(in) " +
- " -Iexternal/protobuf/src " +
- " -I . " +
- " $(in) " +
- "&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
-
- srcs: [
- "proto/**/*.proto",
- ],
- output_extension: "srcjar",
-}
-
java_library {
name: "WindowManager-Shell-proto",
@@ -170,7 +142,6 @@
// TODO(b/168581922) protologtool do not support kotlin(*.kt)
":wm_shell-sources-kt",
":wm_shell-aidls",
- ":wm-shell-protos",
],
resource_dirs: [
"res",
diff --git a/libs/WindowManager/Shell/res/color/taskbar_background.xml b/libs/WindowManager/Shell/res/color/taskbar_background.xml
deleted file mode 100644
index 876ee02..0000000
--- a/libs/WindowManager/Shell/res/color/taskbar_background.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<!-- Should be the same as in packages/apps/Launcher3/res/color-v31/taskbar_background.xml -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="98" />
-</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color-night/taskbar_background.xml b/libs/WindowManager/Shell/res/color/taskbar_background_dark.xml
similarity index 100%
rename from libs/WindowManager/Shell/res/color-night/taskbar_background.xml
rename to libs/WindowManager/Shell/res/color/taskbar_background_dark.xml
diff --git a/libs/WindowManager/Shell/res/values-night/colors.xml b/libs/WindowManager/Shell/res/values-night/colors.xml
index 5c6bb57..83c4d93 100644
--- a/libs/WindowManager/Shell/res/values-night/colors.xml
+++ b/libs/WindowManager/Shell/res/values-night/colors.xml
@@ -15,7 +15,6 @@
-->
<resources>
- <color name="docked_divider_handle">#ffffff</color>
<!-- Bubbles -->
<color name="bubbles_icon_tint">@color/GM2_grey_200</color>
<!-- Splash screen-->
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index c487e4a..54a8f33 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -17,8 +17,8 @@
*/
-->
<resources>
- <color name="docked_divider_handle">#000000</color>
- <color name="split_divider_background">@color/taskbar_background</color>
+ <color name="docked_divider_handle">#ffffff</color>
+ <color name="split_divider_background">@color/taskbar_background_dark</color>
<drawable name="forced_resizable_background">#59000000</drawable>
<color name="minimize_dock_shadow_start">#60000000</color>
<color name="minimize_dock_shadow_end">#00000000</color>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
index 544d757..410ae78d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -181,6 +181,17 @@
}
/**
+ * Returns the list of display ids that are tracked by a {@link DisplayAreaInfo}
+ */
+ public int[] getDisplayIds() {
+ int[] displayIds = new int[mDisplayAreasInfo.size()];
+ for (int i = 0; i < mDisplayAreasInfo.size(); i++) {
+ displayIds[i] = mDisplayAreasInfo.keyAt(i);
+ }
+ return displayIds;
+ }
+
+ /**
* Returns the {@link DisplayAreaInfo} of the {@link DisplayAreaInfo#displayId}.
*/
@Nullable
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index b6fd0bb..9aac694 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -421,7 +421,7 @@
/**
* Removes listener.
*/
- public void removeLocusIdListener(FocusListener listener) {
+ public void removeFocusListener(FocusListener listener) {
synchronized (mLock) {
mFocusListeners.remove(listener);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index c767376..18615f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -19,6 +19,8 @@
import static android.graphics.Matrix.MTRANS_X;
import static android.graphics.Matrix.MTRANS_Y;
+import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationRunner.shouldUseSnapshotAnimationForClosingChange;
+
import android.annotation.CallSuper;
import android.graphics.Point;
import android.graphics.Rect;
@@ -97,10 +99,17 @@
final Rect startBounds = change.getStartAbsBounds();
final Rect endBounds = change.getEndAbsBounds();
mContentBounds.set(startBounds);
- mContentRelOffset.set(change.getEndRelOffset());
- mContentRelOffset.offset(
- startBounds.left - endBounds.left,
- startBounds.top - endBounds.top);
+ // Put the transition to the top left for snapshot animation.
+ if (shouldUseSnapshotAnimationForClosingChange(mChange)) {
+ // TODO(b/275034335): Fix an issue that black hole when closing the right container
+ // in bounds change transition.
+ mContentRelOffset.set(0, 0);
+ } else {
+ mContentRelOffset.set(change.getEndRelOffset());
+ mContentRelOffset.offset(
+ startBounds.left - endBounds.left,
+ startBounds.top - endBounds.top);
+ }
} else {
mContentBounds.set(change.getEndAbsBounds());
mContentRelOffset.set(change.getEndRelOffset());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 1df6ecd..ab7c7d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -21,6 +21,7 @@
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
+import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationSpec.createShowSnapshotForClosingAnimation;
import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
@@ -42,6 +43,7 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationAdapter.SnapshotAdapter;
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.util.TransitionUtil;
@@ -185,23 +187,23 @@
return createChangeAnimationAdapters(info, startTransaction);
}
if (TransitionUtil.isClosingType(info.getType())) {
- return createCloseAnimationAdapters(info);
+ return createCloseAnimationAdapters(info, startTransaction);
}
- return createOpenAnimationAdapters(info);
+ return createOpenAnimationAdapters(info, startTransaction);
}
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createOpenAnimationAdapters(
- @NonNull TransitionInfo info) {
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
return createOpenCloseAnimationAdapters(info, true /* isOpening */,
- mAnimationSpec::loadOpenAnimation);
+ mAnimationSpec::loadOpenAnimation, startTransaction);
}
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createCloseAnimationAdapters(
- @NonNull TransitionInfo info) {
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
return createOpenCloseAnimationAdapters(info, false /* isOpening */,
- mAnimationSpec::loadCloseAnimation);
+ mAnimationSpec::loadCloseAnimation, startTransaction);
}
/**
@@ -211,7 +213,8 @@
@NonNull
private List<ActivityEmbeddingAnimationAdapter> createOpenCloseAnimationAdapters(
@NonNull TransitionInfo info, boolean isOpening,
- @NonNull AnimationProvider animationProvider) {
+ @NonNull AnimationProvider animationProvider,
+ @NonNull SurfaceControl.Transaction startTransaction) {
// We need to know if the change window is only a partial of the whole animation screen.
// If so, we will need to adjust it to make the whole animation screen looks like one.
final List<TransitionInfo.Change> openingChanges = new ArrayList<>();
@@ -224,6 +227,8 @@
openingWholeScreenBounds.union(change.getEndAbsBounds());
} else {
closingChanges.add(change);
+ // Also union with the start bounds because the closing transition may be shrunk.
+ closingWholeScreenBounds.union(change.getStartAbsBounds());
closingWholeScreenBounds.union(change.getEndAbsBounds());
}
}
@@ -241,6 +246,17 @@
adapters.add(adapter);
}
for (TransitionInfo.Change change : closingChanges) {
+ if (shouldUseSnapshotAnimationForClosingChange(change)) {
+ SurfaceControl screenshot = getOrCreateScreenshot(change, change, startTransaction);
+ if (screenshot != null) {
+ final SnapshotAdapter snapshotAdapter = new SnapshotAdapter(
+ createShowSnapshotForClosingAnimation(), change, screenshot);
+ if (!isOpening) {
+ snapshotAdapter.overrideLayer(offsetLayer++);
+ }
+ adapters.add(snapshotAdapter);
+ }
+ }
final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
info, change, animationProvider, closingWholeScreenBounds);
if (!isOpening) {
@@ -251,6 +267,22 @@
return adapters;
}
+ /**
+ * Returns whether we should use snapshot animation for the closing change.
+ * It's usually because the end bounds of the closing change are shrunk, which leaves a black
+ * area in the transition.
+ */
+ static boolean shouldUseSnapshotAnimationForClosingChange(
+ @NonNull TransitionInfo.Change closingChange) {
+ // Only check closing type because we only take screenshot for closing bounds-changing
+ // changes.
+ if (!TransitionUtil.isClosingType(closingChange.getMode())) {
+ return false;
+ }
+ // Don't need to take screenshot if there's no bounds change.
+ return !closingChange.getStartAbsBounds().equals(closingChange.getEndAbsBounds());
+ }
+
/** Sets the first frame to the {@code startTransaction} to avoid any flicker on start. */
private void prepareForFirstFrame(@NonNull SurfaceControl.Transaction startTransaction,
@NonNull List<ActivityEmbeddingAnimationAdapter> adapters) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index cb8342a..19eff0e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -77,6 +77,15 @@
return new AlphaAnimation(alpha, alpha);
}
+ /**
+ * Animation that intended to show snapshot for closing animation because the closing end bounds
+ * are changed.
+ */
+ @NonNull
+ static Animation createShowSnapshotForClosingAnimation() {
+ return new AlphaAnimation(1f, 1f);
+ }
+
/** Animation for window that is opening in a change transition. */
@NonNull
Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change,
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 d3f3958..24fd86b 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 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/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index b9ff5f0..e7ec7aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -2884,6 +2884,7 @@
/** Hide or show the manage menu for the currently expanded bubble. */
@VisibleForTesting
public void showManageMenu(boolean show) {
+ if ((mManageMenu.getVisibility() == VISIBLE) == show) return;
mShowingManage = show;
// This should not happen, since the manage menu is only visible when there's an expanded
@@ -3319,7 +3320,9 @@
* @return the normalized x-axis position of the bubble stack rounded to 4 decimal places.
*/
public float getNormalizedXPosition() {
- return new BigDecimal(getStackPosition().x / mPositioner.getAvailableRect().width())
+ int width = mPositioner.getAvailableRect().width();
+ float stackPosition = width > 0 ? getStackPosition().x / width : 0;
+ return new BigDecimal(stackPosition)
.setScale(4, RoundingMode.CEILING.HALF_UP)
.floatValue();
}
@@ -3328,7 +3331,9 @@
* @return the normalized y-axis position of the bubble stack rounded to 4 decimal places.
*/
public float getNormalizedYPosition() {
- return new BigDecimal(getStackPosition().y / mPositioner.getAvailableRect().height())
+ int height = mPositioner.getAvailableRect().height();
+ float stackPosition = height > 0 ? getStackPosition().y / height : 0;
+ return new BigDecimal(stackPosition)
.setScale(4, RoundingMode.CEILING.HALF_UP)
.floatValue();
}
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 0f9260c..9abf0f6 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 @@
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 e2106e4..d8859ba 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 @@
}
@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 @@
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 b0dea72..d27d05b 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 @@
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 @@
@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 @@
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/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index b8204d0..0bcafe5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -59,4 +59,17 @@
/** Flag applied to a transition change to identify it as a divider bar for animation. */
public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
+
+ public static final String splitPositionToString(@SplitPosition int pos) {
+ switch (pos) {
+ case SPLIT_POSITION_UNDEFINED:
+ return "SPLIT_POSITION_UNDEFINED";
+ case SPLIT_POSITION_TOP_OR_LEFT:
+ return "SPLIT_POSITION_TOP_OR_LEFT";
+ case SPLIT_POSITION_BOTTOM_OR_RIGHT:
+ return "SPLIT_POSITION_BOTTOM_OR_RIGHT";
+ default:
+ return "UNKNOWN";
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 80e920f..28368ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -189,12 +189,13 @@
static Optional<DragAndDropController> provideDragAndDropController(Context context,
ShellInit shellInit,
ShellController shellController,
+ ShellCommandHandler shellCommandHandler,
DisplayController displayController,
UiEventLogger uiEventLogger,
IconProvider iconProvider,
@ShellMainThread ShellExecutor mainExecutor) {
return Optional.ofNullable(DragAndDropController.create(context, shellInit, shellController,
- displayController, uiEventLogger, iconProvider, mainExecutor));
+ shellCommandHandler, displayController, uiEventLogger, iconProvider, mainExecutor));
}
@WMSingleton
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 86ea725..b9d2be2 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 @@
/**
* 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 @@
}
/** 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 @@
@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 @@
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 @@
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/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index d1760ed..76ca68b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -49,6 +49,12 @@
"persist.wm.debug.desktop_veiled_resizing", true);
/**
+ * Flag to indicate is moving task to another display is enabled.
+ */
+ public static final boolean IS_DISPLAY_CHANGE_ENABLED = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_change_display", false);
+
+ /**
* Return {@code true} if desktop mode support is enabled
*/
public static boolean isProto1Enabled() {
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 12f8ea2..00cc57f 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.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 @@
*/
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 @@
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 @@
/**
* 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 @@
}
/**
- * 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 @@
* @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)
- } else {
- 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) })
+ // 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 {
+ displayData[displayId]?.visibleTasks?.remove(taskId)
+ }
+ 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 @@
* Called when the active tasks change in desktop mode.
*/
@JvmDefault
- fun onActiveTasksChanged() {}
+ fun onActiveTasksChanged(displayId: Int) {}
}
/**
@@ -237,6 +298,6 @@
* 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/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index fb0a91f..0f0d572 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -162,7 +162,7 @@
/**
* Release the indicator and its components when it is no longer needed.
*/
- public void releaseVisualIndicator() {
+ public void releaseVisualIndicator(SurfaceControl.Transaction t) {
if (mViewHost == null) return;
if (mViewHost != null) {
mViewHost.release();
@@ -170,13 +170,8 @@
}
if (mLeash != null) {
- final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
t.remove(mLeash);
mLeash = null;
- mSyncQueue.runInSync(transaction -> {
- transaction.merge(t);
- t.close();
- });
}
}
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 0d56023..ee94f30 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
@@ -78,6 +78,11 @@
private val desktopMode: DesktopModeImpl
private var visualIndicator: DesktopModeVisualIndicator? = null
+ private val mOnAnimationFinishedCallback = Consumer<SurfaceControl.Transaction> {
+ t: SurfaceControl.Transaction ->
+ visualIndicator?.releaseVisualIndicator(t)
+ visualIndicator = null
+ }
init {
desktopMode = DesktopModeImpl()
@@ -97,10 +102,11 @@
}
/** 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 +120,8 @@
}
/** 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 +135,7 @@
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 */)
@@ -153,27 +159,28 @@
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
enterDesktopTaskTransitionHandler.startTransition(
- Transitions.TRANSIT_ENTER_FREEFORM, wct)
+ Transitions.TRANSIT_ENTER_FREEFORM, wct, mOnAnimationFinishedCallback)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
}
/** Brings apps to front and sets freeform task bounds */
- fun moveToDesktopWithAnimation(
+ private fun moveToDesktopWithAnimation(
taskInfo: RunningTaskInfo,
freeformBounds: Rect
) {
val wct = WindowContainerTransaction()
- bringDesktopAppsToFront(wct)
+ bringDesktopAppsToFront(taskInfo.displayId, wct)
addMoveToDesktopChanges(wct, taskInfo.getToken())
wct.setBounds(taskInfo.token, freeformBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
enterDesktopTaskTransitionHandler.startTransition(
- Transitions.TRANSIT_ENTER_DESKTOP_MODE, wct)
+ Transitions.TRANSIT_ENTER_DESKTOP_MODE, wct, mOnAnimationFinishedCallback)
} else {
shellTaskOrganizer.applyTransaction(wct)
+ releaseVisualIndicator()
}
}
@@ -204,21 +211,24 @@
val wct = WindowContainerTransaction()
addMoveToFullscreenChanges(wct, task.token)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, startPosition)
+ enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, startPosition,
+ mOnAnimationFinishedCallback)
} else {
shellTaskOrganizer.applyTransaction(wct)
+ releaseVisualIndicator()
}
}
- fun moveToFullscreenWithAnimation(task: ActivityManager.RunningTaskInfo) {
+ private fun moveToFullscreenWithAnimation(task: ActivityManager.RunningTaskInfo) {
val wct = WindowContainerTransaction()
addMoveToFullscreenChanges(wct, task.token)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
exitDesktopTaskTransitionHandler.startTransition(
- Transitions.TRANSIT_EXIT_DESKTOP_MODE, wct)
+ Transitions.TRANSIT_EXIT_DESKTOP_MODE, wct, mOnAnimationFinishedCallback)
} else {
shellTaskOrganizer.applyTransaction(wct)
+ releaseVisualIndicator()
}
}
@@ -234,6 +244,69 @@
}
/**
+ * Move task to the next display.
+ *
+ * Queries all current known display ids and sorts them in ascending order. Then iterates
+ * through the list and looks for the display id that is larger than the display id for
+ * the passed in task. If a display with a higher id is not found, iterates through the list and
+ * finds the first display id that is not the display id for the passed in task.
+ *
+ * If a display matching the above criteria is found, re-parents the task to that display.
+ * No-op if no such display is found.
+ */
+ fun moveToNextDisplay(taskId: Int) {
+ val task = shellTaskOrganizer.getRunningTaskInfo(taskId)
+ if (task == null) {
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d not found", taskId)
+ return
+ }
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d taskDisplayId=%d",
+ taskId, task.displayId)
+
+ val displayIds = rootTaskDisplayAreaOrganizer.displayIds.sorted()
+ // Get the first display id that is higher than current task display id
+ var newDisplayId = displayIds.firstOrNull { displayId -> displayId > task.displayId }
+ if (newDisplayId == null) {
+ // No display with a higher id, get the first display id that is not the task display id
+ newDisplayId = displayIds.firstOrNull { displayId -> displayId < task.displayId }
+ }
+ if (newDisplayId == null) {
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: next display not found")
+ return
+ }
+ moveToDisplay(task, newDisplayId)
+ }
+
+ /**
+ * Move [task] to display with [displayId].
+ *
+ * No-op if task is already on that display per [RunningTaskInfo.displayId].
+ */
+ private fun moveToDisplay(task: RunningTaskInfo, displayId: Int) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToDisplay: taskId=%d displayId=%d",
+ task.taskId, displayId)
+
+ if (task.displayId == displayId) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "moveToDisplay: task already on display")
+ return
+ }
+
+ val displayAreaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)
+ if (displayAreaInfo == null) {
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToDisplay: display not found")
+ return
+ }
+
+ val wct = WindowContainerTransaction()
+ wct.reparent(task.token, displayAreaInfo.token, true /* onTop */)
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+
+ /**
* Get windowing move for a given `taskId`
*
* @return [WindowingMode] for the task or [WINDOWING_MODE_UNDEFINED] if task is not found
@@ -244,9 +317,9 @@
?: 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)
@@ -266,6 +339,16 @@
?.let { homeTask -> wct.reorder(homeTask.getToken(), true /* onTop */) }
}
+ private fun releaseVisualIndicator() {
+ val t = SurfaceControl.Transaction()
+ visualIndicator?.releaseVisualIndicator(t)
+ visualIndicator = null
+ syncQueue.runInSync { transaction ->
+ transaction.merge(t)
+ t.close()
+ }
+ }
+
override fun getContext(): Context {
return context
}
@@ -290,18 +373,17 @@
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 +392,11 @@
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 +412,7 @@
}
// 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) }) {
@@ -407,8 +490,7 @@
rootTaskDisplayAreaOrganizer)
visualIndicator?.createFullscreenIndicatorWithAnimatedBounds()
} else if (y > statusBarHeight && visualIndicator != null) {
- visualIndicator?.releaseVisualIndicator()
- visualIndicator = null
+ releaseVisualIndicator()
}
}
}
@@ -425,8 +507,6 @@
) {
val statusBarHeight = getStatusBarHeight(taskInfo)
if (y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
- visualIndicator?.releaseVisualIndicator()
- visualIndicator = null
moveToFullscreenWithAnimation(taskInfo)
}
}
@@ -444,6 +524,11 @@
taskSurface: SurfaceControl,
y: Float
) {
+ // If the motion event is above the status bar, return since we do not need to show the
+ // visual indicator at this point.
+ if (y < getStatusBarHeight(taskInfo)) {
+ return
+ }
if (visualIndicator == null) {
visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
displayController, context, taskSurface, shellTaskOrganizer,
@@ -471,11 +556,8 @@
freeformBounds: Rect
) {
moveToDesktopWithAnimation(taskInfo, freeformBounds)
- visualIndicator?.releaseVisualIndicator()
- visualIndicator = null
}
-
private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int {
return displayController.getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
}
@@ -502,7 +584,6 @@
desktopModeTaskRepository.removeTaskCorners(taskId)
}
-
/**
* Adds a listener to find out about changes in the visibility of freeform tasks.
*
@@ -559,20 +640,19 @@
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/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 9467578..d55fddd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -39,6 +39,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
import java.util.function.Supplier;
/**
@@ -58,6 +59,7 @@
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
private Point mStartPosition;
+ private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
public EnterDesktopTaskTransitionHandler(
Transitions transitions) {
@@ -75,9 +77,12 @@
* Starts Transition of a given type
* @param type Transition type
* @param wct WindowContainerTransaction for transition
+ * @param onAnimationEndCallback to be called after animation
*/
public void startTransition(@WindowManager.TransitionType int type,
- @NonNull WindowContainerTransaction wct) {
+ @NonNull WindowContainerTransaction wct,
+ Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
+ mOnAnimationFinishedCallback = onAnimationEndCallback;
final IBinder token = mTransitions.startTransition(type, wct, this);
mPendingTransitionTokens.add(token);
}
@@ -86,11 +91,14 @@
* Starts Transition of type TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
* @param wct WindowContainerTransaction for transition
* @param startPosition Position of task when transition is triggered
+ * @param onAnimationEndCallback to be called after animation
*/
public void startCancelMoveToDesktopMode(@NonNull WindowContainerTransaction wct,
- Point startPosition) {
+ Point startPosition,
+ Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
mStartPosition = startPosition;
- startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct);
+ startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct,
+ mOnAnimationFinishedCallback);
}
@Override
@@ -111,7 +119,7 @@
if (change.getMode() == WindowManager.TRANSIT_CHANGE) {
transitionHandled |= startChangeTransition(
- transition, info.getType(), change, startT, finishCallback);
+ transition, info.getType(), change, startT, finishT, finishCallback);
}
}
@@ -125,6 +133,7 @@
@WindowManager.TransitionType int type,
@NonNull TransitionInfo.Change change,
@NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
if (!mPendingTransitionTokens.contains(transition)) {
return false;
@@ -178,6 +187,9 @@
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
+ if (mOnAnimationFinishedCallback != null) {
+ mOnAnimationFinishedCallback.accept(finishT);
+ }
mTransitions.getMainExecutor().execute(
() -> finishCallback.onTransitionFinished(null, null));
}
@@ -204,7 +216,7 @@
animator.setDuration(FREEFORM_ANIMATION_DURATION);
final SurfaceControl.Transaction t = mTransactionSupplier.get();
animator.addUpdateListener(animation -> {
- final float scale = animation.getAnimatedFraction();
+ final float scale = (float) animation.getAnimatedValue();
t.setPosition(sc, mStartPosition.x * (1 - scale), mStartPosition.y * (1 - scale))
.setScale(sc, scale, scale)
.show(sc)
@@ -213,6 +225,9 @@
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
+ if (mOnAnimationFinishedCallback != null) {
+ mOnAnimationFinishedCallback.accept(finishT);
+ }
mTransitions.getMainExecutor().execute(
() -> finishCallback.onTransitionFinished(null, null));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
index fa3eee2..160a83d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -42,6 +42,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -54,7 +55,7 @@
private final Context mContext;
private final Transitions mTransitions;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
-
+ private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
private Supplier<SurfaceControl.Transaction> mTransactionSupplier;
public ExitDesktopTaskTransitionHandler(
@@ -76,9 +77,12 @@
* Starts Transition of a given type
* @param type Transition type
* @param wct WindowContainerTransaction for transition
+ * @param onAnimationEndCallback to be called after animation
*/
public void startTransition(@WindowManager.TransitionType int type,
- @NonNull WindowContainerTransaction wct) {
+ @NonNull WindowContainerTransaction wct,
+ Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
+ mOnAnimationFinishedCallback = onAnimationEndCallback;
final IBinder token = mTransitions.startTransition(type, wct, this);
mPendingTransitionTokens.add(token);
}
@@ -101,7 +105,7 @@
if (change.getMode() == WindowManager.TRANSIT_CHANGE) {
transitionHandled |= startChangeTransition(
- transition, info.getType(), change, startT, finishCallback);
+ transition, info.getType(), change, startT, finishT, finishCallback);
}
}
@@ -116,6 +120,7 @@
@WindowManager.TransitionType int type,
@NonNull TransitionInfo.Change change,
@NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
if (!mPendingTransitionTokens.contains(transition)) {
return false;
@@ -156,6 +161,9 @@
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
+ if (mOnAnimationFinishedCallback != null) {
+ mOnAnimationFinishedCallback.accept(finishT);
+ }
mTransitions.getMainExecutor().execute(
() -> finishCallback.onTransitionFinished(null, null));
}
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 d0739e1..899d672 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 @@
*/
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/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 091de3a..be2489d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -35,10 +35,14 @@
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP;
+
import android.content.ClipDescription;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.HardwareRenderer;
import android.graphics.PixelFormat;
import android.util.Slog;
import android.util.SparseArray;
@@ -50,6 +54,8 @@
import android.view.WindowManager;
import android.widget.FrameLayout;
+import androidx.annotation.BinderThread;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.InstanceId;
@@ -58,25 +64,31 @@
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
+import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalMainThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import java.io.PrintWriter;
import java.util.ArrayList;
/**
* Handles the global drag and drop handling for the Shell.
*/
-public class DragAndDropController implements DisplayController.OnDisplaysChangedListener,
+public class DragAndDropController implements RemoteCallable<DragAndDropController>,
+ DisplayController.OnDisplaysChangedListener,
View.OnDragListener, ComponentCallbacks2 {
private static final String TAG = DragAndDropController.class.getSimpleName();
private final Context mContext;
private final ShellController mShellController;
+ private final ShellCommandHandler mShellCommandHandler;
private final DisplayController mDisplayController;
private final DragAndDropEventLogger mLogger;
private final IconProvider mIconProvider;
@@ -100,6 +112,7 @@
public static DragAndDropController create(Context context,
ShellInit shellInit,
ShellController shellController,
+ ShellCommandHandler shellCommandHandler,
DisplayController displayController,
UiEventLogger uiEventLogger,
IconProvider iconProvider,
@@ -107,19 +120,21 @@
if (!context.getResources().getBoolean(R.bool.config_enableShellDragDrop)) {
return null;
}
- return new DragAndDropController(context, shellInit, shellController, displayController,
- uiEventLogger, iconProvider, mainExecutor);
+ return new DragAndDropController(context, shellInit, shellController, shellCommandHandler,
+ displayController, uiEventLogger, iconProvider, mainExecutor);
}
DragAndDropController(Context context,
ShellInit shellInit,
ShellController shellController,
+ ShellCommandHandler shellCommandHandler,
DisplayController displayController,
UiEventLogger uiEventLogger,
IconProvider iconProvider,
ShellExecutor mainExecutor) {
mContext = context;
mShellController = shellController;
+ mShellCommandHandler = shellCommandHandler;
mDisplayController = displayController;
mLogger = new DragAndDropEventLogger(uiEventLogger);
mIconProvider = iconProvider;
@@ -137,6 +152,23 @@
mMainExecutor.executeDelayed(() -> {
mDisplayController.addDisplayWindowListener(this);
}, 0);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_DRAG_AND_DROP,
+ this::createExternalInterface, this);
+ mShellCommandHandler.addDumpCallback(this::dump, this);
+ }
+
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IDragAndDropImpl(this);
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
}
/**
@@ -156,7 +188,7 @@
mListeners.remove(listener);
}
- private void notifyListeners() {
+ private void notifyDragStarted() {
for (int i = 0; i < mListeners.size(); i++) {
mListeners.get(i).onDragStarted();
}
@@ -273,7 +305,7 @@
pd.dragLayout.prepare(mDisplayController.getDisplayLayout(displayId),
event.getClipData(), loggerSessionId);
setDropTargetWindowVisibility(pd, View.VISIBLE);
- notifyListeners();
+ notifyDragStarted();
break;
case ACTION_DRAG_ENTERED:
pd.dragLayout.show();
@@ -327,13 +359,7 @@
}
private void setDropTargetWindowVisibility(PerDisplay pd, int visibility) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
- "Set drop target window visibility: displayId=%d visibility=%d",
- pd.displayId, visibility);
- pd.rootView.setVisibility(visibility);
- if (visibility == View.VISIBLE) {
- pd.rootView.requestApplyInsets();
- }
+ pd.setWindowVisibility(visibility);
}
private String getMimeTypes(ClipDescription description) {
@@ -347,6 +373,18 @@
return mimeTypes;
}
+ /**
+ * Returns if any displays are currently ready to handle a drag/drop.
+ */
+ private boolean isReadyToHandleDrag() {
+ for (int i = 0; i < mDisplayDropTargets.size(); i++) {
+ if (mDisplayDropTargets.valueAt(i).mHasDrawn) {
+ return true;
+ }
+ }
+ return false;
+ }
+
// Note: Component callbacks are always called on the main thread of the process
@ExternalMainThread
@Override
@@ -372,12 +410,53 @@
// Do nothing
}
- private static class PerDisplay {
+ /**
+ * Dumps information about this controller.
+ */
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ pw.println(prefix + TAG);
+ pw.println(prefix + " listeners=" + mListeners.size());
+ }
+
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private static class IDragAndDropImpl extends IDragAndDrop.Stub
+ implements ExternalInterfaceBinder {
+ private DragAndDropController mController;
+
+ public IDragAndDropImpl(DragAndDropController controller) {
+ mController = controller;
+ }
+
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ @Override
+ public void invalidate() {
+ mController = null;
+ }
+
+ @Override
+ public boolean isReadyToHandleDrag() {
+ boolean[] result = new boolean[1];
+ executeRemoteCallWithTaskPermission(mController, "isReadyToHandleDrag",
+ controller -> result[0] = controller.isReadyToHandleDrag(),
+ true /* blocking */
+ );
+ return result[0];
+ }
+ }
+
+ private static class PerDisplay implements HardwareRenderer.FrameDrawingCallback {
final int displayId;
final Context context;
final WindowManager wm;
final FrameLayout rootView;
final DragLayout dragLayout;
+ // Tracks whether the window has fully drawn since it was last made visible
+ boolean mHasDrawn;
boolean isHandlingDrag;
// A count of the number of active drags in progress to ensure that we only hide the window
@@ -391,5 +470,25 @@
rootView = rv;
dragLayout = dl;
}
+
+ private void setWindowVisibility(int visibility) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Set drop target window visibility: displayId=%d visibility=%d",
+ displayId, visibility);
+ rootView.setVisibility(visibility);
+ if (visibility == View.VISIBLE) {
+ rootView.requestApplyInsets();
+ if (!mHasDrawn && rootView.getViewRootImpl() != null) {
+ rootView.getViewRootImpl().registerRtFrameCallback(this);
+ }
+ } else {
+ mHasDrawn = false;
+ }
+ }
+
+ @Override
+ public void onFrameDraw(long frame) {
+ mHasDrawn = true;
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
index 28f59b5..724a130 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
@@ -104,7 +104,7 @@
setContainerMargin(0, 0, 0, 0); // make sure it's populated
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
- mMarginColor = getResources().getColor(R.color.taskbar_background);
+ mMarginColor = getResources().getColor(R.color.taskbar_background_dark);
int c = getResources().getColor(android.R.color.system_accent1_500);
mHighlightColor = Color.argb(HIGHLIGHT_ALPHA, Color.red(c), Color.green(c), Color.blue(c));
mSplashScreenColor = Color.argb(SPLASHSCREEN_ALPHA, 0, 0, 0);
@@ -125,7 +125,7 @@
public void onThemeChange() {
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(getContext());
- mMarginColor = getResources().getColor(R.color.taskbar_background);
+ mMarginColor = getResources().getColor(R.color.taskbar_background_dark);
mHighlightColor = getResources().getColor(android.R.color.system_accent1_500);
if (mMarginPercent > 0) {
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/IDragAndDrop.aidl
similarity index 61%
copy from services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java
copy to libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/IDragAndDrop.aidl
index 7c339d2..aeb0c63 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/IDragAndDrop.aidl
@@ -14,14 +14,15 @@
* limitations under the License.
*/
-package com.android.server.companion.datatransfer.contextsync;
+package com.android.wm.shell.draganddrop;
-/** Callback for call metadata syncing. */
-public abstract class CallMetadataSyncCallback {
-
- abstract void processCallControlAction(int crossDeviceCallId, int callControlAction);
-
- abstract void requestCrossDeviceSync(int userId);
-
- abstract void updateStatus(int userId, boolean shouldSyncCallMetadata);
+/**
+ * Interface that is exposed to remote callers to manipulate drag and drop.
+ */
+interface IDragAndDrop {
+ /**
+ * Returns whether the shell drop target is showing and will handle a drag/drop.
+ */
+ boolean isReadyToHandleDrag() = 1;
}
+// Last id = 1
\ 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 48487bc..22541bbd 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 @@
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 @@
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 @@
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/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index e1a56a1..6b6a7bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -153,6 +153,8 @@
@Override
public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
+ mWindowDecorViewModel.onTransitionMerged(merged, playing);
+
final List<ActivityManager.RunningTaskInfo> infoOfMerged =
mTransitionToTaskInfo.get(merged);
if (infoOfMerged == null) {
@@ -169,8 +171,6 @@
} else {
mTransitionToTaskInfo.put(playing, infoOfMerged);
}
-
- mWindowDecorViewModel.onTransitionMerged(merged, playing);
}
@Override
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 566c130..6cedcf5 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
@@ -27,6 +27,7 @@
import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
import static com.android.wm.shell.pip.PipAnimationController.FRACTION_START;
@@ -72,7 +73,6 @@
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 +139,6 @@
// 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 +164,7 @@
final int direction = animator.getTransitionDirection();
if (mIsCancelled) {
sendOnPipTransitionFinished(direction);
+ maybePerformFinishResizeCallback();
return;
}
final int animationType = animator.getAnimationType();
@@ -199,6 +189,10 @@
|| 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
@@ -531,12 +525,18 @@
}
}
- final Rect destinationBounds = getExitDestinationBounds();
+ final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+ final Rect destinationBounds = new Rect(displayBounds);
final int direction = syncWithSplitScreenBounds(destinationBounds, requestEnterSplit)
? TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN
: TRANSITION_DIRECTION_LEAVE_PIP;
+ // For exiting to fullscreen, the windowing mode of task will be changed to fullscreen
+ // until the animation is finished. Otherwise if the activity is resumed and focused at the
+ // begin of aniamtion, the app may do something too early to distub the animation.
+ final boolean toFullscreen = destinationBounds.equals(displayBounds);
- if (Transitions.ENABLE_SHELL_TRANSITIONS && direction == TRANSITION_DIRECTION_LEAVE_PIP) {
+ if (Transitions.SHELL_TRANSITIONS_ROTATION || (Transitions.ENABLE_SHELL_TRANSITIONS
+ && !toFullscreen)) {
// When exit to fullscreen with Shell transition enabled, we update the Task windowing
// mode directly so that it can also trigger display rotation and visibility update in
// the same transition if there will be any.
@@ -612,7 +612,7 @@
removePip();
}
- private void applyWindowingModeChangeOnExit(WindowContainerTransaction wct, int direction) {
+ void applyWindowingModeChangeOnExit(WindowContainerTransaction wct, int direction) {
// Reset the final windowing mode.
wct.setWindowingMode(mToken, getOutPipWindowingMode());
// Simply reset the activity mode set prior to the animation running.
@@ -1614,12 +1614,8 @@
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);
}
}
@@ -1782,14 +1778,26 @@
* @return {@code true} if destinationBounds is altered for split screen
*/
private boolean syncWithSplitScreenBounds(Rect destinationBoundsOut, boolean enterSplit) {
- if (!enterSplit || !mSplitScreenOptional.isPresent()) {
+ if (mSplitScreenOptional.isEmpty()) {
+ return false;
+ }
+ final SplitScreenController split = mSplitScreenOptional.get();
+ final int position = mTaskInfo.lastParentTaskIdBeforePip > 0
+ ? split.getSplitPosition(mTaskInfo.lastParentTaskIdBeforePip)
+ : SPLIT_POSITION_UNDEFINED;
+ if (position == SPLIT_POSITION_UNDEFINED && !enterSplit) {
return false;
}
final Rect topLeft = new Rect();
final Rect bottomRight = new Rect();
- mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight);
- destinationBoundsOut.set(isPipToTopLeft() ? topLeft : bottomRight);
- return true;
+ split.getStageBounds(topLeft, bottomRight);
+ if (enterSplit) {
+ destinationBoundsOut.set(isPipToTopLeft() ? topLeft : bottomRight);
+ return true;
+ }
+ // Moving to an existing split task.
+ destinationBoundsOut.set(position == SPLIT_POSITION_TOP_OR_LEFT ? topLeft : bottomRight);
+ return false;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 99cb6f7..98db707 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -508,8 +508,16 @@
currentBounds.offset(-offset.x, -offset.y);
startTransaction.setPosition(pipLeash, currentBounds.left, currentBounds.top);
+ final WindowContainerToken pipTaskToken = pipChange.getContainer();
+ final boolean toFullscreen = pipChange.getEndAbsBounds().equals(
+ mPipBoundsState.getDisplayBounds());
mFinishCallback = (wct, wctCB) -> {
mPipOrganizer.onExitPipFinished(taskInfo);
+ if (!Transitions.SHELL_TRANSITIONS_ROTATION && toFullscreen) {
+ wct = wct != null ? wct : new WindowContainerTransaction();
+ wct.setBounds(pipTaskToken, null);
+ mPipOrganizer.applyWindowingModeChangeOnExit(wct, TRANSITION_DIRECTION_LEAVE_PIP);
+ }
finishCallback.onTransitionFinished(wct, wctCB);
};
mFinishTransaction = finishTransaction;
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 b0bb14b..f8e1435 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 @@
/** 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 956af70..281cae5 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 @@
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 @@
mPhonePipMenuController = menuActivityController;
mPipUiEventLogger = pipUiEventLogger;
mPinchResizingAlgorithm = new PipPinchResizingAlgorithm();
+ mEnableTouch = true;
mUpdateResizeBoundsCallback = (rect) -> {
mUserResizeBounds.set(rect);
@@ -248,6 +250,11 @@
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 @@
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 c5bfd87..5c9709c 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 @@
}
@Override
- public void onActiveTasksChanged() {
+ public void onActiveTasksChanged(int displayId) {
notifyRecentTasksChanged();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index bffc51c..3e568e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -48,6 +48,7 @@
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -97,7 +98,8 @@
mMixers.remove(mixer);
}
- void startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
+ @VisibleForTesting
+ public IBinder startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
IApplicationThread appThread, IRecentsAnimationRunner listener) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"RecentsTransitionHandler.startRecentsTransition");
@@ -121,12 +123,13 @@
if (mixer != null) {
mixer.setRecentsTransition(transition);
}
- if (transition == null) {
+ if (transition != null) {
+ controller.setTransition(transition);
+ mControllers.add(controller);
+ } else {
controller.cancel("startRecentsTransition");
- return;
}
- controller.setTransition(transition);
- mControllers.add(controller);
+ return transition;
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 0ef26fc..e5ae10c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -39,6 +39,7 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
@@ -378,40 +379,18 @@
boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition,
WindowContainerTransaction wct) {
- StageTaskListener targetStage;
- int sideStagePosition;
- if (isSplitScreenVisible()) {
- // If the split screen is foreground, retrieves target stage based on position.
- targetStage = stagePosition == mSideStagePosition ? mSideStage : mMainStage;
- sideStagePosition = mSideStagePosition;
+ prepareEnterSplitScreen(wct, task, stagePosition);
+ if (ENABLE_SHELL_TRANSITIONS) {
+ mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct,
+ null, this, null /* consumedCallback */, null /* finishedCallback */,
+ isSplitScreenVisible()
+ ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN);
} else {
- targetStage = mSideStage;
- sideStagePosition = stagePosition;
- }
-
- if (!isSplitActive()) {
- mSplitLayout.init();
- prepareEnterSplitScreen(wct, task, stagePosition);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
- setDividerVisibility(true, t);
});
- } else {
- setSideStagePosition(sideStagePosition, wct);
- targetStage.addTask(task, wct);
- targetStage.evictAllChildren(wct);
- if (!isSplitScreenVisible()) {
- final StageTaskListener anotherStage = targetStage == mMainStage
- ? mSideStage : mMainStage;
- anotherStage.reparentTopTask(wct);
- anotherStage.evictAllChildren(wct);
- wct.reorder(mRootTaskInfo.token, true);
- }
- setRootForceTranslucent(false, wct);
- mSyncQueue.queue(wct);
}
-
// Due to drag already pip task entering split by this method so need to reset flag here.
mIsDropEntering = false;
return true;
@@ -1509,9 +1488,51 @@
*/
void prepareEnterSplitScreen(WindowContainerTransaction wct,
@Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition) {
- if (mMainStage.isActive()) return;
-
onSplitScreenEnter();
+ if (isSplitActive()) {
+ prepareBringSplit(wct, taskInfo, startPosition);
+ } else {
+ prepareActiveSplit(wct, taskInfo, startPosition);
+ }
+ }
+
+ private void prepareBringSplit(WindowContainerTransaction wct,
+ @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition) {
+ StageTaskListener targetStage;
+ if (isSplitScreenVisible()) {
+ // If the split screen is foreground, retrieves target stage based on position.
+ targetStage = startPosition == mSideStagePosition ? mSideStage : mMainStage;
+ } else {
+ targetStage = mSideStage;
+ }
+
+ if (taskInfo != null) {
+ wct.startTask(taskInfo.taskId,
+ resolveStartStage(STAGE_TYPE_UNDEFINED, startPosition, null, wct));
+ targetStage.evictAllChildren(wct);
+ }
+ // If running background, we need to reparent current top visible task to another stage
+ // and evict all tasks current under its.
+ if (!isSplitScreenVisible()) {
+ // Recreate so we need to reset position rather than keep position of background split.
+ mSplitLayout.resetDividerPosition();
+ updateWindowBounds(mSplitLayout, wct);
+ final StageTaskListener anotherStage = targetStage == mMainStage
+ ? mSideStage : mMainStage;
+ anotherStage.evictAllChildren(wct);
+ anotherStage.reparentTopTask(wct);
+ wct.reorder(mRootTaskInfo.token, true);
+ setRootForceTranslucent(false, wct);
+ }
+ }
+
+ private void prepareActiveSplit(WindowContainerTransaction wct,
+ @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition) {
+ if (!ENABLE_SHELL_TRANSITIONS) {
+ // Legacy transition we need to create divider here, shell transition case we will
+ // create it on #finishEnterSplitScreen
+ mSplitLayout.init();
+ }
if (taskInfo != null) {
setSideStagePosition(startPosition, wct);
mSideStage.addTask(taskInfo, wct);
@@ -2383,7 +2404,17 @@
}
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (taskInfo == null || !taskInfo.hasParentTask()) continue;
+ if (taskInfo == null) continue;
+ if (taskInfo.token.equals(mRootTaskInfo.token)) {
+ if (isOpeningType(change.getMode())) {
+ // Split is opened by someone so set it as visible.
+ setSplitsVisible(true);
+ } else if (isClosingType(change.getMode())) {
+ // Split is closed by someone so set it as invisible.
+ setSplitsVisible(false);
+ }
+ continue;
+ }
final StageTaskListener stage = getStageOfTask(taskInfo);
if (stage == null) continue;
if (isOpeningType(change.getMode())) {
@@ -2823,12 +2854,18 @@
final String childPrefix = innerPrefix + " ";
pw.println(prefix + TAG + " mDisplayId=" + mDisplayId);
pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible);
+ pw.println(innerPrefix + "isSplitActive=" + isSplitActive());
+ pw.println(innerPrefix + "isSplitVisible=" + isSplitScreenVisible());
pw.println(innerPrefix + "MainStage");
- pw.println(childPrefix + "stagePosition=" + getMainStagePosition());
+ pw.println(childPrefix + "stagePosition=" + splitPositionToString(getMainStagePosition()));
pw.println(childPrefix + "isActive=" + mMainStage.isActive());
+ mMainStage.dump(pw, childPrefix);
+ pw.println(innerPrefix + "MainStageListener");
mMainStageListener.dump(pw, childPrefix);
pw.println(innerPrefix + "SideStage");
- pw.println(childPrefix + "stagePosition=" + getSideStagePosition());
+ pw.println(childPrefix + "stagePosition=" + splitPositionToString(getSideStagePosition()));
+ mSideStage.dump(pw, childPrefix);
+ pw.println(innerPrefix + "SideStageListener");
mSideStageListener.dump(pw, childPrefix);
if (mMainStage.isActive()) {
pw.println(innerPrefix + "SplitLayout");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index a841b7f..18b09b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -421,6 +421,13 @@
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
final String childPrefix = innerPrefix + " ";
- pw.println(prefix + this);
+ if (mChildrenTaskInfo.size() > 0) {
+ pw.println(prefix + "Children list:");
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
+ pw.println(childPrefix + "Task#" + i + " taskID=" + taskInfo.taskId
+ + " baseActivity=" + taskInfo.baseActivity);
+ }
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
index 8a4d4c2..ae72220 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
@@ -477,15 +477,15 @@
}
@Override
- public void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
+ public boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
if (mRootView == null) {
- return;
+ return true;
}
if (mSplashView == null) {
// shouldn't happen, the app window may be drawn earlier than starting window?
Slog.e(TAG, "Found empty splash screen, remove!");
removeWindowInner(mRootView, false);
- return;
+ return true;
}
clearSystemBarColor();
if (immediately
@@ -503,6 +503,7 @@
removeWindowInner(mRootView, true);
}
}
+ return true;
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index ff06db3..7cbf263 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -18,6 +18,8 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NORMAL;
+import static android.window.StartingWindowRemovalInfo.DEFER_MODE_ROTATION;
import android.annotation.CallSuper;
import android.app.TaskInfo;
@@ -216,7 +218,17 @@
}
abstract static class StartingWindowRecord {
protected int mBGColor;
- abstract void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately);
+
+ /**
+ * Remove the starting window with the given {@link StartingWindowRemovalInfo} if possible.
+ * @param info The removal info sent from the task organizer controller in the WM core.
+ * @param immediately {@code true} means removing the starting window immediately,
+ * {@code false} otherwise.
+ * @return {@code true} means {@link StartingWindowRecordManager} can safely remove the
+ * record itself. {@code false} means {@link StartingWindowRecordManager} requires
+ * to manage the record reference and remove it later.
+ */
+ abstract boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately);
int getBGColor() {
return mBGColor;
}
@@ -231,6 +243,15 @@
* {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}.
*/
private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600;
+
+ /**
+ * The max delay time in milliseconds for removing the task snapshot window with IME
+ * visible after the fixed rotation finished.
+ * Ideally the delay time will be shorter when receiving
+ * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}.
+ */
+ private static final long MAX_DELAY_REMOVAL_TIME_FIXED_ROTATION = 3000;
+
private final Runnable mScheduledRunnable = this::removeImmediately;
@WindowConfiguration.ActivityType protected final int mActivityType;
@@ -242,24 +263,34 @@
}
@Override
- public final void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
+ public final boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
if (immediately) {
removeImmediately();
} else {
- scheduleRemove(info.deferRemoveForIme);
+ scheduleRemove(info.deferRemoveForImeMode);
+ return false;
}
+ return true;
}
- void scheduleRemove(boolean deferRemoveForIme) {
+ void scheduleRemove(@StartingWindowRemovalInfo.DeferMode int deferRemoveForImeMode) {
// Show the latest content as soon as possible for unlocking to home.
if (mActivityType == ACTIVITY_TYPE_HOME) {
removeImmediately();
return;
}
mRemoveExecutor.removeCallbacks(mScheduledRunnable);
- final long delayRemovalTime = hasImeSurface() && deferRemoveForIme
- ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE
- : DELAY_REMOVAL_TIME_GENERAL;
+ final long delayRemovalTime;
+ switch (deferRemoveForImeMode) {
+ case DEFER_MODE_ROTATION:
+ delayRemovalTime = MAX_DELAY_REMOVAL_TIME_FIXED_ROTATION;
+ break;
+ case DEFER_MODE_NORMAL:
+ delayRemovalTime = MAX_DELAY_REMOVAL_TIME_IME_VISIBLE;
+ break;
+ default:
+ delayRemovalTime = DELAY_REMOVAL_TIME_GENERAL;
+ }
mRemoveExecutor.executeDelayed(mScheduledRunnable, delayRemovalTime);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
"Defer removing snapshot surface in %d", delayRemovalTime);
@@ -297,8 +328,10 @@
final int taskId = removeInfo.taskId;
final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
if (record != null) {
- record.removeIfPossible(removeInfo, immediately);
- mStartingWindowRecords.remove(taskId);
+ final boolean canRemoveRecord = record.removeIfPossible(removeInfo, immediately);
+ if (canRemoveRecord) {
+ mStartingWindowRecords.remove(taskId);
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
index 12a0d40..98a8031 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
@@ -124,7 +124,7 @@
}
@Override
- public void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
+ public boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
if (!immediately) {
mSplashscreenContentDrawer.applyExitAnimation(mSplashView,
info.windowAnimationLeash, info.mainFrame,
@@ -132,6 +132,7 @@
} else {
release();
}
+ return true;
}
void release() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
index bfa6390..5f54f58 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
@@ -42,4 +42,6 @@
public static final String KEY_EXTRA_SHELL_FLOATING_TASKS = "extra_shell_floating_tasks";
// See IDesktopMode.aidl
public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
+ // See IDragAndDrop.aidl
+ public static final String KEY_EXTRA_SHELL_DRAG_AND_DROP = "extra_shell_drag_and_drop";
}
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 7991c52..2ab4c75 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 @@
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 81d69a4..2faed3a 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
@@ -202,6 +202,7 @@
}
void setTaskViewVisible(TaskViewTaskController taskView, boolean visible) {
+ if (mTaskViews.get(taskView) == null) return;
if (mTaskViews.get(taskView).mVisible == visible) return;
if (taskView.getTaskInfo() == null) {
// Nothing to update, task is not yet available
@@ -220,17 +221,19 @@
void updateBoundsState(TaskViewTaskController taskView, Rect boundsOnScreen) {
TaskViewRequestedState state = mTaskViews.get(taskView);
+ if (state == null) return;
state.mBounds.set(boundsOnScreen);
}
void updateVisibilityState(TaskViewTaskController taskView, boolean visible) {
TaskViewRequestedState state = mTaskViews.get(taskView);
+ if (state == null) return;
state.mVisible = visible;
}
void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen) {
TaskViewRequestedState state = mTaskViews.get(taskView);
- if (Objects.equals(boundsOnScreen, state.mBounds)) {
+ if (state == null || Objects.equals(boundsOnScreen, state.mBounds)) {
return;
}
state.mBounds.set(boundsOnScreen);
@@ -336,6 +339,19 @@
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/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 7c729a4..4942932 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -73,7 +73,7 @@
/** Pip was entered while handling an intent with its own remoteTransition. */
static final int TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE = 3;
- /** Recents transition while split-screen active. */
+ /** Recents transition while split-screen foreground. */
static final int TYPE_RECENTS_DURING_SPLIT = 4;
/** The default animation for this mixed transition. */
@@ -152,7 +152,7 @@
@NonNull TransitionRequestInfo request) {
if (mPipHandler.requestHasPipEnter(request) && mSplitHandler.isSplitScreenVisible()) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a PiP-enter request while "
- + "Split-Screen is active, so treat it as Mixed.");
+ + "Split-Screen is foreground, so treat it as Mixed.");
if (request.getRemoteTransition() != null) {
throw new IllegalStateException("Unexpected remote transition in"
+ "pip-enter-from-split request");
@@ -183,13 +183,13 @@
mixed.mLeftoversHandler = handler.first;
mActiveTransitions.add(mixed);
return handler.second;
- } else if (mSplitHandler.isSplitActive()
+ } else if (mSplitHandler.isSplitScreenVisible()
&& isOpeningType(request.getType())
&& request.getTriggerTask() != null
&& request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
&& request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_HOME) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a going-home request while "
- + "Split-Screen is active, so treat it as Mixed.");
+ + "Split-Screen is foreground, so treat it as Mixed.");
Pair<Transitions.TransitionHandler, WindowContainerTransaction> handler =
mPlayer.dispatchRequest(transition, request, this);
if (handler == null) {
@@ -211,7 +211,7 @@
@Override
public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) {
- if (mRecentsHandler != null && mSplitHandler.isSplitActive()) {
+ if (mRecentsHandler != null && mSplitHandler.isSplitScreenVisible()) {
return this;
}
return null;
@@ -219,9 +219,9 @@
@Override
public void setRecentsTransition(IBinder transition) {
- if (mSplitHandler.isSplitActive()) {
+ if (mSplitHandler.isSplitScreenVisible()) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
- + "Split-Screen is active, so treat it as Mixed.");
+ + "Split-Screen is foreground, so treat it as Mixed.");
final MixedTransition mixed = new MixedTransition(
MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
mixed.mLeftoversHandler = mRecentsHandler;
@@ -351,7 +351,7 @@
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
- + "entering PIP while Split-Screen is active.");
+ + "entering PIP while Split-Screen is foreground.");
TransitionInfo.Change pipChange = null;
TransitionInfo.Change wallpaper = null;
final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 6e9ecda..1ee52ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -21,6 +21,7 @@
import static android.app.ActivityOptions.ANIM_NONE;
import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
import static android.app.ActivityOptions.ANIM_SCALE_UP;
+import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
@@ -625,6 +626,9 @@
} else if ((changeFlags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0 && isOpeningType) {
// This received a transferred starting window, so don't animate
return null;
+ } else if (overrideType == ANIM_SCENE_TRANSITION) {
+ // If there's a scene-transition, then jump-cut.
+ return null;
} else {
a = loadAttributeAnimation(info, change, wallpaperTransit, mTransitionAnimation);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index d25318d..9ce2209 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -144,6 +144,7 @@
.setCaptureSecureLayers(true)
.setAllowProtected(true)
.setSourceCrop(new Rect(0, 0, mStartWidth, mStartHeight))
+ .setHintForSeamlessTransition(true)
.build();
ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
ScreenCapture.captureLayers(args);
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 9d7c39f..0cede90 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
@@ -18,25 +18,29 @@
import static android.os.Build.IS_USER;
-import static com.android.wm.shell.WmShellTransitionTraceProto.MAGIC_NUMBER;
-import static com.android.wm.shell.WmShellTransitionTraceProto.MAGIC_NUMBER_H;
-import static com.android.wm.shell.WmShellTransitionTraceProto.MAGIC_NUMBER_L;
+import static com.android.wm.shell.nano.WmShellTransitionTraceProto.MAGIC_NUMBER_H;
+import static com.android.wm.shell.nano.WmShellTransitionTraceProto.MAGIC_NUMBER_L;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.SystemClock;
import android.os.Trace;
import android.util.Log;
-import android.util.proto.ProtoOutputStream;
import com.android.internal.util.TraceBuffer;
+import com.android.wm.shell.nano.HandlerMapping;
import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.google.protobuf.nano.MessageNano;
+
import java.io.File;
import java.io.IOException;
+import java.io.OutputStream;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
+import java.util.Queue;
/**
* Helper class to collect and dump transition traces.
@@ -54,8 +58,35 @@
private final Object mEnabledLock = new Object();
private boolean mActiveTracingEnabled = false;
- private final TraceBuffer mTraceBuffer = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY,
- (proto) -> handleOnEntryRemovedFromTrace(proto));
+ private final TraceBuffer.ProtoProvider mProtoProvider =
+ new TraceBuffer.ProtoProvider<MessageNano,
+ com.android.wm.shell.nano.WmShellTransitionTraceProto,
+ com.android.wm.shell.nano.Transition>() {
+ @Override
+ public int getItemSize(MessageNano proto) {
+ return proto.getCachedSize();
+ }
+
+ @Override
+ public byte[] getBytes(MessageNano proto) {
+ return MessageNano.toByteArray(proto);
+ }
+
+ @Override
+ public void write(
+ com.android.wm.shell.nano.WmShellTransitionTraceProto encapsulatingProto,
+ Queue<com.android.wm.shell.nano.Transition> buffer, OutputStream os)
+ throws IOException {
+ encapsulatingProto.transitions = buffer.toArray(
+ new com.android.wm.shell.nano.Transition[0]);
+ os.write(getBytes(encapsulatingProto));
+ }
+ };
+ private final TraceBuffer<MessageNano,
+ com.android.wm.shell.nano.WmShellTransitionTraceProto,
+ com.android.wm.shell.nano.Transition> mTraceBuffer
+ = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY, mProtoProvider,
+ (proto) -> handleOnEntryRemovedFromTrace(proto));
private final Map<Object, Runnable> mRemovedFromTraceCallbacks = new HashMap<>();
private final Map<Transitions.TransitionHandler, Integer> mHandlerIds = new HashMap<>();
@@ -73,30 +104,25 @@
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);
}
- ProtoOutputStream outputStream = new ProtoOutputStream();
- final long protoToken =
- outputStream.start(com.android.wm.shell.WmShellTransitionTraceProto.TRANSITIONS);
-
- outputStream.write(com.android.wm.shell.Transition.ID, transitionId);
- outputStream.write(com.android.wm.shell.Transition.DISPATCH_TIME_NS,
- SystemClock.elapsedRealtimeNanos());
- outputStream.write(com.android.wm.shell.Transition.HANDLER, handlerId);
-
- outputStream.end(protoToken);
+ com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
+ proto.id = transitionId;
+ proto.dispatchTimeNs = SystemClock.elapsedRealtimeNanos();
+ proto.handler = handlerId;
final int useCountAfterAdd = mHandlerUseCountInTrace.getOrDefault(handler, 0) + 1;
mHandlerUseCountInTrace.put(handler, useCountAfterAdd);
- mRemovedFromTraceCallbacks.put(outputStream, () -> {
+ mRemovedFromTraceCallbacks.put(proto, () -> {
final int useCountAfterRemove = mHandlerUseCountInTrace.get(handler) - 1;
mHandlerUseCountInTrace.put(handler, useCountAfterRemove);
});
- mTraceBuffer.add(outputStream);
+ mTraceBuffer.add(proto);
}
/**
@@ -106,18 +132,12 @@
* @param playingTransitionId The id of the transition we was to merge the transition into.
*/
public void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
- ProtoOutputStream outputStream = new ProtoOutputStream();
- final long protoToken =
- outputStream.start(com.android.wm.shell.WmShellTransitionTraceProto.TRANSITIONS);
+ com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
+ proto.id = mergeRequestedTransitionId;
+ proto.mergeRequestTimeNs = SystemClock.elapsedRealtimeNanos();
+ proto.mergedInto = playingTransitionId;
- outputStream.write(com.android.wm.shell.Transition.ID, mergeRequestedTransitionId);
- outputStream.write(com.android.wm.shell.Transition.MERGE_REQUEST_TIME_NS,
- SystemClock.elapsedRealtimeNanos());
- outputStream.write(com.android.wm.shell.Transition.MERGED_INTO, playingTransitionId);
-
- outputStream.end(protoToken);
-
- mTraceBuffer.add(outputStream);
+ mTraceBuffer.add(proto);
}
/**
@@ -127,18 +147,12 @@
* @param playingTransitionId The id of the transition the transition was merged into.
*/
public void logMerged(int mergedTransitionId, int playingTransitionId) {
- ProtoOutputStream outputStream = new ProtoOutputStream();
- final long protoToken =
- outputStream.start(com.android.wm.shell.WmShellTransitionTraceProto.TRANSITIONS);
+ com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
+ proto.id = mergedTransitionId;
+ proto.mergeTimeNs = SystemClock.elapsedRealtimeNanos();
+ proto.mergedInto = playingTransitionId;
- outputStream.write(com.android.wm.shell.Transition.ID, mergedTransitionId);
- outputStream.write(
- com.android.wm.shell.Transition.MERGE_TIME_NS, SystemClock.elapsedRealtimeNanos());
- outputStream.write(com.android.wm.shell.Transition.MERGED_INTO, playingTransitionId);
-
- outputStream.end(protoToken);
-
- mTraceBuffer.add(outputStream);
+ mTraceBuffer.add(proto);
}
/**
@@ -147,17 +161,11 @@
* @param transitionId The id of the transition that was aborted.
*/
public void logAborted(int transitionId) {
- ProtoOutputStream outputStream = new ProtoOutputStream();
- final long protoToken =
- outputStream.start(com.android.wm.shell.WmShellTransitionTraceProto.TRANSITIONS);
+ com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
+ proto.id = transitionId;
+ proto.abortTimeNs = SystemClock.elapsedRealtimeNanos();
- outputStream.write(com.android.wm.shell.Transition.ID, transitionId);
- outputStream.write(
- com.android.wm.shell.Transition.ABORT_TIME_NS, SystemClock.elapsedRealtimeNanos());
-
- outputStream.end(protoToken);
-
- mTraceBuffer.add(outputStream);
+ mTraceBuffer.add(proto);
}
/**
@@ -229,8 +237,9 @@
private void writeTraceToFileLocked(@Nullable PrintWriter pw, File file) {
Trace.beginSection("TransitionTracer#writeTraceToFileLocked");
try {
- ProtoOutputStream proto = new ProtoOutputStream();
- proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+ com.android.wm.shell.nano.WmShellTransitionTraceProto proto =
+ new com.android.wm.shell.nano.WmShellTransitionTraceProto();
+ proto.magicNumber = MAGIC_NUMBER_VALUE;
writeHandlerMappingToProto(proto);
int pid = android.os.Process.myPid();
LogAndPrintln.i(pw, "Writing file to " + file.getAbsolutePath()
@@ -242,19 +251,21 @@
Trace.endSection();
}
- private void writeHandlerMappingToProto(ProtoOutputStream outputStream) {
+ private void writeHandlerMappingToProto(
+ com.android.wm.shell.nano.WmShellTransitionTraceProto proto) {
+ ArrayList<com.android.wm.shell.nano.HandlerMapping> handlerMappings = new ArrayList<>();
for (Transitions.TransitionHandler handler : mHandlerUseCountInTrace.keySet()) {
final int count = mHandlerUseCountInTrace.get(handler);
if (count > 0) {
- final long protoToken = outputStream.start(
- com.android.wm.shell.WmShellTransitionTraceProto.HANDLER_MAPPINGS);
- outputStream.write(com.android.wm.shell.HandlerMapping.ID,
- mHandlerIds.get(handler));
- outputStream.write(com.android.wm.shell.HandlerMapping.NAME,
- handler.getClass().getName());
- outputStream.end(protoToken);
+ com.android.wm.shell.nano.HandlerMapping mapping =
+ new com.android.wm.shell.nano.HandlerMapping();
+ mapping.id = mHandlerIds.get(handler);
+ mapping.name = handler.getClass().getName();
+ handlerMappings.add(mapping);
}
}
+ proto.handlerMappings = handlerMappings.toArray(
+ new com.android.wm.shell.nano.HandlerMapping[0]);
}
private void handleOnEntryRemovedFromTrace(Object proto) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index f79f1f7..a73ab77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -533,8 +533,12 @@
final int layer;
// Put all the OPEN/SHOW on top
if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
- // Wallpaper is always at the bottom.
- layer = -zSplitLine;
+ // Wallpaper is always at the bottom, opening wallpaper on top of closing one.
+ if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
+ layer = -zSplitLine + numChanges - i;
+ } else {
+ layer = -zSplitLine - i;
+ }
} else if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
if (isOpening) {
// put on top
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 4ef3350..dffbedd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -111,12 +111,6 @@
private ValueAnimator mDragToDesktopValueAnimator;
private final Rect mDragToDesktopAnimationStartBounds = new Rect();
private boolean mDragToDesktopAnimationStarted;
- private float mCaptionDragStartX;
-
- // These values keep track of any transitions to freeform to stop relayout from running on
- // changing task so that shellTransitions has a chance to animate the transition
- private int mPauseRelayoutForTask = -1;
- private IBinder mTransitionPausingRelayout;
public DesktopModeWindowDecorViewModel(
Context context,
@@ -195,22 +189,25 @@
@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change) {
if (change.getMode() == WindowManager.TRANSIT_CHANGE
- && info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE) {
- mTransitionPausingRelayout = transition;
+ && (info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE)) {
+ mWindowDecorByTaskId.get(change.getTaskInfo().taskId)
+ .addTransitionPausingRelayout(transition);
}
}
@Override
public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
- if (merged.equals(mTransitionPausingRelayout)) {
- mTransitionPausingRelayout = playing;
+ for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
+ final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
+ decor.mergeTransitionPausingRelayout(merged, playing);
}
}
@Override
public void onTransitionFinished(@NonNull IBinder transition) {
- if (transition.equals(mTransitionPausingRelayout)) {
- mPauseRelayoutForTask = -1;
+ for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
+ final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
+ decor.removeTransitionPausingRelayout(transition);
}
}
@@ -225,12 +222,7 @@
incrementEventReceiverTasks(taskInfo.displayId);
}
- // TaskListener callbacks and shell transitions aren't synchronized, so starting a shell
- // transition can trigger an onTaskInfoChanged call that updates the task's SurfaceControl
- // and interferes with the transition animation that is playing at the same time.
- if (taskInfo.taskId != mPauseRelayoutForTask) {
- decoration.relayout(taskInfo);
- }
+ decoration.relayout(taskInfo);
}
@Override
@@ -333,6 +325,13 @@
decoration.closeHandleMenu();
} else if (id == R.id.collapse_menu_button) {
decoration.closeHandleMenu();
+ } else if (id == R.id.select_button) {
+ if (DesktopModeStatus.IS_DISPLAY_CHANGE_ENABLED) {
+ // TODO(b/278084491): dev option to enable display switching
+ // remove when select is implemented
+ mDesktopTasksController.ifPresent(c -> c.moveToNextDisplay(mTaskId));
+ decoration.closeHandleMenu();
+ }
}
}
@@ -525,7 +524,6 @@
DesktopModeWindowDecoration relevantDecor) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
- mCaptionDragStartX = ev.getX();
// Begin drag through status bar if applicable.
if (relevantDecor != null) {
mDragToDesktopAnimationStartBounds.set(
@@ -555,8 +553,7 @@
relevantDecor.mTaskInfo.displayId);
if (ev.getY() > 2 * statusBarHeight) {
if (DesktopModeStatus.isProto2Enabled()) {
- mPauseRelayoutForTask = relevantDecor.mTaskInfo.taskId;
- centerAndMoveToDesktopWithAnimation(relevantDecor, ev);
+ animateToDesktop(relevantDecor, ev);
} else if (DesktopModeStatus.isProto1Enabled()) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
}
@@ -581,8 +578,9 @@
}
if (mTransitionDragActive) {
mDesktopTasksController.ifPresent(
- c -> c.onDragPositioningMoveThroughStatusBar(relevantDecor.mTaskInfo,
- relevantDecor.mTaskSurface, ev.getY()));
+ c -> c.onDragPositioningMoveThroughStatusBar(
+ relevantDecor.mTaskInfo,
+ relevantDecor.mTaskSurface, ev.getY()));
final int statusBarHeight = getStatusBarHeight(
relevantDecor.mTaskInfo.displayId);
if (ev.getY() > statusBarHeight) {
@@ -632,6 +630,15 @@
}
/**
+ * Blocks relayout until transition is finished and transitions to Desktop
+ */
+ private void animateToDesktop(DesktopModeWindowDecoration relevantDecor,
+ MotionEvent ev) {
+ relevantDecor.incrementRelayoutBlock();
+ centerAndMoveToDesktopWithAnimation(relevantDecor, ev);
+ }
+
+ /**
* Animates a window to the center, grows to freeform size, and transitions to Desktop Mode.
* @param relevantDecor the window decor of the task to be animated
* @param ev the motion event that triggers the animation
@@ -658,10 +665,9 @@
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- mDesktopTasksController.ifPresent(
- c -> c.onDragPositioningEndThroughStatusBar(
- relevantDecor.mTaskInfo,
- calculateFreeformBounds(FINAL_FREEFORM_SCALE)));
+ mDesktopTasksController.ifPresent(c ->
+ c.onDragPositioningEndThroughStatusBar(relevantDecor.mTaskInfo,
+ calculateFreeformBounds(FINAL_FREEFORM_SCALE)));
}
});
animator.start();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index b1c3791..95ed42a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -30,6 +30,7 @@
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.os.Handler;
+import android.os.IBinder;
import android.util.Log;
import android.view.Choreographer;
import android.view.MotionEvent;
@@ -49,6 +50,9 @@
import com.android.wm.shell.windowdecor.viewholder.DesktopModeFocusedWindowDecorationViewHolder;
import com.android.wm.shell.windowdecor.viewholder.DesktopModeWindowDecorationViewHolder;
+import java.util.HashSet;
+import java.util.Set;
+
/**
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
* {@link DesktopModeWindowDecorViewModel}.
@@ -83,6 +87,9 @@
private TaskCornersListener mCornersListener;
+ private final Set<IBinder> mTransitionsPausingRelayout = new HashSet<>();
+ private int mRelayoutBlock;
+
DesktopModeWindowDecoration(
Context context,
DisplayController displayController,
@@ -134,6 +141,13 @@
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
+ // TaskListener callbacks and shell transitions aren't synchronized, so starting a shell
+ // transition can trigger an onTaskInfoChanged call that updates the task's SurfaceControl
+ // and interferes with the transition animation that is playing at the same time.
+ if (mRelayoutBlock > 0) {
+ return;
+ }
+
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
// Use |applyStartTransactionOnDraw| so that the transaction (that applies task crop) is
// synced with the buffer transaction (that draws the View). Both will be shown on screen
@@ -455,6 +469,40 @@
return cornersRegion;
}
+ /**
+ * If transition exists in mTransitionsPausingRelayout, remove the transition and decrement
+ * mRelayoutBlock
+ */
+ void removeTransitionPausingRelayout(IBinder transition) {
+ if (mTransitionsPausingRelayout.remove(transition)) {
+ mRelayoutBlock--;
+ }
+ }
+
+ /**
+ * Add transition to mTransitionsPausingRelayout
+ */
+ void addTransitionPausingRelayout(IBinder transition) {
+ mTransitionsPausingRelayout.add(transition);
+ }
+
+ /**
+ * If two transitions merge and the merged transition is in mTransitionsPausingRelayout,
+ * remove the merged transition from the set and add the transition it was merged into.
+ */
+ public void mergeTransitionPausingRelayout(IBinder merged, IBinder playing) {
+ if (mTransitionsPausingRelayout.remove(merged)) {
+ mTransitionsPausingRelayout.add(playing);
+ }
+ }
+
+ /**
+ * Increase mRelayoutBlock, stopping relayout if mRelayoutBlock is now greater than 0.
+ */
+ public void incrementRelayoutBlock() {
+ mRelayoutBlock++;
+ }
+
static class Factory {
DesktopModeWindowDecoration create(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
index ed3cca0..ac4a597 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -187,6 +187,8 @@
final View moreActionsPillView = mMoreActionsPill.mWindowViewHost.getView();
final Button closeBtn = moreActionsPillView.findViewById(R.id.close_button);
closeBtn.setOnClickListener(mOnClickListener);
+ final Button selectBtn = moreActionsPillView.findViewById(R.id.select_button);
+ selectBtn.setOnClickListener(mOnClickListener);
}
/**
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 ef8332f..1d416c6 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 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/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
index b6d92814..b5937ae 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -21,6 +21,9 @@
<option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
<!-- Ensure output directory is empty at the start -->
<option name="run-command" value="rm -rf /sdcard/flicker" />
+ <!-- Increase trace size: 20mb for WM and 80mb for SF -->
+ <option name="run-command" value="cmd window tracing size 20480" />
+ <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
new file mode 100644
index 0000000..e06e074
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.wm.shell.flicker
+
+import android.app.Instrumentation
+import android.tools.device.flicker.junit.FlickerBuilderProvider
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+
+abstract class BaseBenchmarkTest
+@JvmOverloads
+constructor(
+ protected open val flicker: FlickerTest,
+ protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+ protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
+) {
+ /** Specification of the test transition to execute */
+ abstract val transition: FlickerBuilder.() -> Unit
+
+ /**
+ * Entry point for the test runner. It will use this method to initialize and cache flicker
+ * executions
+ */
+ @FlickerBuilderProvider
+ fun buildFlicker(): FlickerBuilder {
+ return FlickerBuilder(instrumentation).apply {
+ setup { flicker.scenario.setIsTablet(tapl.isTablet) }
+ transition()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
index 86edc25..c98c5a0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
@@ -17,24 +17,10 @@
package com.android.wm.shell.flicker
import android.app.Instrumentation
-import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerBuilderProvider
-import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
-import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd
-import com.android.server.wm.flicker.statusBarLayerPositionAtStartAndEnd
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd
-import com.android.server.wm.flicker.taskBarWindowIsAlwaysVisible
-import org.junit.Assume
-import org.junit.Test
/**
* Base test class containing common assertions for [ComponentNameMatcher.NAV_BAR],
@@ -44,124 +30,7 @@
abstract class BaseTest
@JvmOverloads
constructor(
- protected val flicker: FlickerTest,
- protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
- protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
-) {
- /** Specification of the test transition to execute */
- abstract val transition: FlickerBuilder.() -> Unit
-
- /**
- * Entry point for the test runner. It will use this method to initialize and cache flicker
- * executions
- */
- @FlickerBuilderProvider
- fun buildFlicker(): FlickerBuilder {
- return FlickerBuilder(instrumentation).apply {
- setup { flicker.scenario.setIsTablet(tapl.isTablet) }
- transition()
- }
- }
-
- /** Checks that all parts of the screen are covered during the transition */
- @Presubmit @Test open fun entireScreenCovered() = flicker.entireScreenCovered()
-
- /**
- * Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition
- */
- @Presubmit
- @Test
- open fun navBarLayerIsVisibleAtStartAndEnd() {
- Assume.assumeFalse(flicker.scenario.isTablet)
- flicker.navBarLayerIsVisibleAtStartAndEnd()
- }
-
- /**
- * Checks the position of the [ComponentNameMatcher.NAV_BAR] at the start and end of the
- * transition
- */
- @Presubmit
- @Test
- open fun navBarLayerPositionAtStartAndEnd() {
- Assume.assumeFalse(flicker.scenario.isTablet)
- flicker.navBarLayerPositionAtStartAndEnd()
- }
-
- /**
- * Checks that the [ComponentNameMatcher.NAV_BAR] window is visible during the whole transition
- *
- * Note: Phones only
- */
- @Presubmit
- @Test
- open fun navBarWindowIsAlwaysVisible() {
- Assume.assumeFalse(flicker.scenario.isTablet)
- flicker.navBarWindowIsAlwaysVisible()
- }
-
- /**
- * Checks that the [ComponentNameMatcher.TASK_BAR] layer is visible during the whole transition
- */
- @Presubmit
- @Test
- open fun taskBarLayerIsVisibleAtStartAndEnd() {
- Assume.assumeTrue(flicker.scenario.isTablet)
- flicker.taskBarLayerIsVisibleAtStartAndEnd()
- }
-
- /**
- * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole transition
- *
- * Note: Large screen only
- */
- @Presubmit
- @Test
- open fun taskBarWindowIsAlwaysVisible() {
- Assume.assumeTrue(flicker.scenario.isTablet)
- flicker.taskBarWindowIsAlwaysVisible()
- }
-
- /**
- * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible during the whole
- * transition
- */
- @Presubmit
- @Test
- open fun statusBarLayerIsVisibleAtStartAndEnd() = flicker.statusBarLayerIsVisibleAtStartAndEnd()
-
- /**
- * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
- * transition
- */
- @Presubmit
- @Test
- open fun statusBarLayerPositionAtStartAndEnd() = flicker.statusBarLayerPositionAtStartAndEnd()
-
- /**
- * Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole
- * transition
- */
- @Presubmit
- @Test
- open fun statusBarWindowIsAlwaysVisible() = flicker.statusBarWindowIsAlwaysVisible()
-
- /**
- * Checks that all layers that are visible on the trace, are visible for at least 2 consecutive
- * entries.
- */
- @Presubmit
- @Test
- open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
- }
-
- /**
- * Checks that all windows that are visible on the trace, are visible for at least 2 consecutive
- * entries.
- */
- @Presubmit
- @Test
- open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
- flicker.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
- }
-}
+ override val flicker: FlickerTest,
+ instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+ tapl: LauncherInstrumentation = LauncherInstrumentation()
+) : BaseBenchmarkTest(flicker, instrumentation, tapl), ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt
new file mode 100644
index 0000000..02d9a056
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt
@@ -0,0 +1,136 @@
+/*
+ * 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.wm.shell.flicker
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.legacy.FlickerTest
+import com.android.server.wm.flicker.entireScreenCovered
+import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
+import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd
+import com.android.server.wm.flicker.statusBarLayerPositionAtStartAndEnd
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd
+import com.android.server.wm.flicker.taskBarWindowIsAlwaysVisible
+import org.junit.Assume
+import org.junit.Test
+
+interface ICommonAssertions {
+ val flicker: FlickerTest
+
+ /** Checks that all parts of the screen are covered during the transition */
+ @Presubmit @Test fun entireScreenCovered() = flicker.entireScreenCovered()
+
+ /**
+ * Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition
+ */
+ @Presubmit
+ @Test
+ fun navBarLayerIsVisibleAtStartAndEnd() {
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ /**
+ * Checks the position of the [ComponentNameMatcher.NAV_BAR] at the start and end of the
+ * transition
+ */
+ @Presubmit
+ @Test
+ fun navBarLayerPositionAtStartAndEnd() {
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarLayerPositionAtStartAndEnd()
+ }
+
+ /**
+ * Checks that the [ComponentNameMatcher.NAV_BAR] window is visible during the whole transition
+ *
+ * Note: Phones only
+ */
+ @Presubmit
+ @Test
+ fun navBarWindowIsAlwaysVisible() {
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarWindowIsAlwaysVisible()
+ }
+
+ /**
+ * Checks that the [ComponentNameMatcher.TASK_BAR] layer is visible during the whole transition
+ */
+ @Presubmit
+ @Test
+ fun taskBarLayerIsVisibleAtStartAndEnd() {
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.taskBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ /**
+ * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole transition
+ *
+ * Note: Large screen only
+ */
+ @Presubmit
+ @Test
+ fun taskBarWindowIsAlwaysVisible() {
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.taskBarWindowIsAlwaysVisible()
+ }
+
+ /**
+ * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible during the whole
+ * transition
+ */
+ @Presubmit
+ @Test
+ fun statusBarLayerIsVisibleAtStartAndEnd() = flicker.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /**
+ * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
+ * transition
+ */
+ @Presubmit
+ @Test
+ fun statusBarLayerPositionAtStartAndEnd() = flicker.statusBarLayerPositionAtStartAndEnd()
+
+ /**
+ * Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole
+ * transition
+ */
+ @Presubmit @Test fun statusBarWindowIsAlwaysVisible() = flicker.statusBarWindowIsAlwaysVisible()
+
+ /**
+ * Checks that all layers that are visible on the trace, are visible for at least 2 consecutive
+ * entries.
+ */
+ @Presubmit
+ @Test
+ fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
+ }
+
+ /**
+ * Checks that all windows that are visible on the trace, are visible for at least 2 consecutive
+ * entries.
+ */
+ @Presubmit
+ @Test
+ fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ flicker.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
index 11c5951..6178156 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
@@ -18,15 +18,18 @@
import android.content.Context
import android.system.helpers.CommandsHelper
+import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.LetterboxAppHelper
import android.tools.device.flicker.legacy.FlickerTestFactory
import android.tools.device.flicker.legacy.IFlickerTestData
-import com.android.server.wm.flicker.helpers.LetterboxAppHelper
-import com.android.server.wm.flicker.helpers.setRotation
import com.android.wm.shell.flicker.BaseTest
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.layerKeepVisible
+import org.junit.After
import org.junit.Assume
import org.junit.Before
import org.junit.runners.Parameterized
@@ -35,7 +38,7 @@
protected val context: Context = instrumentation.context
protected val letterboxApp = LetterboxAppHelper(instrumentation)
lateinit var cmdHelper: CommandsHelper
- lateinit var letterboxStyle: HashMap<String, String>
+ private lateinit var letterboxStyle: HashMap<String, String>
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
@@ -45,12 +48,22 @@
letterboxApp.launchViaIntent(wmHelper)
setEndRotation()
}
+ teardown {
+ letterboxApp.exit(wmHelper)
+ }
}
@Before
fun before() {
cmdHelper = CommandsHelper.getInstance(instrumentation)
Assume.assumeTrue(tapl.isTablet && isIgnoreOrientationRequest())
+ letterboxStyle = mapLetterboxStyle()
+ setLetterboxEducationEnabled(false)
+ }
+
+ @After
+ fun after() {
+ resetLetterboxEducationEnabled()
}
private fun mapLetterboxStyle(): HashMap<String, String> {
@@ -67,6 +80,22 @@
return map
}
+ private fun getLetterboxStyle(): HashMap<String, String> {
+ if (!::letterboxStyle.isInitialized) {
+ letterboxStyle = mapLetterboxStyle()
+ }
+ return letterboxStyle
+ }
+
+ private fun resetLetterboxEducationEnabled() {
+ val enabled = getLetterboxStyle().getValue("Is education enabled")
+ cmdHelper.executeShellCommand("wm set-letterbox-style --isEducationEnabled $enabled")
+ }
+
+ private fun setLetterboxEducationEnabled(enabled: Boolean) {
+ cmdHelper.executeShellCommand("wm set-letterbox-style --isEducationEnabled $enabled")
+ }
+
private fun isIgnoreOrientationRequest(): Boolean {
val res = cmdHelper.executeShellCommand("wm get-ignore-orientation-request")
return res != null && res.contains("true")
@@ -89,10 +118,7 @@
/** Only run on tests with config_letterboxActivityCornersRadius != 0 in devices */
private fun assumeLetterboxRoundedCornersEnabled() {
- if (!::letterboxStyle.isInitialized) {
- letterboxStyle = mapLetterboxStyle()
- }
- Assume.assumeTrue(letterboxStyle.getValue("Corner radius") != "0")
+ Assume.assumeTrue(getLetterboxStyle().getValue("Corner radius") != "0")
}
fun assertLetterboxAppVisibleAtStartAndEnd() {
@@ -100,12 +126,20 @@
flicker.appWindowIsVisibleAtEnd(letterboxApp)
}
+ fun assertAppLetterboxedAtEnd() =
+ flicker.assertLayersEnd { isVisible(ComponentNameMatcher.LETTERBOX) }
+
+ fun assertAppLetterboxedAtStart() =
+ flicker.assertLayersStart { isVisible(ComponentNameMatcher.LETTERBOX) }
+
+ fun assertLetterboxAppLayerKeepVisible() = flicker.layerKeepVisible(letterboxApp)
+
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestFactory.rotationTests] for configuring screen orientation and navigation
- * modes.
+ * See [FlickerTestFactory.rotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
index f212a4e..c2141a3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
@@ -70,6 +70,10 @@
@Test
fun letterboxedAppHasRoundedCorners() = assertLetterboxAppAtEndHasRoundedCorners()
+ @Postsubmit
+ @Test
+ fun appIsLetterboxedAtEnd() = assertAppLetterboxedAtEnd()
+
/**
* Checks that the [ComponentNameMatcher.ROTATION] layer appears during the transition, doesn't
* flicker, and disappears before the transition is complete
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
index 8e75439..b0e1a42 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
@@ -53,25 +53,32 @@
get() = {
super.transition(this)
transitions { letterboxApp.clickRestart(wmHelper) }
- teardown { letterboxApp.exit(wmHelper) }
}
@Postsubmit @Test fun appVisibleAtStartAndEnd() = assertLetterboxAppVisibleAtStartAndEnd()
@Postsubmit
@Test
- fun appLayerVisibilityChanges() {
- flicker.assertLayers {
- this.isVisible(letterboxApp)
+ fun appWindowVisibilityChanges() {
+ flicker.assertWm {
+ this.isAppWindowVisible(letterboxApp)
.then()
- .isInvisible(letterboxApp)
+ .isAppWindowInvisible(letterboxApp) // animatingExit true
.then()
- .isVisible(letterboxApp)
+ .isAppWindowVisible(letterboxApp) // Activity finish relaunching
}
}
@Postsubmit
@Test
+ fun appLayerKeepVisible() = assertLetterboxAppLayerKeepVisible()
+
+ @Postsubmit
+ @Test
+ fun appIsLetterboxedAtStart() = assertAppLetterboxedAtStart()
+
+ @Postsubmit
+ @Test
fun letterboxedAppHasRoundedCorners() = assertLetterboxAppAtStartHasRoundedCorners()
/** Checks that the visible region of [letterboxApp] is still within display bounds */
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index 93ee699..a4ac261 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -54,7 +54,6 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 238367575)
class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(flicker) {
/** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
@@ -71,7 +70,7 @@
transitions { tapl.goHome() }
}
- @FlakyTest(bugId = 256863309)
+ @Presubmit
@Test
override fun pipLayerReduces() {
flicker.assertLayers {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index d53eac0..b7e73ad 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -26,6 +26,7 @@
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
@@ -34,6 +35,7 @@
import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible
import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
+import com.android.wm.shell.flicker.splitscreen.benchmark.CopyContentInSplitBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -49,29 +51,19 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CopyContentInSplit(flicker: FlickerTest) : SplitScreenBase(flicker) {
- private val textEditApp = SplitScreenUtils.getIme(instrumentation)
- private val MagnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#")
- private val PopupWindowLayer = ComponentNameMatcher("", "PopupWindow:")
-
+class CopyContentInSplit(override val flicker: FlickerTest) :
+ CopyContentInSplitBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
get() = {
- super.transition(this)
- setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp) }
- transitions {
- SplitScreenUtils.copyContentInSplit(
- instrumentation,
- device,
- primaryApp,
- textEditApp
- )
- }
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
}
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() {
+ override fun cujCompleted() {
flicker.appWindowIsVisibleAtStart(primaryApp)
flicker.appWindowIsVisibleAtStart(textEditApp)
flicker.splitScreenDividerIsVisibleAtStart()
@@ -128,8 +120,8 @@
ComponentNameMatcher.SNAPSHOT,
ComponentNameMatcher.IME_SNAPSHOT,
EdgeExtensionComponentMatcher(),
- MagnifierLayer,
- PopupWindowLayer
+ magnifierLayer,
+ popupWindowLayer
)
)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index 1b55f39..3fd6d17 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -17,22 +17,21 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
import android.tools.device.helpers.WindowUtils
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
import com.android.wm.shell.flicker.appWindowBecomesInvisible
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.layerBecomesInvisible
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
-import com.android.wm.shell.flicker.splitScreenDismissed
import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
+import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByDividerBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -48,37 +47,16 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DismissSplitScreenByDivider(flicker: FlickerTest) : SplitScreenBase(flicker) {
+class DismissSplitScreenByDivider(override val flicker: FlickerTest) :
+ DismissSplitScreenByDividerBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
get() = {
- super.transition(this)
- setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
- transitions {
- if (tapl.isTablet) {
- SplitScreenUtils.dragDividerToDismissSplit(
- device,
- wmHelper,
- dragToRight = false,
- dragToBottom = true
- )
- } else {
- SplitScreenUtils.dragDividerToDismissSplit(
- device,
- wmHelper,
- dragToRight = true,
- dragToBottom = true
- )
- }
- wmHelper.StateSyncBuilder().withFullScreenApp(secondaryApp).waitForAndVerify()
- }
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
}
- @IwTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
-
@Presubmit
@Test
fun splitScreenDividerBecomesInvisible() = flicker.splitScreenDividerBecomesInvisible()
@@ -176,12 +154,4 @@
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): List<FlickerTest> {
- return FlickerTestFactory.nonRotationTests()
- }
- }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index 2e81b30..e05b221 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -17,18 +17,18 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
import com.android.wm.shell.flicker.appWindowBecomesInvisible
import com.android.wm.shell.flicker.layerBecomesInvisible
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
-import com.android.wm.shell.flicker.splitScreenDismissed
import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
+import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByGoHomeBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -44,21 +44,14 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DismissSplitScreenByGoHome(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
+class DismissSplitScreenByGoHome(override val flicker: FlickerTest) :
+ DismissSplitScreenByGoHomeBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
get() = {
- super.transition(this)
- setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
- transitions {
- tapl.goHome()
- wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
- }
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
}
- @IwTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index 5180791..0b0a3da 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -24,6 +24,7 @@
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
@@ -32,8 +33,7 @@
import com.android.wm.shell.flicker.splitAppLayerBoundsChanges
import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
-import org.junit.Assume
-import org.junit.Before
+import com.android.wm.shell.flicker.splitscreen.benchmark.DragDividerToResizeBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -49,24 +49,19 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DragDividerToResize(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
+class DragDividerToResize(override val flicker: FlickerTest) :
+ DragDividerToResizeBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
get() = {
- super.transition(this)
- setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
- transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) }
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
}
- @Before
- fun before() {
- Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart)
- }
-
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() {
+ override fun cujCompleted() {
flicker.appWindowIsVisibleAtStart(primaryApp)
flicker.appWindowIsVisibleAtStart(secondaryApp)
flicker.splitScreenDividerIsVisibleAtStart()
@@ -74,9 +69,6 @@
flicker.appWindowIsVisibleAtEnd(primaryApp)
flicker.appWindowIsVisibleAtEnd(secondaryApp)
flicker.splitScreenDividerIsVisibleAtEnd()
-
- // TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is
- // robust enough to get the correct end state.
}
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index 69da1e2..e558686 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
@@ -26,6 +25,7 @@
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
@@ -34,9 +34,7 @@
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
-import org.junit.Assume
-import org.junit.Before
+import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromAllAppsBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -53,40 +51,15 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromAllApps(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
- @Before
- fun before() {
- Assume.assumeTrue(tapl.isTablet)
- }
-
+class EnterSplitScreenByDragFromAllApps(override val flicker: FlickerTest) :
+ EnterSplitScreenByDragFromAllAppsBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
get() = {
- super.transition(this)
- setup {
- tapl.goHome()
- primaryApp.launchViaIntent(wmHelper)
- }
- transitions {
- tapl.launchedAppState.taskbar
- .openAllApps()
- .getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
- SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
- }
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
}
- @IwTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() =
- flicker.splitScreenEntered(
- primaryApp,
- secondaryApp,
- fromOtherApp = false,
- appExistAtStart = false
- )
-
@FlakyTest(bugId = 245472831)
@Test
fun splitScreenDividerBecomesVisible() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index 1773846..ab8ecc5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
@@ -26,6 +25,7 @@
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.layerBecomesVisible
@@ -33,9 +33,7 @@
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
-import org.junit.Assume
-import org.junit.Before
+import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromNotificationBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -52,39 +50,16 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromNotification(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
- private val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
-
- @Before
- fun before() {
- Assume.assumeTrue(tapl.isTablet)
- }
-
+class EnterSplitScreenByDragFromNotification(override val flicker: FlickerTest) :
+ EnterSplitScreenByDragFromNotificationBenchmark(flicker), ICommonAssertions {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = {
- super.transition(this)
- setup {
- // Send a notification
- sendNotificationApp.launchViaIntent(wmHelper)
- sendNotificationApp.postNotification(wmHelper)
- tapl.goHome()
- primaryApp.launchViaIntent(wmHelper)
- }
- transitions {
- SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper)
- SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
- }
- teardown { sendNotificationApp.exit(wmHelper) }
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
}
- @IwTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() =
- flicker.splitScreenEntered(primaryApp, sendNotificationApp, fromOtherApp = false)
-
@FlakyTest(bugId = 245472831)
@Test
fun splitScreenDividerBecomesVisible() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
index c1977e9..516ca97 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -25,15 +24,14 @@
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
-import org.junit.Assume
-import org.junit.Before
+import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromShortcutBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -49,42 +47,16 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromShortcut(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
- @Before
- fun before() {
- Assume.assumeTrue(tapl.isTablet)
- }
+class EnterSplitScreenByDragFromShortcut(override val flicker: FlickerTest) :
+ EnterSplitScreenByDragFromShortcutBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
get() = {
- super.transition(this)
- setup {
- tapl.goHome()
- SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
- primaryApp.launchViaIntent(wmHelper)
- }
- transitions {
- tapl.launchedAppState.taskbar
- .getAppIcon(secondaryApp.appName)
- .openDeepShortcutMenu()
- .getMenuItem("Split Screen Secondary Activity")
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
- SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
- }
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
}
- @IwTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() =
- flicker.splitScreenEntered(
- primaryApp,
- secondaryApp,
- fromOtherApp = false,
- appExistAtStart = false
- )
-
@Presubmit
@Test
fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index 3bea66e..4af7e24 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
@@ -26,6 +25,7 @@
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
@@ -34,9 +34,7 @@
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
-import org.junit.Assume
-import org.junit.Before
+import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromTaskbarBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -53,41 +51,16 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromTaskbar(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
- @Before
- fun before() {
- Assume.assumeTrue(tapl.isTablet)
- }
-
+class EnterSplitScreenByDragFromTaskbar(override val flicker: FlickerTest) :
+ EnterSplitScreenByDragFromTaskbarBenchmark(flicker), ICommonAssertions {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = {
- super.transition(this)
- setup {
- tapl.goHome()
- SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
- primaryApp.launchViaIntent(wmHelper)
- }
- transitions {
- tapl.launchedAppState.taskbar
- .getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
- SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
- }
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
}
- @IwTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() =
- flicker.splitScreenEntered(
- primaryApp,
- secondaryApp,
- fromOtherApp = false,
- appExistAtStart = false
- )
-
@FlakyTest(bugId = 245472831)
@Test
fun splitScreenDividerBecomesVisible() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
index c453877..faad9e8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -17,20 +17,20 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenFromOverviewBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -46,31 +46,15 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenFromOverview(flicker: FlickerTest) : SplitScreenBase(flicker) {
+class EnterSplitScreenFromOverview(override val flicker: FlickerTest) :
+ EnterSplitScreenFromOverviewBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
get() = {
- super.transition(this)
- setup {
- primaryApp.launchViaIntent(wmHelper)
- secondaryApp.launchViaIntent(wmHelper)
- tapl.goHome()
- wmHelper
- .StateSyncBuilder()
- .withAppTransitionIdle()
- .withHomeActivityVisible()
- .waitForAndVerify()
- }
- transitions {
- SplitScreenUtils.splitFromOverview(tapl, device)
- SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
- }
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
}
- @IwTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
@Presubmit
@Test
fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
index 7abdc06..195b73a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
@@ -20,25 +20,33 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
import com.android.server.wm.flicker.helpers.setRotation
-import com.android.wm.shell.flicker.BaseTest
+import com.android.wm.shell.flicker.BaseBenchmarkTest
-abstract class SplitScreenBase(flicker: FlickerTest) : BaseTest(flicker) {
+abstract class SplitScreenBase(flicker: FlickerTest) : BaseBenchmarkTest(flicker) {
protected val context: Context = instrumentation.context
protected val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
protected val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
- /** {@inheritDoc} */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- setup {
- tapl.setEnableRotation(true)
- setRotation(flicker.scenario.startRotation)
- tapl.setExpectedRotation(flicker.scenario.startRotation.value)
- tapl.workspace.switchToOverview().dismissAllTasks()
- }
- teardown {
- primaryApp.exit(wmHelper)
- secondaryApp.exit(wmHelper)
- }
+ protected open val defaultSetup: FlickerBuilder.() -> Unit = {
+ setup {
+ tapl.setEnableRotation(true)
+ setRotation(flicker.scenario.startRotation)
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
+ tapl.workspace.switchToOverview().dismissAllTasks()
}
+ }
+
+ protected open val defaultTeardown: FlickerBuilder.() -> Unit = {
+ teardown {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+ }
+
+ protected open val withoutTracing: FlickerBuilder.() -> Unit = {
+ withoutLayerTracing()
+ withoutWindowManagerTracing()
+ withoutTransitionTracing()
+ withoutTransactionsTracing()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index fbb7c71..8cf871f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -20,14 +20,12 @@
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
-import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.helpers.WindowUtils
-import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
@@ -36,6 +34,7 @@
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
+import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchAppByDoubleTapDividerBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -51,98 +50,19 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchAppByDoubleTapDivider(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
+class SwitchAppByDoubleTapDivider(override val flicker: FlickerTest) :
+ SwitchAppByDoubleTapDividerBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
get() = {
- super.transition(this)
- setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
- transitions {
- SplitScreenUtils.doubleTapDividerToSwitch(device)
- wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
-
- waitForLayersToSwitch(wmHelper)
- waitForWindowsToSwitch(wmHelper)
- }
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
}
- private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) {
- wmHelper
- .StateSyncBuilder()
- .add("appWindowsSwitched") {
- val primaryAppWindow =
- it.wmState.visibleWindows.firstOrNull { window ->
- primaryApp.windowMatchesAnyOf(window)
- }
- ?: return@add false
- val secondaryAppWindow =
- it.wmState.visibleWindows.firstOrNull { window ->
- secondaryApp.windowMatchesAnyOf(window)
- }
- ?: return@add false
-
- if (isLandscape(flicker.scenario.endRotation)) {
- return@add if (flicker.scenario.isTablet) {
- secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
- } else {
- primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
- }
- } else {
- return@add if (flicker.scenario.isTablet) {
- primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
- } else {
- primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
- }
- }
- }
- .waitForAndVerify()
- }
-
- private fun waitForLayersToSwitch(wmHelper: WindowManagerStateHelper) {
- wmHelper
- .StateSyncBuilder()
- .add("appLayersSwitched") {
- val primaryAppLayer =
- it.layerState.visibleLayers.firstOrNull { window ->
- primaryApp.layerMatchesAnyOf(window)
- }
- ?: return@add false
- val secondaryAppLayer =
- it.layerState.visibleLayers.firstOrNull { window ->
- secondaryApp.layerMatchesAnyOf(window)
- }
- ?: return@add false
-
- val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds ?: return@add false
- val secondaryVisibleRegion =
- secondaryAppLayer.visibleRegion?.bounds ?: return@add false
-
- if (isLandscape(flicker.scenario.endRotation)) {
- return@add if (flicker.scenario.isTablet) {
- secondaryVisibleRegion.right <= primaryVisibleRegion.left
- } else {
- primaryVisibleRegion.right <= secondaryVisibleRegion.left
- }
- } else {
- return@add if (flicker.scenario.isTablet) {
- primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
- } else {
- primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
- }
- }
- }
- .waitForAndVerify()
- }
-
- private fun isLandscape(rotation: Rotation): Boolean {
- val displayBounds = WindowUtils.getDisplayBounds(rotation)
- return displayBounds.width > displayBounds.height
- }
-
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() {
+ override fun cujCompleted() {
flicker.appWindowIsVisibleAtStart(primaryApp)
flicker.appWindowIsVisibleAtStart(secondaryApp)
flicker.splitScreenDividerIsVisibleAtStart()
@@ -150,9 +70,6 @@
flicker.appWindowIsVisibleAtEnd(primaryApp)
flicker.appWindowIsVisibleAtEnd(secondaryApp)
flicker.splitScreenDividerIsVisibleAtEnd()
-
- // TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is
- // robust enough to get the correct end state.
}
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index d675bfb..078d95d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -25,11 +24,12 @@
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromAnotherAppBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -45,29 +45,15 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromAnotherApp(flicker: FlickerTest) : SplitScreenBase(flicker) {
- val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
-
+class SwitchBackToSplitFromAnotherApp(override val flicker: FlickerTest) :
+ SwitchBackToSplitFromAnotherAppBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
get() = {
- super.transition(this)
- setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
-
- thirdApp.launchViaIntent(wmHelper)
- wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify()
- }
- transitions {
- tapl.launchedAppState.quickSwitchToPreviousApp()
- SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
- }
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
}
- @IwTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
@Presubmit
@Test
fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index 9f4cb8c..7c84243 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -25,11 +24,12 @@
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromHomeBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -45,28 +45,15 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromHome(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
+class SwitchBackToSplitFromHome(override val flicker: FlickerTest) :
+ SwitchBackToSplitFromHomeBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
get() = {
- super.transition(this)
- setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
-
- tapl.goHome()
- wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
- }
- transitions {
- tapl.workspace.quickSwitchToPreviousApp()
- SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
- }
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
}
- @IwTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
@Presubmit
@Test
fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index a33d8ca..7c46d3e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.splitscreen
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -25,11 +24,12 @@
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
-import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromRecentBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -45,28 +45,15 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromRecent(flicker: FlickerTest) : SplitScreenBase(flicker) {
-
+class SwitchBackToSplitFromRecent(override val flicker: FlickerTest) :
+ SwitchBackToSplitFromRecentBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
get() = {
- super.transition(this)
- setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
-
- tapl.goHome()
- wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
- }
- transitions {
- tapl.workspace.switchToOverview().currentTask.open()
- SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
- }
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
}
- @IwTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
@Presubmit
@Test
fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
index 4c96b3a..3b2da8d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
@@ -24,6 +24,7 @@
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.ICommonAssertions
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowBecomesInvisible
import com.android.wm.shell.flicker.appWindowBecomesVisible
@@ -36,6 +37,7 @@
import com.android.wm.shell.flicker.splitAppLayerBoundsSnapToDivider
import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
+import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBetweenSplitPairsBenchmark
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -51,32 +53,19 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBetweenSplitPairs(flicker: FlickerTest) : SplitScreenBase(flicker) {
- private val thirdApp = SplitScreenUtils.getIme(instrumentation)
- private val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
-
+class SwitchBetweenSplitPairs(override val flicker: FlickerTest) :
+ SwitchBetweenSplitPairsBenchmark(flicker), ICommonAssertions {
override val transition: FlickerBuilder.() -> Unit
get() = {
- super.transition(this)
- setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp)
- SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp)
- }
- transitions {
- tapl.launchedAppState.quickSwitchToPreviousApp()
- SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
- }
- teardown {
- thirdApp.exit(wmHelper)
- fourthApp.exit(wmHelper)
- }
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
}
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() {
+ override fun cujCompleted() {
flicker.appWindowIsVisibleAtStart(thirdApp)
flicker.appWindowIsVisibleAtStart(fourthApp)
flicker.splitScreenDividerIsVisibleAtStart()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
new file mode 100644
index 0000000..a189a3f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class CopyContentInSplitBenchmark(override val flicker: FlickerTest) :
+ SplitScreenBase(flicker) {
+ protected val textEditApp = SplitScreenUtils.getIme(instrumentation)
+ protected val magnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#")
+ protected val popupWindowLayer = ComponentNameMatcher("", "PopupWindow:")
+ protected val thisTransition: FlickerBuilder.() -> Unit
+ get() = {
+ setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp) }
+ transitions {
+ SplitScreenUtils.copyContentInSplit(
+ instrumentation,
+ device,
+ primaryApp,
+ textEditApp
+ )
+ }
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ withoutTracing(this)
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
+ }
+
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ open fun cujCompleted() {
+ // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
new file mode 100644
index 0000000..55ab7b3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenDismissed
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class DismissSplitScreenByDividerBenchmark(flicker: FlickerTest) : SplitScreenBase(flicker) {
+ protected val thisTransition: FlickerBuilder.() -> Unit
+ get() = {
+ setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+ transitions {
+ if (tapl.isTablet) {
+ SplitScreenUtils.dragDividerToDismissSplit(
+ device,
+ wmHelper,
+ dragToRight = false,
+ dragToBottom = true
+ )
+ } else {
+ SplitScreenUtils.dragDividerToDismissSplit(
+ device,
+ wmHelper,
+ dragToRight = true,
+ dragToBottom = true
+ )
+ }
+ wmHelper.StateSyncBuilder().withFullScreenApp(secondaryApp).waitForAndVerify()
+ }
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ withoutTracing(this)
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
+ }
+
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
new file mode 100644
index 0000000..c4cfd1a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenDismissed
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class DismissSplitScreenByGoHomeBenchmark(override val flicker: FlickerTest) :
+ SplitScreenBase(flicker) {
+ protected val thisTransition: FlickerBuilder.() -> Unit
+ get() = {
+ setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+ transitions {
+ tapl.goHome()
+ wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+ }
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ withoutTracing(this)
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
+ }
+
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
new file mode 100644
index 0000000..146287c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class DragDividerToResizeBenchmark(override val flicker: FlickerTest) :
+ SplitScreenBase(flicker) {
+ protected val thisTransition: FlickerBuilder.() -> Unit
+ get() = {
+ setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+ transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) }
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ withoutTracing(this)
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
+ }
+
+ @Before
+ fun before() {
+ Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart)
+ }
+
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ open fun cujCompleted() {
+ // TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is
+ // robust enough to get the correct end state.
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
new file mode 100644
index 0000000..cc71502
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: FlickerTest) :
+ SplitScreenBase(flicker) {
+
+ protected val thisTransition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ tapl.goHome()
+ primaryApp.launchViaIntent(wmHelper)
+ }
+ transitions {
+ tapl.launchedAppState.taskbar
+ .openAllApps()
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ withoutTracing(this)
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
+ }
+
+ @Before
+ fun before() {
+ Assume.assumeTrue(tapl.isTablet)
+ }
+
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ fun cujCompleted() =
+ flicker.splitScreenEntered(
+ primaryApp,
+ secondaryApp,
+ fromOtherApp = false,
+ appExistAtStart = false
+ )
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
new file mode 100644
index 0000000..de78f09
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class EnterSplitScreenByDragFromNotificationBenchmark(override val flicker: FlickerTest) :
+ SplitScreenBase(flicker) {
+ protected val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
+ protected val thisTransition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ // Send a notification
+ sendNotificationApp.launchViaIntent(wmHelper)
+ sendNotificationApp.postNotification(wmHelper)
+ tapl.goHome()
+ primaryApp.launchViaIntent(wmHelper)
+ }
+ transitions {
+ SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
+ }
+ teardown { sendNotificationApp.exit(wmHelper) }
+ }
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ withoutTracing(this)
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
+ }
+
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ fun cujCompleted() =
+ flicker.splitScreenEntered(primaryApp, sendNotificationApp, fromOtherApp = false)
+
+ @Before
+ fun before() {
+ Assume.assumeTrue(tapl.isTablet)
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
new file mode 100644
index 0000000..a29eb40
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class EnterSplitScreenByDragFromShortcutBenchmark(flicker: FlickerTest) :
+ SplitScreenBase(flicker) {
+ @Before
+ fun before() {
+ Assume.assumeTrue(tapl.isTablet)
+ }
+
+ protected val thisTransition: FlickerBuilder.() -> Unit = {
+ setup {
+ tapl.goHome()
+ SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
+ primaryApp.launchViaIntent(wmHelper)
+ }
+ transitions {
+ tapl.launchedAppState.taskbar
+ .getAppIcon(secondaryApp.appName)
+ .openDeepShortcutMenu()
+ .getMenuItem("Split Screen Secondary Activity")
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ withoutTracing(this)
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
+ }
+
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ fun cujCompleted() =
+ flicker.splitScreenEntered(
+ primaryApp,
+ secondaryApp,
+ fromOtherApp = false,
+ appExistAtStart = false
+ )
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
new file mode 100644
index 0000000..b2395ca
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: FlickerTest) :
+ SplitScreenBase(flicker) {
+ protected val thisTransition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ tapl.goHome()
+ SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
+ primaryApp.launchViaIntent(wmHelper)
+ }
+ transitions {
+ tapl.launchedAppState.taskbar
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ withoutTracing(this)
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
+ }
+
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ fun cujCompleted() =
+ flicker.splitScreenEntered(
+ primaryApp,
+ secondaryApp,
+ fromOtherApp = false,
+ appExistAtStart = false
+ )
+
+ @Before
+ fun before() {
+ Assume.assumeTrue(tapl.isTablet)
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
new file mode 100644
index 0000000..e1d85d0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class EnterSplitScreenFromOverviewBenchmark(override val flicker: FlickerTest) :
+ SplitScreenBase(flicker) {
+ protected val thisTransition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ primaryApp.launchViaIntent(wmHelper)
+ secondaryApp.launchViaIntent(wmHelper)
+ tapl.goHome()
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
+ }
+ transitions {
+ SplitScreenUtils.splitFromOverview(tapl, device)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ withoutTracing(this)
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
+ }
+
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
new file mode 100644
index 0000000..ba8c460
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
@@ -0,0 +1,155 @@
+/*
+ * 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.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.helpers.WindowUtils
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: FlickerTest) :
+ SplitScreenBase(flicker) {
+ protected val thisTransition: FlickerBuilder.() -> Unit
+ get() = {
+ setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+ transitions {
+ SplitScreenUtils.doubleTapDividerToSwitch(device)
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+
+ waitForLayersToSwitch(wmHelper)
+ waitForWindowsToSwitch(wmHelper)
+ }
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ withoutTracing(this)
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
+ }
+
+ private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) {
+ wmHelper
+ .StateSyncBuilder()
+ .add("appWindowsSwitched") {
+ val primaryAppWindow =
+ it.wmState.visibleWindows.firstOrNull { window ->
+ primaryApp.windowMatchesAnyOf(window)
+ }
+ ?: return@add false
+ val secondaryAppWindow =
+ it.wmState.visibleWindows.firstOrNull { window ->
+ secondaryApp.windowMatchesAnyOf(window)
+ }
+ ?: return@add false
+
+ if (isLandscape(flicker.scenario.endRotation)) {
+ return@add if (flicker.scenario.isTablet) {
+ secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
+ } else {
+ primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
+ }
+ } else {
+ return@add if (flicker.scenario.isTablet) {
+ primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+ } else {
+ primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+ }
+ }
+ }
+ .waitForAndVerify()
+ }
+
+ private fun waitForLayersToSwitch(wmHelper: WindowManagerStateHelper) {
+ wmHelper
+ .StateSyncBuilder()
+ .add("appLayersSwitched") {
+ val primaryAppLayer =
+ it.layerState.visibleLayers.firstOrNull { window ->
+ primaryApp.layerMatchesAnyOf(window)
+ }
+ ?: return@add false
+ val secondaryAppLayer =
+ it.layerState.visibleLayers.firstOrNull { window ->
+ secondaryApp.layerMatchesAnyOf(window)
+ }
+ ?: return@add false
+
+ val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds ?: return@add false
+ val secondaryVisibleRegion =
+ secondaryAppLayer.visibleRegion?.bounds ?: return@add false
+
+ if (isLandscape(flicker.scenario.endRotation)) {
+ return@add if (flicker.scenario.isTablet) {
+ secondaryVisibleRegion.right <= primaryVisibleRegion.left
+ } else {
+ primaryVisibleRegion.right <= secondaryVisibleRegion.left
+ }
+ } else {
+ return@add if (flicker.scenario.isTablet) {
+ primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+ } else {
+ primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+ }
+ }
+ }
+ .waitForAndVerify()
+ }
+
+ private fun isLandscape(rotation: Rotation): Boolean {
+ val displayBounds = WindowUtils.getDisplayBounds(rotation)
+ return displayBounds.width > displayBounds.height
+ }
+
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ open fun cujCompleted() {
+ // TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is
+ // robust enough to get the correct end state.
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
new file mode 100644
index 0000000..bbb2edc
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: FlickerTest) :
+ SplitScreenBase(flicker) {
+ private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
+
+ protected val thisTransition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+ thirdApp.launchViaIntent(wmHelper)
+ wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify()
+ }
+ transitions {
+ tapl.launchedAppState.quickSwitchToPreviousApp()
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ withoutTracing(this)
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
+ }
+
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
new file mode 100644
index 0000000..fa38293
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class SwitchBackToSplitFromHomeBenchmark(override val flicker: FlickerTest) :
+ SplitScreenBase(flicker) {
+ protected val thisTransition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+ tapl.goHome()
+ wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+ }
+ transitions {
+ tapl.workspace.quickSwitchToPreviousApp()
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ withoutTracing(this)
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
+ }
+
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
new file mode 100644
index 0000000..1064bd9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitScreenEntered
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class SwitchBackToSplitFromRecentBenchmark(override val flicker: FlickerTest) :
+ SplitScreenBase(flicker) {
+ protected val thisTransition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+ tapl.goHome()
+ wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+ }
+ transitions {
+ tapl.workspace.switchToOverview().currentTask.open()
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ withoutTracing(this)
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
+ }
+
+ @IwTest(focusArea = "sysui")
+ @Presubmit
+ @Test
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
new file mode 100644
index 0000000..8f4393f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.wm.shell.flicker.splitscreen.benchmark
+
+import android.platform.test.annotations.IwTest
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
+import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class SwitchBetweenSplitPairsBenchmark(override val flicker: FlickerTest) :
+ SplitScreenBase(flicker) {
+ protected val thirdApp = SplitScreenUtils.getIme(instrumentation)
+ protected val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
+
+ protected val thisTransition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp)
+ }
+ transitions {
+ tapl.launchedAppState.quickSwitchToPreviousApp()
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ teardown {
+ thirdApp.exit(wmHelper)
+ fourthApp.exit(wmHelper)
+ }
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ defaultSetup(this)
+ defaultTeardown(this)
+ thisTransition(this)
+ }
+
+ @IwTest(focusArea = "sysui") @Presubmit @Test open fun cujCompleted() {}
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 57a6981..ad4d97f 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -47,7 +47,7 @@
"truth-prebuilt",
"testables",
"platform-test-annotations",
- "frameworks-base-testutils",
+ "servicestests-utils",
],
libs: [
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 de967bf..afec1ee 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.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 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 @@
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 @@
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 1347e06..60ee918 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 @@
@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 63de74f..d6387ee 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_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 @@
@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 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 @@
@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 @@
@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 @@
}
@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 45cb3a0..3bc2f0e 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
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()
- // Equal to 2 because adding the listener notifies the current state
- assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(2)
+ 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()
+
+ // Secondary display should have 1 call for visible task added
+ assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1)
+ assertThat(listener.hasVisibleTasksOnSecondaryDisplay).isTrue()
}
@Test
@@ -122,52 +208,83 @@
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 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
+ var hasVisibleTasksOnDefaultDisplay = false
+ var hasVisibleTasksOnSecondaryDisplay = false
- override fun onVisibilityChanged(hasVisibleTasks: Boolean) {
- hasVisibleFreeformTasks = hasVisibleTasks
- visibleFreeformTaskChangedCalls++
+ 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 c9bd695..1335ebf 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,11 +25,13 @@
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
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.window.DisplayAreaInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
@@ -84,10 +86,10 @@
@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 +157,7 @@
markTaskHidden(task1)
markTaskHidden(task2)
- controller.showDesktopApps()
+ controller.showDesktopApps(DEFAULT_DISPLAY)
val wct = getLatestWct(expectTransition = TRANSIT_NONE)
assertThat(wct.hierarchyOps).hasSize(3)
@@ -173,7 +175,7 @@
markTaskVisible(task1)
markTaskVisible(task2)
- controller.showDesktopApps()
+ controller.showDesktopApps(DEFAULT_DISPLAY)
val wct = getLatestWct(expectTransition = TRANSIT_NONE)
assertThat(wct.hierarchyOps).hasSize(3)
@@ -191,7 +193,7 @@
markTaskHidden(task1)
markTaskVisible(task2)
- controller.showDesktopApps()
+ controller.showDesktopApps(DEFAULT_DISPLAY)
val wct = getLatestWct(expectTransition = TRANSIT_NONE)
assertThat(wct.hierarchyOps).hasSize(3)
@@ -205,7 +207,7 @@
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 +215,26 @@
}
@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 +242,7 @@
setUpHomeTask()
setUpFreeformTask().also(::markTaskVisible)
setUpFreeformTask().also(::markTaskVisible)
- assertThat(controller.getVisibleTaskCount()).isEqualTo(2)
+ assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
}
@Test
@@ -230,7 +250,15 @@
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 +286,7 @@
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 +295,28 @@
}
@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 +332,70 @@
}
@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 moveToNextDisplay_noOtherDisplays() {
+ whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY))
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ controller.moveToNextDisplay(task.taskId)
+ verifyWCTNotExecuted()
+ }
+
+ @Test
+ fun moveToNextDisplay_moveFromFirstToSecondDisplay() {
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Create a mock for the target display area: second display
+ val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY))
+ .thenReturn(secondDisplayArea)
+
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ controller.moveToNextDisplay(task.taskId)
+ with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+ assertThat(hierarchyOps).hasSize(1)
+ assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder())
+ assertThat(hierarchyOps[0].isReparent).isTrue()
+ assertThat(hierarchyOps[0].newParent).isEqualTo(secondDisplayArea.token.asBinder())
+ assertThat(hierarchyOps[0].toTop).isTrue()
+ }
+ }
+
+ @Test
+ fun moveToNextDisplay_moveFromSecondToFirstDisplay() {
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Create a mock for the target display area: default display
+ val defaultDisplayArea = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .thenReturn(defaultDisplayArea)
+
+ val task = setUpFreeformTask(displayId = SECOND_DISPLAY)
+ controller.moveToNextDisplay(task.taskId)
+
+ with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+ assertThat(hierarchyOps).hasSize(1)
+ assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder())
+ assertThat(hierarchyOps[0].isReparent).isTrue()
+ assertThat(hierarchyOps[0].newParent).isEqualTo(defaultDisplayArea.token.asBinder())
+ assertThat(hierarchyOps[0].toTop).isTrue()
+ }
+ }
+
+ @Test
fun getTaskWindowingMode() {
val fullscreenTask = setUpFullscreenTask()
val freeformTask = setUpFreeformTask()
@@ -324,6 +439,18 @@
}
@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 +489,18 @@
}
@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 +539,43 @@
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 +604,10 @@
): 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 dc91d75..cf1ff32 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_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 @@
/** 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 @@
/** 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/desktopmode/EnterDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
index 6199e0b..8592dea 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
@@ -94,7 +94,7 @@
WindowContainerTransaction wct = new WindowContainerTransaction();
doReturn(mToken).when(mTransitions)
.startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler);
- mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct);
+ mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct, null);
TransitionInfo.Change change =
createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
@@ -115,7 +115,7 @@
WindowContainerTransaction wct = new WindowContainerTransaction();
doReturn(mToken).when(mTransitions)
.startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler);
- mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct);
+ mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct, null);
TransitionInfo.Change change =
createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
index 4fad054..265b10d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
@@ -100,7 +100,7 @@
doReturn(mToken).when(mTransitions)
.startTransition(transitionType, wct, mExitDesktopTaskTransitionHandler);
- mExitDesktopTaskTransitionHandler.startTransition(transitionType, wct);
+ mExitDesktopTaskTransitionHandler.startTransition(transitionType, wct, null);
TransitionInfo.Change change =
createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FULLSCREEN);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
index 523cb66..54f36f6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
@@ -48,6 +48,7 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -71,6 +72,8 @@
@Mock
private ShellController mShellController;
@Mock
+ private ShellCommandHandler mShellCommandHandler;
+ @Mock
private DisplayController mDisplayController;
@Mock
private UiEventLogger mUiEventLogger;
@@ -89,7 +92,8 @@
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
mController = new DragAndDropController(mContext, mShellInit, mShellController,
- mDisplayController, mUiEventLogger, mIconProvider, mMainExecutor);
+ mShellCommandHandler, mDisplayController, mUiEventLogger, mIconProvider,
+ mMainExecutor);
mController.onInit();
}
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 6995d10..04f2c99 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 @@
}
@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 @@
}
@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/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index e6219d1..d27064d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -152,10 +152,15 @@
when(mStageCoordinator.isSplitActive()).thenReturn(true);
final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
- final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final WindowContainerTransaction wct = spy(new WindowContainerTransaction());
mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
- verify(mSideStage).addTask(eq(task), eq(wct));
+ verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task),
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT));
+ verify(mMainStage).reparentTopTask(eq(wct));
+ verify(mMainStage).evictAllChildren(eq(wct));
+ verify(mSideStage).evictAllChildren(eq(wct));
+ verify(mSplitLayout).resetDividerPosition();
assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
}
@@ -171,14 +176,11 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
- verify(mMainStage).addTask(eq(task), eq(wct));
+ verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task),
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT));
+ verify(mMainStage).evictAllChildren(eq(wct));
assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition());
assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getSideStagePosition());
-
- mStageCoordinator.moveToStage(task, SPLIT_POSITION_TOP_OR_LEFT, wct);
- verify(mSideStage).addTask(eq(task), eq(wct));
- assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getSideStagePosition());
- assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index bf62acf..8115a5d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -265,17 +265,17 @@
mStartingSurfaceDrawer.mWindowRecords.addRecord(taskId,
new StartingSurfaceDrawer.StartingWindowRecord() {
@Override
- public void removeIfPossible(StartingWindowRemovalInfo info,
+ public boolean removeIfPossible(StartingWindowRemovalInfo info,
boolean immediately) {
-
+ return true;
}
});
mStartingSurfaceDrawer.mWindowlessRecords.addRecord(taskId,
new StartingSurfaceDrawer.StartingWindowRecord() {
@Override
- public void removeIfPossible(StartingWindowRemovalInfo info,
+ public boolean removeIfPossible(StartingWindowRemovalInfo info,
boolean immediately) {
-
+ return true;
}
});
mStartingSurfaceDrawer.clearAllWindows();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
index 4559095..9d56686 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
@@ -179,4 +179,23 @@
mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE);
assertThat(pendingBounds2).isNull();
}
+
+ @Test
+ public void testSetTaskVisibility_taskRemoved_noNPE() {
+ mTaskViewTransitions.removeTaskView(mTaskViewTaskController);
+
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, false);
+ }
+
+ @Test
+ public void testSetTaskBounds_taskRemoved_noNPE() {
+ mTaskViewTransitions.removeTaskView(mTaskViewTaskController);
+
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
+ new Rect(0, 0, 100, 100));
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 8eb5c6a..963632b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.transition;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
@@ -50,6 +51,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.after;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
@@ -58,14 +60,19 @@
import static org.mockito.Mockito.verify;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.IApplicationThread;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.ArraySet;
import android.util.Pair;
+import android.view.IRecentsAnimationRunner;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -86,6 +93,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.server.testutils.StubTransaction;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.TransitionInfoBuilder;
@@ -93,6 +101,7 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.sysui.ShellSharedConstants;
@@ -100,6 +109,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Answers;
import org.mockito.InOrder;
import java.util.ArrayList;
@@ -162,8 +172,8 @@
verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
- transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
- mock(SurfaceControl.Transaction.class));
+ transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+ new StubTransaction());
assertEquals(1, mDefaultHandler.activeCount());
mDefaultHandler.finishAll();
mMainExecutor.flushAll();
@@ -212,8 +222,8 @@
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
verify(mOrganizer, times(1)).startTransition(eq(transitToken), isNull());
- transitions.onTransitionReady(transitToken, open, mock(SurfaceControl.Transaction.class),
- mock(SurfaceControl.Transaction.class));
+ transitions.onTransitionReady(transitToken, open, new StubTransaction(),
+ new StubTransaction());
assertEquals(1, mDefaultHandler.activeCount());
assertEquals(0, testHandler.activeCount());
mDefaultHandler.finishAll();
@@ -228,8 +238,8 @@
new TransitionRequestInfo(TRANSIT_OPEN, mwTaskInfo, null /* remote */));
verify(mOrganizer, times(1)).startTransition(
eq(transitToken), eq(handlerWCT));
- transitions.onTransitionReady(transitToken, open, mock(SurfaceControl.Transaction.class),
- mock(SurfaceControl.Transaction.class));
+ transitions.onTransitionReady(transitToken, open, new StubTransaction(),
+ new StubTransaction());
assertEquals(1, mDefaultHandler.activeCount());
assertEquals(0, testHandler.activeCount());
mDefaultHandler.finishAll();
@@ -246,8 +256,8 @@
eq(transitToken), eq(handlerWCT));
TransitionInfo change = new TransitionInfoBuilder(TRANSIT_CHANGE)
.addChange(TRANSIT_CHANGE).build();
- transitions.onTransitionReady(transitToken, change, mock(SurfaceControl.Transaction.class),
- mock(SurfaceControl.Transaction.class));
+ transitions.onTransitionReady(transitToken, change, new StubTransaction(),
+ new StubTransaction());
assertEquals(0, mDefaultHandler.activeCount());
assertEquals(1, testHandler.activeCount());
assertEquals(0, topHandler.activeCount());
@@ -284,8 +294,8 @@
verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
- transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
- mock(SurfaceControl.Transaction.class));
+ transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+ new StubTransaction());
assertEquals(0, mDefaultHandler.activeCount());
assertTrue(remoteCalled[0]);
mDefaultHandler.finishAll();
@@ -434,8 +444,8 @@
verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
- transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
- mock(SurfaceControl.Transaction.class));
+ transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+ new StubTransaction());
assertEquals(0, mDefaultHandler.activeCount());
assertTrue(remoteCalled[0]);
mDefaultHandler.finishAll();
@@ -484,10 +494,10 @@
oneShot.setTransition(transitToken);
IBinder anotherToken = new Binder();
assertFalse(oneShot.startAnimation(anotherToken, new TransitionInfo(transitType, 0),
- mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class),
+ new StubTransaction(), new StubTransaction(),
testFinish));
assertTrue(oneShot.startAnimation(transitToken, new TransitionInfo(transitType, 0),
- mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class),
+ new StubTransaction(), new StubTransaction(),
testFinish));
}
@@ -501,8 +511,8 @@
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
- transitions.onTransitionReady(transitToken1, info1, mock(SurfaceControl.Transaction.class),
- mock(SurfaceControl.Transaction.class));
+ transitions.onTransitionReady(transitToken1, info1, new StubTransaction(),
+ new StubTransaction());
assertEquals(1, mDefaultHandler.activeCount());
IBinder transitToken2 = new Binder();
@@ -510,8 +520,8 @@
new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
- transitions.onTransitionReady(transitToken2, info2, mock(SurfaceControl.Transaction.class),
- mock(SurfaceControl.Transaction.class));
+ transitions.onTransitionReady(transitToken2, info2, new StubTransaction(),
+ new StubTransaction());
// default handler doesn't merge by default, so it shouldn't increment active count.
assertEquals(1, mDefaultHandler.activeCount());
assertEquals(0, mDefaultHandler.mergeCount());
@@ -542,8 +552,8 @@
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
- transitions.onTransitionReady(transitToken1, info1, mock(SurfaceControl.Transaction.class),
- mock(SurfaceControl.Transaction.class));
+ transitions.onTransitionReady(transitToken1, info1, new StubTransaction(),
+ new StubTransaction());
assertEquals(1, mDefaultHandler.activeCount());
IBinder transitToken2 = new Binder();
@@ -551,8 +561,8 @@
new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
- transitions.onTransitionReady(transitToken2, info2, mock(SurfaceControl.Transaction.class),
- mock(SurfaceControl.Transaction.class));
+ transitions.onTransitionReady(transitToken2, info2, new StubTransaction(),
+ new StubTransaction());
// it should still only have 1 active, but then show 1 merged
assertEquals(1, mDefaultHandler.activeCount());
assertEquals(1, mDefaultHandler.mergeCount());
@@ -611,8 +621,8 @@
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
- transitions.onTransitionReady(token, info, mock(SurfaceControl.Transaction.class),
- mock(SurfaceControl.Transaction.class));
+ transitions.onTransitionReady(token, info, new StubTransaction(),
+ new StubTransaction());
return token;
};
@@ -678,8 +688,8 @@
// queued), so continue the transition lifecycle for that.
TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
- transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
- mock(SurfaceControl.Transaction.class));
+ transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+ new StubTransaction());
// At this point, if things are not working, we'd get an NPE due to attempting to merge
// into the shellInit transition which hasn't started yet.
assertEquals(1, mDefaultHandler.activeCount());
@@ -791,8 +801,8 @@
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
- transitions.onTransitionReady(transitToken1, info1, mock(SurfaceControl.Transaction.class),
- mock(SurfaceControl.Transaction.class));
+ transitions.onTransitionReady(transitToken1, info1, new StubTransaction(),
+ new StubTransaction());
assertEquals(1, mDefaultHandler.activeCount());
transitions.runOnIdle(runnable2);
@@ -806,8 +816,8 @@
new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
- transitions.onTransitionReady(transitToken2, info2, mock(SurfaceControl.Transaction.class),
- mock(SurfaceControl.Transaction.class));
+ transitions.onTransitionReady(transitToken2, info2, new StubTransaction(),
+ new StubTransaction());
assertEquals(1, mDefaultHandler.activeCount());
mDefaultHandler.finishAll();
@@ -858,8 +868,8 @@
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
- SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
- SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction startT = new StubTransaction();
+ SurfaceControl.Transaction finishT = new StubTransaction();
transitions.onTransitionReady(transitToken, info, startT, finishT);
InOrder observerOrder = inOrder(observer);
@@ -883,8 +893,8 @@
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
- SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
- SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction startT1 = new StubTransaction();
+ SurfaceControl.Transaction finishT1 = new StubTransaction();
transitions.onTransitionReady(transitToken1, info1, startT1, finishT1);
verify(observer).onTransitionReady(transitToken1, info1, startT1, finishT1);
@@ -893,8 +903,8 @@
new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
- SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
- SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction startT2 = new StubTransaction();
+ SurfaceControl.Transaction finishT2 = new StubTransaction();
transitions.onTransitionReady(transitToken2, info2, startT2, finishT2);
verify(observer, times(1)).onTransitionReady(transitToken2, info2, startT2, finishT2);
verify(observer, times(0)).onTransitionStarting(transitToken2);
@@ -927,8 +937,8 @@
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
- SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
- SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction startT1 = new StubTransaction();
+ SurfaceControl.Transaction finishT1 = new StubTransaction();
transitions.onTransitionReady(transitToken1, info1, startT1, finishT1);
IBinder transitToken2 = new Binder();
@@ -936,8 +946,8 @@
new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
- SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
- SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction startT2 = new StubTransaction();
+ SurfaceControl.Transaction finishT2 = new StubTransaction();
transitions.onTransitionReady(transitToken2, info2, startT2, finishT2);
InOrder observerOrder = inOrder(observer);
@@ -999,8 +1009,8 @@
new TransitionRequestInfo(TRANSIT_CHANGE, mwTaskInfo, null /* remote */));
TransitionInfo change = new TransitionInfoBuilder(TRANSIT_CHANGE)
.addChange(TRANSIT_CHANGE).build();
- SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
- SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction startT1 = new StubTransaction();
+ SurfaceControl.Transaction finishT1 = new StubTransaction();
transitions.onTransitionReady(transitToken1, change, startT1, finishT1);
// Request the second transition that should be handled by the default handler
@@ -1009,8 +1019,8 @@
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
transitions.requestStartTransition(transitToken2,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
- SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
- SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction startT2 = new StubTransaction();
+ SurfaceControl.Transaction finishT2 = new StubTransaction();
transitions.onTransitionReady(transitToken2, open, startT2, finishT2);
verify(observer).onTransitionReady(transitToken2, open, startT2, finishT2);
verify(observer, times(0)).onTransitionStarting(transitToken2);
@@ -1019,8 +1029,8 @@
IBinder transitToken3 = new Binder();
transitions.requestStartTransition(transitToken3,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
- SurfaceControl.Transaction startT3 = mock(SurfaceControl.Transaction.class);
- SurfaceControl.Transaction finishT3 = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction startT3 = new StubTransaction();
+ SurfaceControl.Transaction finishT3 = new StubTransaction();
transitions.onTransitionReady(transitToken3, open, startT3, finishT3);
verify(observer, times(0)).onTransitionStarting(transitToken2);
verify(observer).onTransitionReady(transitToken3, open, startT3, finishT3);
@@ -1045,6 +1055,104 @@
}
@Test
+ public void testTransitSleep_squashesRecents() {
+ ShellInit shellInit = new ShellInit(mMainExecutor);
+ final Transitions transitions =
+ new Transitions(mContext, shellInit, mock(ShellController.class), mOrganizer,
+ mTransactionPool, createTestDisplayController(), mMainExecutor,
+ mMainHandler, mAnimExecutor);
+ final RecentsTransitionHandler recentsHandler =
+ new RecentsTransitionHandler(shellInit, transitions, null);
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+ shellInit.init();
+
+ Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
+ transitions.registerObserver(observer);
+
+ RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_RECENTS);
+ RunningTaskInfo task2 = createTaskInfo(2);
+
+ // Start an open transition for the purpose of occupying the ready queue
+ final IBinder transitOpen1 = new Binder("transitOpen1");
+ final TransitionInfo infoOpen1 =
+ new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN, task1)
+ .build();
+ mMainExecutor.execute(() -> {
+ transitions.requestStartTransition(transitOpen1, new TransitionRequestInfo(
+ TRANSIT_OPEN, task1 /* trigger */, null /* remote */));
+ onTransitionReady(transitions, transitOpen1, infoOpen1);
+ });
+
+ // First transition on the queue should start immediately.
+ mMainExecutor.flushAll();
+ verify(observer).onTransitionReady(eq(transitOpen1), any(), any(), any());
+ verify(observer).onTransitionStarting(eq(transitOpen1));
+
+ // Start recents
+ final IRecentsAnimationRunner recentsListener =
+ mock(IRecentsAnimationRunner.class, Answers.RETURNS_DEEP_STUBS);
+ final IBinder transitRecents = recentsHandler.startRecentsTransition(
+ mock(PendingIntent.class) /* intent */,
+ mock(Intent.class) /* fillIn */,
+ new Bundle() /* options */,
+ mock(IApplicationThread.class) /* appThread */,
+ recentsListener);
+ final TransitionInfo infoRecents =
+ new TransitionInfoBuilder(TRANSIT_TO_FRONT)
+ .addChange(TRANSIT_TO_FRONT, task1)
+ .addChange(TRANSIT_CLOSE, task2)
+ .build();
+ onTransitionReady(transitions, transitRecents, infoRecents);
+
+ // Start another open transition during recents
+ final IBinder transitOpen2 = new Binder("transitOpen2");
+ final TransitionInfo infoOpen2 =
+ new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN, task2)
+ .addChange(TRANSIT_TO_BACK, task1)
+ .build();
+ mMainExecutor.execute(() -> {
+ transitions.requestStartTransition(transitOpen2, new TransitionRequestInfo(
+ TRANSIT_OPEN, task2 /* trigger */, null /* remote */));
+ onTransitionReady(transitions, transitOpen2, infoOpen2);
+ });
+
+ // Finish testOpen1 to start processing the other transitions
+ mMainExecutor.execute(() -> {
+ mDefaultHandler.finishOne();
+ });
+ mMainExecutor.flushAll();
+
+ // Recents transition SHOULD start, and merge the open transition, which should NOT start.
+ verify(observer).onTransitionFinished(eq(transitOpen1), eq(false) /* aborted */);
+ verify(observer).onTransitionReady(eq(transitRecents), any(), any(), any());
+ verify(observer).onTransitionStarting(eq(transitRecents));
+ verify(observer).onTransitionReady(eq(transitOpen2), any(), any(), any());
+ verify(observer).onTransitionMerged(eq(transitOpen2), eq(transitRecents));
+ // verify(observer).onTransitionFinished(eq(transitOpen2), eq(true) /* aborted */);
+
+ // Go to sleep
+ final IBinder transitSleep = new Binder("transitSleep");
+ final TransitionInfo infoSleep = new TransitionInfoBuilder(TRANSIT_SLEEP).build();
+ mMainExecutor.execute(() -> {
+ transitions.requestStartTransition(transitSleep, new TransitionRequestInfo(
+ TRANSIT_SLEEP, null /* trigger */, null /* remote */));
+ onTransitionReady(transitions, transitSleep, infoSleep);
+ });
+ mMainExecutor.flushAll();
+
+ // Recents transition should finish itself when it sees the sleep transition coming.
+ verify(observer).onTransitionFinished(eq(transitRecents), eq(false));
+ verify(observer).onTransitionFinished(eq(transitSleep), eq(false));
+ }
+
+ private void onTransitionReady(Transitions transitions, IBinder token, TransitionInfo info) {
+ transitions.onTransitionReady(token, info, new StubTransaction(),
+ new StubTransaction());
+ }
+
+ @Test
public void testEmptyTransitionStillReportsKeyguardGoingAway() {
Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
@@ -1056,8 +1164,8 @@
// Make a no-op transition
TransitionInfo info = new TransitionInfoBuilder(
TRANSIT_OPEN, TRANSIT_FLAG_KEYGUARD_GOING_AWAY, true /* noOp */).build();
- transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
- mock(SurfaceControl.Transaction.class));
+ transitions.onTransitionReady(transitToken, info, new StubTransaction(),
+ new StubTransaction());
// If keyguard-going-away flag set, then it shouldn't be aborted.
assertEquals(1, mDefaultHandler.activeCount());
@@ -1397,7 +1505,7 @@
private static void onTransitionReady(Transitions transitions, IBinder token) {
transitions.onTransitionReady(token, createTransitionInfo(),
- mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class));
+ new StubTransaction(), new StubTransaction());
}
private static TransitionInfo createTransitionInfo() {
@@ -1414,15 +1522,15 @@
private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode, int activityType) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
+ taskInfo.topActivityType = activityType;
taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
taskInfo.configuration.windowConfiguration.setActivityType(activityType);
+ taskInfo.token = mock(WindowContainerToken.class);
return taskInfo;
}
private static RunningTaskInfo createTaskInfo(int taskId) {
- RunningTaskInfo taskInfo = new RunningTaskInfo();
- taskInfo.taskId = taskId;
- return taskInfo;
+ return createTaskInfo(taskId, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
}
private DisplayController createTestDisplayController() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
new file mode 100644
index 0000000..348b365
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -0,0 +1,232 @@
+/*
+ * 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.wm.shell.windowdecor
+
+import android.app.ActivityManager
+import android.graphics.PointF
+import android.graphics.Rect
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.window.WindowContainerToken
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
+import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.Mockito.any
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [DragPositioningCallbackUtility].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DragPositioningCallbackUtilityTest
+ */
+@RunWith(AndroidTestingRunner::class)
+class DragPositioningCallbackUtilityTest {
+ @Mock
+ private lateinit var mockWindowDecoration: WindowDecoration<*>
+ @Mock
+ private lateinit var taskToken: WindowContainerToken
+ @Mock
+ private lateinit var taskBinder: IBinder
+ @Mock
+ private lateinit var mockDisplayController: DisplayController
+ @Mock
+ private lateinit var mockDisplayLayout: DisplayLayout
+ @Mock
+ private lateinit var mockDisplay: Display
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(taskToken.asBinder()).thenReturn(taskBinder)
+ whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
+ whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ }
+
+ mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
+ taskId = TASK_ID
+ token = taskToken
+ minWidth = MIN_WIDTH
+ minHeight = MIN_HEIGHT
+ defaultMinSize = DEFAULT_MIN
+ displayId = DISPLAY_ID
+ configuration.windowConfiguration.bounds = STARTING_BOUNDS
+ }
+ mockWindowDecoration.mDisplay = mockDisplay
+ whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+ }
+
+ @Test
+ fun testChangeBoundsDoesNotChangeHeightWhenLessThanMin() {
+ val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+ val repositionTaskBounds = Rect()
+
+ // Resize to width of 95px and height of 5px with min width of 10px
+ val newX = STARTING_BOUNDS.right.toFloat() - 5
+ val newY = STARTING_BOUNDS.top.toFloat() + 95
+ val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+ DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+ false /* hasMoved */, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
+ mockDisplayController, mockWindowDecoration)
+
+ assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+ assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+ assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right - 5)
+ assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom)
+ }
+
+ @Test
+ fun testChangeBoundsDoesNotChangeWidthWhenLessThanMin() {
+ val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+ val repositionTaskBounds = Rect()
+
+ // Resize to height of 95px and width of 5px with min width of 10px
+ val newX = STARTING_BOUNDS.right.toFloat() - 95
+ val newY = STARTING_BOUNDS.top.toFloat() + 5
+ val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+ DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+ false /* hasMoved */, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
+ mockDisplayController, mockWindowDecoration)
+
+ assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+ assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top + 5)
+ assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right)
+ assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom)
+ }
+
+ @Test
+ fun testChangeBoundsDoesNotChangeHeightWhenNegative() {
+ val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+ val repositionTaskBounds = Rect()
+
+ // Resize to width of 95px and width of -5px with minimum of 10px
+ val newX = STARTING_BOUNDS.right.toFloat() - 5
+ val newY = STARTING_BOUNDS.top.toFloat() + 105
+ val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+ DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+ false /* hasMoved */, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
+ mockDisplayController, mockWindowDecoration)
+
+ assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+ assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+ assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right - 5)
+ assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom)
+ }
+
+ @Test
+ fun testChangeBoundsRunsWhenResizeBoundsValid() {
+ val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+ val repositionTaskBounds = Rect()
+
+ // Shrink to height 20px and width 20px with both min height/width equal to 10px
+ val newX = STARTING_BOUNDS.right.toFloat() - 80
+ val newY = STARTING_BOUNDS.top.toFloat() + 80
+ val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+ DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+ false /* hasMoved */, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
+ mockDisplayController, mockWindowDecoration)
+ assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+ assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top + 80)
+ assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right - 80)
+ assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom)
+ }
+
+ @Test
+ fun testChangeBoundsDoesNotRunWithNegativeHeightAndWidth() {
+ val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+ val repositionTaskBounds = Rect()
+ // Shrink to height -5px and width -5px with both min height/width equal to 10px
+ val newX = STARTING_BOUNDS.right.toFloat() - 105
+ val newY = STARTING_BOUNDS.top.toFloat() + 105
+
+ val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+
+ DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+ false /* hasMoved */, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
+ mockDisplayController, mockWindowDecoration)
+ assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+ assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+ assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right)
+ assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom)
+ }
+
+ @Test
+ fun testChangeBounds_toDisallowedBounds_freezesAtLimit() {
+ var hasMoved = false
+ val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.bottom.toFloat())
+ val repositionTaskBounds = Rect()
+ // Initial resize to width and height 110px.
+ var newX = STARTING_BOUNDS.right.toFloat() + 10
+ var newY = STARTING_BOUNDS.bottom.toFloat() + 10
+ var delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+ assertTrue(DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+ hasMoved, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
+ mockDisplayController, mockWindowDecoration))
+ hasMoved = true
+ // Resize width to 120px, height to disallowed area which should not result in a change.
+ newX += 10
+ newY = DISALLOWED_RESIZE_AREA.top.toFloat()
+ delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+ assertTrue(DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+ hasMoved, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta,
+ mockDisplayController, mockWindowDecoration))
+ assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+ assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+ assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right + 20)
+ assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom + 10)
+ }
+
+ companion object {
+ private const val TASK_ID = 5
+ private const val MIN_WIDTH = 10
+ private const val MIN_HEIGHT = 10
+ private const val DENSITY_DPI = 20
+ private const val DEFAULT_MIN = 40
+ private const val DISPLAY_ID = 1
+ private const val NAVBAR_HEIGHT = 50
+ private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
+ private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
+ private val DISALLOWED_RESIZE_AREA = Rect(
+ DISPLAY_BOUNDS.left,
+ DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
+ DISPLAY_BOUNDS.right,
+ DISPLAY_BOUNDS.bottom)
+ private val STABLE_BOUNDS = Rect(
+ DISPLAY_BOUNDS.left,
+ DISPLAY_BOUNDS.top,
+ DISPLAY_BOUNDS.right,
+ DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
+ )
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index 84ccdde..5bea8f2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -14,7 +14,6 @@
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED
@@ -22,7 +21,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.`when`
+import org.mockito.Mockito.`when` as whenever
import org.mockito.Mockito.any
import org.mockito.Mockito.argThat
import org.mockito.Mockito.never
@@ -72,10 +71,10 @@
mockDragStartListener
)
- `when`(taskToken.asBinder()).thenReturn(taskBinder)
- `when`(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
- `when`(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
- `when`(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+ whenever(taskToken.asBinder()).thenReturn(taskBinder)
+ whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
+ whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
(i.arguments.first() as Rect).set(STABLE_BOUNDS)
}
@@ -89,7 +88,7 @@
configuration.windowConfiguration.bounds = STARTING_BOUNDS
}
mockWindowDecoration.mDisplay = mockDisplay
- `when`(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+ whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
}
@Test
@@ -237,293 +236,6 @@
})
}
- @Test
- fun testDragResize_resize_setBoundsDoesNotChangeHeightWhenLessThanMin() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
- STARTING_BOUNDS.right.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- // Resize to width of 95px and height of 5px with min width of 10px
- val newX = STARTING_BOUNDS.right.toFloat() - 5
- val newY = STARTING_BOUNDS.top.toFloat() + 95
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
-
- taskPositioner.onDragPositioningEnd(newX, newY)
-
- verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
- != 0) && change.configuration.windowConfiguration.bounds.top ==
- STARTING_BOUNDS.top &&
- change.configuration.windowConfiguration.bounds.bottom ==
- STARTING_BOUNDS.bottom
- }
- })
- }
-
- @Test
- fun testDragResize_resize_setBoundsDoesNotChangeWidthWhenLessThanMin() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
- STARTING_BOUNDS.right.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- // Resize to height of 95px and width of 5px with min width of 10px
- val newX = STARTING_BOUNDS.right.toFloat() - 95
- val newY = STARTING_BOUNDS.top.toFloat() + 5
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
-
- taskPositioner.onDragPositioningEnd(newX, newY)
-
- verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
- != 0) && change.configuration.windowConfiguration.bounds.right ==
- STARTING_BOUNDS.right &&
- change.configuration.windowConfiguration.bounds.left ==
- STARTING_BOUNDS.left
- }
- })
- }
-
- @Test
- fun testDragResize_resize_setBoundsDoesNotChangeHeightWhenNegative() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
- STARTING_BOUNDS.right.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- // Resize to height of -5px and width of 95px
- val newX = STARTING_BOUNDS.right.toFloat() - 5
- val newY = STARTING_BOUNDS.top.toFloat() + 105
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
-
- taskPositioner.onDragPositioningEnd(newX, newY)
-
- verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
- != 0) && change.configuration.windowConfiguration.bounds.top ==
- STARTING_BOUNDS.top &&
- change.configuration.windowConfiguration.bounds.bottom ==
- STARTING_BOUNDS.bottom
- }
- })
- }
-
- @Test
- fun testDragResize_resize_setBoundsDoesNotChangeWidthWhenNegative() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
- STARTING_BOUNDS.right.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- // Resize to width of -5px and height of 95px
- val newX = STARTING_BOUNDS.right.toFloat() - 105
- val newY = STARTING_BOUNDS.top.toFloat() + 5
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
-
- taskPositioner.onDragPositioningEnd(newX, newY)
-
- verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS)
- != 0) && change.configuration.windowConfiguration.bounds.right ==
- STARTING_BOUNDS.right &&
- change.configuration.windowConfiguration.bounds.left ==
- STARTING_BOUNDS.left
- }
- })
- }
-
- @Test
- fun testDragResize_resize_setBoundsRunsWhenResizeBoundsValid() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
- STARTING_BOUNDS.right.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- // Shrink to height 20px and width 20px with both min height/width equal to 10px
- val newX = STARTING_BOUNDS.right.toFloat() - 80
- val newY = STARTING_BOUNDS.top.toFloat() + 80
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
-
- taskPositioner.onDragPositioningEnd(newX, newY)
-
- verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
- }
- })
- }
-
- @Test
- fun testDragResize_resize_setBoundsDoesNotRunWithNegativeHeightAndWidth() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
- STARTING_BOUNDS.right.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- // Shrink to height 5px and width 5px with both min height/width equal to 10px
- val newX = STARTING_BOUNDS.right.toFloat() - 95
- val newY = STARTING_BOUNDS.top.toFloat() + 95
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
-
- taskPositioner.onDragPositioningEnd(newX, newY)
-
- verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
- }
- })
- }
-
- @Test
- fun testDragResize_resize_useDefaultMinWhenMinWidthInvalid() {
- mockWindowDecoration.mTaskInfo.minWidth = -1
-
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
- STARTING_BOUNDS.right.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- // Shrink to width and height of 3px with invalid minWidth = -1 and defaultMinSize = 5px
- val newX = STARTING_BOUNDS.right.toFloat() - 97
- val newY = STARTING_BOUNDS.top.toFloat() + 97
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
-
- taskPositioner.onDragPositioningEnd(newX, newY)
-
- verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
- }
- })
- }
-
- @Test
- fun testDragResize_resize_useMinWidthWhenValid() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top
- STARTING_BOUNDS.right.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- // Shrink to width and height of 7px with valid minWidth = 10px and defaultMinSize = 5px
- val newX = STARTING_BOUNDS.right.toFloat() - 93
- val newY = STARTING_BOUNDS.top.toFloat() + 93
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
-
- taskPositioner.onDragPositioningEnd(newX, newY)
-
- verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
- }
- })
- }
-
- fun testDragResize_toDisallowedBounds_freezesAtLimit() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM, // Resize right-bottom corner
- STARTING_BOUNDS.right.toFloat(),
- STARTING_BOUNDS.bottom.toFloat()
- )
-
- // Resize the task by 10px to the right and bottom, a valid destination
- val newBounds = Rect(
- STARTING_BOUNDS.left,
- STARTING_BOUNDS.top,
- STARTING_BOUNDS.right + 10,
- STARTING_BOUNDS.bottom + 10)
- taskPositioner.onDragPositioningMove(
- newBounds.right.toFloat(),
- newBounds.bottom.toFloat()
- )
-
- // Resize the task by another 10px to the right (allowed) and to just in the disallowed
- // area of the Y coordinate.
- val newBounds2 = Rect(
- newBounds.left,
- newBounds.top,
- newBounds.right + 10,
- DISALLOWED_RESIZE_AREA.top
- )
- taskPositioner.onDragPositioningMove(
- newBounds2.right.toFloat(),
- newBounds2.bottom.toFloat()
- )
-
- taskPositioner.onDragPositioningEnd(newBounds2.right.toFloat(), newBounds2.bottom.toFloat())
-
- // The first resize falls in the allowed area, verify there's a change for it.
- verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder && change.ofBounds(newBounds)
- }
- })
- // The second resize falls in the disallowed area, verify there's no change for it.
- verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder && change.ofBounds(newBounds2)
- }
- })
- // Instead, there should be a change for its allowed portion (the X movement) with the Y
- // staying frozen in the last valid resize position.
- verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder && change.ofBounds(
- Rect(
- newBounds2.left,
- newBounds2.top,
- newBounds2.right,
- newBounds.bottom // Stayed at the first resize destination.
- )
- )
- }
- })
- }
-
private fun WindowContainerTransaction.Change.ofBounds(bounds: Rect): Boolean {
return ((windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) &&
bounds == configuration.windowConfiguration.bounds
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index bf365ca..498082b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -34,7 +34,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.`when`
+import org.mockito.Mockito.`when` as whenever
import org.mockito.Mockito.any
import org.mockito.Mockito.argThat
import org.mockito.Mockito.never
@@ -85,10 +85,10 @@
mockDragStartListener
)
- `when`(taskToken.asBinder()).thenReturn(taskBinder)
- `when`(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
- `when`(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
- `when`(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+ whenever(taskToken.asBinder()).thenReturn(taskBinder)
+ whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
+ whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
(i.arguments.first() as Rect).set(STABLE_BOUNDS)
}
@@ -102,7 +102,7 @@
configuration.windowConfiguration.bounds = STARTING_BOUNDS
}
mockDesktopWindowDecoration.mDisplay = mockDisplay
- `when`(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+ whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
}
@Test
diff --git a/libs/hwui/Tonemapper.cpp b/libs/hwui/Tonemapper.cpp
index 0d39f0e..974a5d0 100644
--- a/libs/hwui/Tonemapper.cpp
+++ b/libs/hwui/Tonemapper.cpp
@@ -97,7 +97,6 @@
.inputDataspace = sourceDataspace,
.outputDataspace = destinationDataspace,
.undoPremultipliedAlpha = source.alphaType() == kPremul_SkAlphaType,
- .fakeInputDataspace = destinationDataspace,
.type = shaders::LinearEffect::SkSLType::ColorFilter};
constexpr float kMaxDisplayBrightnessNits = 1000.f;
constexpr float kCurrentDisplayBrightnessNits = 500.f;
diff --git a/libs/hwui/effects/GainmapRenderer.cpp b/libs/hwui/effects/GainmapRenderer.cpp
index bfe4eaf..613f52b 100644
--- a/libs/hwui/effects/GainmapRenderer.cpp
+++ b/libs/hwui/effects/GainmapRenderer.cpp
@@ -38,7 +38,7 @@
using namespace renderthread;
-static float getTargetHdrSdrRatio(const SkColorSpace* destColorspace) {
+float getTargetHdrSdrRatio(const SkColorSpace* destColorspace) {
// We should always have a known destination colorspace. If we don't we must be in some
// legacy mode where we're lost and also definitely not going to HDR
if (destColorspace == nullptr) {
diff --git a/libs/hwui/effects/GainmapRenderer.h b/libs/hwui/effects/GainmapRenderer.h
index 4ed2445..0ab03f0 100644
--- a/libs/hwui/effects/GainmapRenderer.h
+++ b/libs/hwui/effects/GainmapRenderer.h
@@ -25,6 +25,8 @@
namespace android::uirenderer {
+float getTargetHdrSdrRatio(const SkColorSpace* destColorspace);
+
void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkRect& src,
const SkRect& dst, const SkSamplingOptions& sampling, const SkPaint* paint,
SkCanvas::SrcRectConstraint constraint,
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index a4960ea..c58ba68 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -26,6 +26,7 @@
#include "SkM44.h"
#include "include/gpu/GpuTypes.h" // from Skia
#include "utils/GLUtils.h"
+#include <effects/GainmapRenderer.h>
namespace android {
namespace uirenderer {
@@ -129,6 +130,7 @@
info.height = fboSize.height();
mat4.getColMajor(&info.transform[0]);
info.color_space_ptr = canvas->imageInfo().colorSpace();
+ info.currentHdrSdrRatio = getTargetHdrSdrRatio(info.color_space_ptr);
// ensure that the framebuffer that the webview will render into is bound before we clear
// the stencil and/or draw the functor.
diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
index e6ef95b..e299d12 100644
--- a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
@@ -30,6 +30,7 @@
#include "renderthread/VulkanManager.h"
#include "thread/ThreadBase.h"
#include "utils/TimeUtils.h"
+#include "effects/GainmapRenderer.h"
namespace android {
namespace uirenderer {
@@ -73,6 +74,7 @@
.clip_right = mClip.fRight,
.clip_bottom = mClip.fBottom,
.is_layer = !vulkan_info.fFromSwapchainOrAndroidWindow,
+ .currentHdrSdrRatio = getTargetHdrSdrRatio(mImageInfo.colorSpace()),
};
mat4.getColMajor(¶ms.transform[0]);
params.secondary_command_buffer = vulkan_info.fSecondaryCommandBuffer;
diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
index e168a7b..adf3c06 100644
--- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
@@ -32,6 +32,7 @@
#include "renderthread/EglManager.h"
#include "thread/ThreadBase.h"
#include "utils/TimeUtils.h"
+#include "effects/GainmapRenderer.h"
#include <SkBlendMode.h>
@@ -139,6 +140,7 @@
info.height = mFBInfo.height();
mat4.getColMajor(&info.transform[0]);
info.color_space_ptr = canvas->imageInfo().colorSpace();
+ info.currentHdrSdrRatio = getTargetHdrSdrRatio(info.color_space_ptr);
glViewport(0, 0, info.width, info.height);
diff --git a/libs/hwui/private/hwui/DrawGlInfo.h b/libs/hwui/private/hwui/DrawGlInfo.h
index 501b8df..7888c87 100644
--- a/libs/hwui/private/hwui/DrawGlInfo.h
+++ b/libs/hwui/private/hwui/DrawGlInfo.h
@@ -86,6 +86,11 @@
// commands are issued.
kStatusDrew = 0x4
};
+
+ // The current HDR/SDR ratio that we are rendering to. The transform to SDR will already
+ // be baked into the color_space_ptr, so this is just to indicate the amount of extended
+ // range is available if desired
+ float currentHdrSdrRatio;
}; // struct DrawGlInfo
} // namespace uirenderer
diff --git a/libs/hwui/private/hwui/DrawVkInfo.h b/libs/hwui/private/hwui/DrawVkInfo.h
index 5c59657..8f7063d 100644
--- a/libs/hwui/private/hwui/DrawVkInfo.h
+++ b/libs/hwui/private/hwui/DrawVkInfo.h
@@ -71,6 +71,11 @@
// Input: Whether destination surface is offscreen surface.
bool is_layer;
+
+ // The current HDR/SDR ratio that we are rendering to. The transform to SDR will already
+ // be baked into the color_space_ptr, so this is just to indicate the amount of extended
+ // range is available if desired
+ float currentHdrSdrRatio;
};
} // namespace uirenderer
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index 7e9d44f..c00a270 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -29,6 +29,7 @@
#include "Layer.h"
#include "Properties.h"
#include "RenderThread.h"
+#include "VulkanManager.h"
#include "pipeline/skia/ATraceMemoryDump.h"
#include "pipeline/skia/ShaderCache.h"
#include "pipeline/skia/SkiaMemoryTracer.h"
@@ -182,8 +183,14 @@
}
log.appendFormat("Contexts: %zu (stopped = %zu)\n", mCanvasContexts.size(), stoppedContexts);
+ auto vkInstance = VulkanManager::peekInstance();
if (!mGrContext) {
- log.appendFormat("No GPU context.\n");
+ if (!vkInstance) {
+ log.appendFormat("No GPU context.\n");
+ } else {
+ log.appendFormat("No GrContext; however %d remaining Vulkan refs",
+ vkInstance->getStrongCount() - 1);
+ }
return;
}
std::vector<skiapipeline::ResourcePair> cpuResourceMap = {
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 96bfc10..4cffc6c 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -42,7 +42,7 @@
namespace uirenderer {
namespace renderthread {
-static std::array<std::string_view, 18> sEnableExtensions{
+static std::array<std::string_view, 19> sEnableExtensions{
VK_KHR_BIND_MEMORY_2_EXTENSION_NAME,
VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME,
@@ -56,6 +56,7 @@
VK_KHR_SURFACE_EXTENSION_NAME,
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME,
+ VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME,
VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME,
VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME,
@@ -106,11 +107,11 @@
#define GET_INST_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(mInstance, "vk" #F)
#define GET_DEV_PROC(F) m##F = (PFN_vk##F)vkGetDeviceProcAddr(mDevice, "vk" #F)
-sp<VulkanManager> VulkanManager::getInstance() {
- // cache a weakptr to the context to enable a second thread to share the same vulkan state
- static wp<VulkanManager> sWeakInstance = nullptr;
- static std::mutex sLock;
+// cache a weakptr to the context to enable a second thread to share the same vulkan state
+static wp<VulkanManager> sWeakInstance = nullptr;
+static std::mutex sLock;
+sp<VulkanManager> VulkanManager::getInstance() {
std::lock_guard _lock{sLock};
sp<VulkanManager> vulkanManager = sWeakInstance.promote();
if (!vulkanManager.get()) {
@@ -121,6 +122,11 @@
return vulkanManager;
}
+sp<VulkanManager> VulkanManager::peekInstance() {
+ std::lock_guard _lock{sLock};
+ return sWeakInstance.promote();
+}
+
VulkanManager::~VulkanManager() {
if (mDevice != VK_NULL_HANDLE) {
mDeviceWaitIdle(mDevice);
@@ -403,9 +409,13 @@
}
}
-sk_sp<GrDirectContext> VulkanManager::createContext(const GrContextOptions& options,
- ContextType contextType) {
+static void onGrContextReleased(void* context) {
+ VulkanManager* manager = (VulkanManager*)context;
+ manager->decStrong((void*)onGrContextReleased);
+}
+sk_sp<GrDirectContext> VulkanManager::createContext(GrContextOptions& options,
+ ContextType contextType) {
GrVkBackendContext backendContext;
backendContext.fInstance = mInstance;
backendContext.fPhysicalDevice = mPhysicalDevice;
@@ -417,6 +427,11 @@
backendContext.fDeviceFeatures2 = &mPhysicalDeviceFeatures2;
backendContext.fGetProc = sSkiaGetProp;
+ LOG_ALWAYS_FATAL_IF(options.fContextDeleteProc != nullptr, "Conflicting fContextDeleteProcs!");
+ this->incStrong((void*)onGrContextReleased);
+ options.fContextDeleteContext = this;
+ options.fContextDeleteProc = onGrContextReleased;
+
return GrDirectContext::MakeVulkan(backendContext, options);
}
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index c5196ee..00a40c0 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -66,6 +66,7 @@
class VulkanManager final : public RefBase {
public:
static sp<VulkanManager> getInstance();
+ static sp<VulkanManager> peekInstance();
// Sets up the vulkan context that is shared amonst all clients of the VulkanManager. This must
// be call once before use of the VulkanManager. Multiple calls after the first will simiply
@@ -109,7 +110,7 @@
};
// returns a Skia graphic context used to draw content on the specified thread
- sk_sp<GrDirectContext> createContext(const GrContextOptions& options,
+ sk_sp<GrDirectContext> createContext(GrContextOptions& options,
ContextType contextType = ContextType::kRenderThread);
uint32_t getDriverVersion() const { return mDriverVersion; }
diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp
index bffe137..913af8a 100644
--- a/libs/hwui/utils/Color.cpp
+++ b/libs/hwui/utils/Color.cpp
@@ -284,7 +284,9 @@
case HAL_DATASPACE_TRANSFER_GAMMA2_8:
return SkColorSpace::MakeRGB({2.8f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut);
case HAL_DATASPACE_TRANSFER_ST2084:
- return SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, gamut);
+ return SkColorSpace::MakeRGB({-2.0, -1.555223, 1.860454, 32 / 2523.0, 2413 / 128.0,
+ -2392 / 128.0, 8192 / 1305.0},
+ gamut);
case HAL_DATASPACE_TRANSFER_SMPTE_170M:
return SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, gamut);
case HAL_DATASPACE_TRANSFER_UNSPECIFIED:
@@ -427,10 +429,10 @@
}
// Skia skcms' default HLG maps encoded [0, 1] to linear [1, 12] in order to follow ARIB
-// but LinearEffect expects a decoded [0, 1] range instead to follow Rec 2100.
+// but LinearEffect expects to map 1.0 == 203 nits
std::optional<skcms_TransferFunction> GetHLGScaleTransferFunction() {
skcms_TransferFunction hlgFn;
- if (skcms_TransferFunction_makeScaledHLGish(&hlgFn, 1.f / 12.f, 2.f, 2.f, 1.f / 0.17883277f,
+ if (skcms_TransferFunction_makeScaledHLGish(&hlgFn, 0.314509843, 2.f, 2.f, 1.f / 0.17883277f,
0.28466892f, 0.55991073f)) {
return std::make_optional<skcms_TransferFunction>(hlgFn);
}
diff --git a/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
index 6092ac5..82fc33e 100644
--- a/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
@@ -15,8 +15,8 @@
*/
package android.media.soundtrigger_middleware;
-import android.media.soundtrigger.RecognitionEvent;
-import android.media.soundtrigger.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
/**
* Main interface for a client to get notifications of events coming from this module.
@@ -31,7 +31,7 @@
* In case of abortion, the caller may retry after the next onRecognitionAvailabilityChange()
* callback.
*/
- void onRecognition(int modelHandle, in RecognitionEvent event, int captureSession);
+ void onRecognition(int modelHandle, in RecognitionEventSys event, int captureSession);
/**
* Invoked whenever a phrase recognition event is triggered (typically, on recognition, but
* also in case of external aborting of a recognition or a forced recognition event - see the
@@ -39,7 +39,7 @@
* In case of abortion, the caller may retry after the next onRecognitionAvailabilityChange()
* callback.
*/
- void onPhraseRecognition(int modelHandle, in PhraseRecognitionEvent event, int captureSession);
+ void onPhraseRecognition(int modelHandle, in PhraseRecognitionEventSys event, int captureSession);
/**
* Notifies the client that some start/load operations that have previously failed for resource
* reasons (threw a ServiceSpecificException(RESOURCE_CONTENTION) or have been preempted) may
diff --git a/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEventSys.aidl b/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEventSys.aidl
new file mode 100644
index 0000000..6c912ed
--- /dev/null
+++ b/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEventSys.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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 android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger.PhraseRecognitionEvent;
+
+/**
+ * Wrapper to android.media.soundtrigger.RecognitionEvent providing additional fields used by the
+ * framework.
+ */
+parcelable PhraseRecognitionEventSys {
+
+ PhraseRecognitionEvent phraseRecognitionEvent;
+ /**
+ * Timestamp of when the trigger event from SoundTriggerHal was received by the
+ * framework.
+ *
+ * <p>same units and timebase as {@link SystemClock#elapsedRealtime()}.
+ * The value will be -1 if the event was not generated from the HAL.
+ */
+ // @ElapsedRealtimeLong
+ long halEventReceivedMillis = -1;
+}
diff --git a/media/aidl/android/media/soundtrigger_middleware/RecognitionEventSys.aidl b/media/aidl/android/media/soundtrigger_middleware/RecognitionEventSys.aidl
new file mode 100644
index 0000000..84e327d
--- /dev/null
+++ b/media/aidl/android/media/soundtrigger_middleware/RecognitionEventSys.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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 android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger.RecognitionEvent;
+
+/**
+ * Wrapper to android.media.soundtrigger.RecognitionEvent providing additional fields used by the
+ * framework.
+ */
+parcelable RecognitionEventSys {
+
+ RecognitionEvent recognitionEvent;
+ /**
+ * Timestamp of when the trigger event from SoundTriggerHal was received by the
+ * framework.
+ *
+ * <p>same units and timebase as {@link SystemClock#elapsedRealtime()}.
+ * The value will be -1 if the event was not generated from the HAL.
+ */
+ // @ElapsedRealtimeLong
+ long halEventReceivedMillis = -1;
+}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index e73cf87..3123ee6 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1237,6 +1237,9 @@
public static final Set<Integer> DEVICE_IN_ALL_SCO_SET;
/** @hide */
public static final Set<Integer> DEVICE_IN_ALL_USB_SET;
+ /** @hide */
+ public static final Set<Integer> DEVICE_IN_ALL_BLE_SET;
+
static {
DEVICE_IN_ALL_SET = new HashSet<>();
DEVICE_IN_ALL_SET.add(DEVICE_IN_COMMUNICATION);
@@ -1276,6 +1279,66 @@
DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_ACCESSORY);
DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_DEVICE);
DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_HEADSET);
+
+ DEVICE_IN_ALL_BLE_SET = new HashSet<>();
+ DEVICE_IN_ALL_BLE_SET.add(DEVICE_IN_BLE_HEADSET);
+ }
+
+ /** @hide */
+ public static boolean isBluetoothDevice(int deviceType) {
+ return isBluetoothA2dpOutDevice(deviceType)
+ || isBluetoothScoDevice(deviceType)
+ || isBluetoothLeDevice(deviceType);
+ }
+
+ /** @hide */
+ public static boolean isBluetoothOutDevice(int deviceType) {
+ return isBluetoothA2dpOutDevice(deviceType)
+ || isBluetoothScoOutDevice(deviceType)
+ || isBluetoothLeOutDevice(deviceType);
+ }
+
+ /** @hide */
+ public static boolean isBluetoothInDevice(int deviceType) {
+ return isBluetoothScoInDevice(deviceType)
+ || isBluetoothLeInDevice(deviceType);
+ }
+
+ /** @hide */
+ public static boolean isBluetoothA2dpOutDevice(int deviceType) {
+ return DEVICE_OUT_ALL_A2DP_SET.contains(deviceType);
+ }
+
+ /** @hide */
+ public static boolean isBluetoothScoOutDevice(int deviceType) {
+ return DEVICE_OUT_ALL_SCO_SET.contains(deviceType);
+ }
+
+ /** @hide */
+ public static boolean isBluetoothScoInDevice(int deviceType) {
+ return DEVICE_IN_ALL_SCO_SET.contains(deviceType);
+ }
+
+ /** @hide */
+ public static boolean isBluetoothScoDevice(int deviceType) {
+ return isBluetoothScoOutDevice(deviceType)
+ || isBluetoothScoInDevice(deviceType);
+ }
+
+ /** @hide */
+ public static boolean isBluetoothLeOutDevice(int deviceType) {
+ return DEVICE_OUT_ALL_BLE_SET.contains(deviceType);
+ }
+
+ /** @hide */
+ public static boolean isBluetoothLeInDevice(int deviceType) {
+ return DEVICE_IN_ALL_BLE_SET.contains(deviceType);
+ }
+
+ /** @hide */
+ public static boolean isBluetoothLeDevice(int deviceType) {
+ return isBluetoothLeOutDevice(deviceType)
+ || isBluetoothLeInDevice(deviceType);
}
/** @hide */
diff --git a/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java b/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java
index 80bc5c0..3dfc587 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java
@@ -231,9 +231,17 @@
*/
public void setModelCallback(@NonNull @CallbackExecutor Executor executor, @NonNull
ModelCallback callback) {
+ Objects.requireNonNull(callback);
+ Objects.requireNonNull(executor);
synchronized (SoundTriggerInstrumentation.this.mLock) {
- mModelCallback = Objects.requireNonNull(callback);
- mModelExecutor = Objects.requireNonNull(executor);
+ if (mModelCallback == null) {
+ for (var droppedConsumer : mDroppedConsumerList) {
+ executor.execute(() -> droppedConsumer.accept(callback));
+ }
+ mDroppedConsumerList.clear();
+ }
+ mModelCallback = callback;
+ mModelExecutor = executor;
}
}
@@ -267,9 +275,11 @@
private void wrap(Consumer<ModelCallback> consumer) {
synchronized (SoundTriggerInstrumentation.this.mLock) {
- if (mModelCallback != null && mModelExecutor != null) {
+ if (mModelCallback != null) {
final ModelCallback callback = mModelCallback;
mModelExecutor.execute(() -> consumer.accept(callback));
+ } else {
+ mDroppedConsumerList.add(consumer);
}
}
}
@@ -282,6 +292,8 @@
private ModelCallback mModelCallback = null;
@GuardedBy("SoundTriggerInstrumentation.this.mLock")
private Executor mModelExecutor = null;
+ @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+ private final List<Consumer<ModelCallback>> mDroppedConsumerList = new ArrayList<>();
}
/**
@@ -374,9 +386,18 @@
*/
public void setRecognitionCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull RecognitionCallback callback) {
+ Objects.requireNonNull(callback);
+ Objects.requireNonNull(executor);
synchronized (SoundTriggerInstrumentation.this.mLock) {
+ if (mRecognitionCallback == null) {
+ for (var droppedConsumer : mDroppedConsumerList) {
+ executor.execute(() -> droppedConsumer.accept(callback));
+ }
+ mDroppedConsumerList.clear();
+ }
mRecognitionCallback = callback;
mRecognitionExecutor = executor;
+
}
}
@@ -401,9 +422,11 @@
private void wrap(Consumer<RecognitionCallback> consumer) {
synchronized (SoundTriggerInstrumentation.this.mLock) {
- if (mRecognitionCallback != null && mRecognitionExecutor != null) {
+ if (mRecognitionCallback != null) {
final RecognitionCallback callback = mRecognitionCallback;
mRecognitionExecutor.execute(() -> consumer.accept(callback));
+ } else {
+ mDroppedConsumerList.add(consumer);
}
}
}
@@ -416,6 +439,8 @@
private Executor mRecognitionExecutor = null;
@GuardedBy("SoundTriggerInstrumentation.this.mLock")
private RecognitionCallback mRecognitionCallback = null;
+ @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+ private final List<Consumer<RecognitionCallback>> mDroppedConsumerList = new ArrayList<>();
}
// Implementation of injection interface passed to the HAL.
diff --git a/native/android/activity_manager.cpp b/native/android/activity_manager.cpp
index 155a355..bc6a84f 100644
--- a/native/android/activity_manager.cpp
+++ b/native/android/activity_manager.cpp
@@ -45,7 +45,7 @@
void onUidIdle(uid_t uid, bool disabled) override;
void onUidStateChanged(uid_t uid, int32_t procState, int64_t procStateSeq,
int32_t capability) override;
- void onUidProcAdjChanged(uid_t uid) override;
+ void onUidProcAdjChanged(uid_t uid, int32_t adj) override;
// IBinder::DeathRecipient implementation
void binderDied(const wp<IBinder>& who) override;
@@ -121,7 +121,7 @@
void UidObserver::onUidIdle(uid_t uid __unused, bool disabled __unused) {}
-void UidObserver::onUidProcAdjChanged(uid_t uid __unused) {}
+void UidObserver::onUidProcAdjChanged(uid_t uid __unused, int32_t adj __unused) {}
void UidObserver::onUidStateChanged(uid_t uid, int32_t procState,
int64_t procStateSeq __unused,
diff --git a/native/android/input.cpp b/native/android/input.cpp
index 1bff97d..64e8efe 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -87,11 +87,8 @@
const AInputEvent* AKeyEvent_fromJava(JNIEnv* env, jobject keyEvent) {
std::unique_ptr<KeyEvent> event = std::make_unique<KeyEvent>();
- android::status_t ret = android::android_view_KeyEvent_toNative(env, keyEvent, event.get());
- if (ret == android::OK) {
- return event.release();
- }
- return nullptr;
+ *event = android::android_view_KeyEvent_toNative(env, keyEvent);
+ return event.release();
}
int64_t AKeyEvent_getEventTime(const AInputEvent* key_event) {
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
index 5f067e9..b8887390 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -60,6 +60,7 @@
@NonNull private Intent mIntent;
@NonNull private URL mUrl;
@TelephonyManager.PremiumCapability protected int mCapability;
+ private boolean mIsUserTriggeredFinish;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -71,6 +72,7 @@
SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
String url = mIntent.getStringExtra(SlicePurchaseController.EXTRA_PURCHASE_URL);
mApplicationContext = getApplicationContext();
+ mIsUserTriggeredFinish = true;
logd("onCreate: subId=" + subId + ", capability="
+ TelephonyManager.convertPremiumCapabilityToString(mCapability) + ", url=" + url);
@@ -153,12 +155,20 @@
@Override
protected void onDestroy() {
- logd("onDestroy: User canceled the purchase by closing the application.");
- SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
- mIntent, SlicePurchaseController.EXTRA_INTENT_CANCELED);
+ if (mIsUserTriggeredFinish) {
+ logd("onDestroy: User canceled the purchase by closing the application.");
+ SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
+ mIntent, SlicePurchaseController.EXTRA_INTENT_CANCELED);
+ }
super.onDestroy();
}
+ @Override
+ public void finishAndRemoveTask() {
+ mIsUserTriggeredFinish = false;
+ super.finishAndRemoveTask();
+ }
+
private void setupWebView() {
// Create WebView
mWebView = new WebView(this);
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
index 6bfcd82..8c35da1 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 74072e9..2502bbf 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 b86ef64..7ed1816 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
@@ -123,12 +123,11 @@
viewHolder.mExpandButton.setTag(R.drawable.btn_expand_more);
}
- setAccessibility(view, viewType,
- AccessibilityNodeInfo.ACTION_CLICK, R.string.permission_expand);
-
// Add expand buttons if the permissions are more than PERMISSION_SIZE in this list also
// make the summary invisible by default.
if (mPermissions.size() > PERMISSION_SIZE) {
+ setAccessibility(view, viewType,
+ AccessibilityNodeInfo.ACTION_CLICK, R.string.permission_expand, 0);
viewHolder.mPermissionSummary.setVisibility(View.GONE);
@@ -137,18 +136,16 @@
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 +197,20 @@
}
}
- 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/AndroidManifest.xml b/packages/CredentialManager/AndroidManifest.xml
index 8724d69..4161601 100644
--- a/packages/CredentialManager/AndroidManifest.xml
+++ b/packages/CredentialManager/AndroidManifest.xml
@@ -42,15 +42,6 @@
android:excludeFromRecents="true"
android:theme="@style/Theme.CredentialSelector">
</activity>
-
- <receiver
- android:name=".CredentialProviderReceiver"
- android:exported="true"
- android:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR">
- <intent-filter>
- <action android:name="android.credentials.ui.action.CREDMAN_ENABLED_PROVIDERS_UPDATED"/>
- </intent-filter>
- </receiver>
</application>
</manifest>
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index a3b2752..6df0778 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -24,8 +24,8 @@
<string name="string_cancel">Cancel</string>
<!-- This is a label for a button that takes user to the next screen. [CHAR LIMIT=20] -->
<string name="string_continue">Continue</string>
- <!-- This is a label for a button that links to different places where the user can save their passkeys. [CHAR LIMIT=20] -->
- <string name="string_more_options">More options</string>
+ <!-- This is a label for a button that leads to a holistic view of all different options where the user can save their new app credential. [CHAR LIMIT=20] -->
+ <string name="string_more_options">Save another way</string>
<!-- This is a label for a button that links to additional information about passkeys. [CHAR LIMIT=20] -->
<string name="string_learn_more">Learn more</string>
<!-- This is a label for content description for show password icon button. -->
@@ -78,14 +78,20 @@
<!-- This text is followed by a list of one or more options. [CHAR LIMIT=200] -->
<string name="save_credential_to_title">Save <xliff:g id="credentialTypes" example="passkey">%1$s</xliff:g> to</string>
- <!-- This appears as the title of the modal bottom sheet for users to choose to create a passkey on another device. [CHAR LIMIT=200] -->
- <string name="create_passkey_in_other_device_title">Create passkey in another device?</string>
+ <!-- This appears as the title of the modal bottom sheet for users to confirm to create a passkey on another device. [CHAR LIMIT=200] -->
+ <string name="create_passkey_in_other_device_title">Create passkey on another device?</string>
+ <!-- This appears as the title of the modal bottom sheet for users to confirm to save a password on another device. [CHAR LIMIT=200] -->
+ <string name="save_password_on_other_device_title">Save password on another device?</string>
+ <!-- This appears as the title of the modal bottom sheet for users to confirm to save a sign-in credential on another device. [CHAR LIMIT=200] -->
+ <string name="save_sign_in_on_other_device_title">Save sign-in on another device?</string>
<!-- This appears as the title of the modal bottom sheet for users to confirm whether they should use the selected provider as default or not. [CHAR LIMIT=200] -->
<string name="use_provider_for_all_title">Use <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g> for all your sign-ins?</string>
<!-- This appears as the description body of the modal bottom sheet for users to confirm whether they should use the selected provider as default or not. [CHAR LIMIT=300] -->
<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] -->
@@ -114,6 +120,8 @@
<!-- Strings for the get flow. -->
<!-- This appears as the title of the modal bottom sheet asking for user confirmation to use the single previously saved passkey to sign in to the app. [CHAR LIMIT=200] -->
<string name="get_dialog_title_use_passkey_for">Use your saved passkey for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
+ <!-- This appears as the title of the modal bottom sheet asking for user confirmation to use the single previously saved password to sign in to the app. [CHAR LIMIT=200] -->
+ <string name="get_dialog_title_use_password_for">Use your saved password for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
<!-- This appears as the title of the dialog asking for user confirmation to use the single user credential (previously saved or to be created) to sign in to the app. [CHAR LIMIT=200] -->
<string name="get_dialog_title_use_sign_in_for">Use your sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
<!-- This appears as the title of the dialog asking for user to make a choice from various available user credentials (previously saved or to be created) to sign in to the app. [CHAR LIMIT=200] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index a9bee03..693e767 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -107,7 +107,6 @@
initialUiState = when (requestInfo?.type) {
RequestInfo.TYPE_CREATE -> {
- val defaultProviderIdSetByUser = userConfigRepo.getDefaultProviderId()
val isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
val providerEnableListUiState = getCreateProviderEnableListInitialUiState()
val providerDisableListUiState = getCreateProviderDisableListInitialUiState()
@@ -119,7 +118,8 @@
disabledProviders = providerDisableListUiState,
defaultProviderIdPreferredByApp =
requestDisplayInfoUiState.appPreferredDefaultProviderId,
- defaultProviderIdSetByUser = defaultProviderIdSetByUser,
+ defaultProviderIdsSetByUser =
+ requestDisplayInfoUiState.userSetDefaultProviderIds,
requestDisplayInfo = requestDisplayInfoUiState,
isOnPasskeyIntroStateAlready = false,
isPasskeyFirstUse = isPasskeyFirstUse,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt
deleted file mode 100644
index ee8cffe..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialProviderReceiver.kt
+++ /dev/null
@@ -1,35 +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.credentialmanager
-
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.util.Log
-import com.android.credentialmanager.common.Constants
-
-
-class CredentialProviderReceiver : BroadcastReceiver() {
-
- override fun onReceive(context: Context?, intent: Intent?) {
- Log.d(Constants.LOG_TAG, "Received intent in CredentialProviderReceiver")
-
- val sharedPreferences = context?.getSharedPreferences(context?.packageName,
- Context.MODE_PRIVATE)
- sharedPreferences?.edit()?.remove(UserConfigRepo.DEFAULT_PROVIDER)?.commit()
- }
-}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 6549b2d..54a8678d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -25,6 +25,7 @@
import android.os.ResultReceiver
import android.util.Log
import androidx.activity.ComponentActivity
+import androidx.activity.OnBackPressedCallback
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.viewModels
@@ -48,9 +49,12 @@
class CredentialSelectorActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ Log.d(Constants.LOG_TAG, "Creating new CredentialSelectorActivity")
overrideActivityTransition(Activity.OVERRIDE_TRANSITION_OPEN,
0, 0)
- Log.d(Constants.LOG_TAG, "Creating new CredentialSelectorActivity")
+ overrideActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE,
+ 0, 0)
+
try {
val (isCancellationRequest, shouldShowCancellationUi, _) =
maybeCancelUIUponRequest(intent)
@@ -59,6 +63,18 @@
}
val userConfigRepo = UserConfigRepo(this)
val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo)
+
+ val backPressedCallback = object : OnBackPressedCallback(
+ true // default to enabled
+ ) {
+ override fun handleOnBackPressed() {
+ credManRepo.onUserCancel()
+ Log.d(Constants.LOG_TAG, "Activity back triggered: finish the activity.")
+ this@CredentialSelectorActivity.finish()
+ }
+ }
+ onBackPressedDispatcher.addCallback(this, backPressedCallback)
+
setContent {
PlatformTheme {
CredentialManagerBottomSheet(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 8b74d76..de67989 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -255,7 +255,8 @@
disabledProviders = prevUiState.disabledProviders,
defaultProviderIdPreferredByApp =
prevUiState.requestDisplayInfo.appPreferredDefaultProviderId,
- defaultProviderIdSetByUser = userConfigRepo.getDefaultProviderId(),
+ defaultProviderIdsSetByUser =
+ prevUiState.requestDisplayInfo.userSetDefaultProviderIds,
requestDisplayInfo = prevUiState.requestDisplayInfo,
isOnPasskeyIntroStateAlready = true,
isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
@@ -269,28 +270,10 @@
userConfigRepo.setIsPasskeyFirstUse(false)
}
- fun createFlowOnMoreOptionsSelectedOnProviderSelection() {
- uiState = uiState.copy(
- createCredentialUiState = uiState.createCredentialUiState?.copy(
- currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION,
- isFromProviderSelection = true
- )
- )
- }
-
fun createFlowOnMoreOptionsSelectedOnCreationSelection() {
uiState = uiState.copy(
createCredentialUiState = uiState.createCredentialUiState?.copy(
currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION,
- isFromProviderSelection = false
- )
- )
- }
-
- fun createFlowOnBackProviderSelectionButtonSelected() {
- uiState = uiState.copy(
- createCredentialUiState = uiState.createCredentialUiState?.copy(
- currentScreenState = CreateScreenState.PROVIDER_SELECTION,
)
)
}
@@ -315,7 +298,10 @@
uiState = uiState.copy(
createCredentialUiState = uiState.createCredentialUiState?.copy(
currentScreenState =
- if (activeEntry.activeProvider.id == userConfigRepo.getDefaultProviderId() ||
+ if (uiState.createCredentialUiState?.requestDisplayInfo?.userSetDefaultProviderIds
+ ?.contains(activeEntry.activeProvider.id) ?: true ||
+ !(uiState.createCredentialUiState?.foundCandidateFromUserDefaultProvider
+ ?: false) ||
!TextUtils.isEmpty(uiState.createCredentialUiState?.requestDisplayInfo
?.appPreferredDefaultProviderId))
CreateScreenState.CREATION_OPTION_SELECTION
@@ -325,18 +311,7 @@
)
}
- fun createFlowOnEntrySelectedFromFirstUseScreen(activeEntry: ActiveEntry) {
- val providerId = activeEntry.activeProvider.id
- createFlowOnDefaultChanged(providerId)
- uiState = uiState.copy(
- createCredentialUiState = uiState.createCredentialUiState?.copy(
- currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
- activeEntry = activeEntry
- )
- )
- }
-
- fun createFlowOnDisabledProvidersSelected() {
+ fun createFlowOnLaunchSettings() {
credManRepo.onSettingLaunchCancel()
uiState = uiState.copy(dialogState = DialogState.CANCELED_FOR_SETTINGS)
}
@@ -349,16 +324,6 @@
)
}
- fun createFlowOnChangeDefaultSelected() {
- uiState = uiState.copy(
- createCredentialUiState = uiState.createCredentialUiState?.copy(
- currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
- )
- )
- val providerId = uiState.createCredentialUiState?.activeEntry?.activeProvider?.id
- createFlowOnDefaultChanged(providerId)
- }
-
fun createFlowOnUseOnceSelected() {
uiState = uiState.copy(
createCredentialUiState = uiState.createCredentialUiState?.copy(
@@ -367,17 +332,6 @@
)
}
- fun createFlowOnDefaultChanged(providerId: String?) {
- if (providerId != null) {
- Log.d(
- Constants.LOG_TAG, "Default provider changed to: " +
- " {provider=$providerId")
- userConfigRepo.setDefaultProvider(providerId)
- } else {
- Log.w(Constants.LOG_TAG, "Null provider is being changed")
- }
- }
-
fun createFlowOnEntrySelected(selectedEntry: BaseEntry) {
val providerId = selectedEntry.providerId
val entryKey = selectedEntry.entryKey
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 57035d4..a35310c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -61,6 +61,7 @@
import androidx.credentials.provider.PublicKeyCredentialEntry
import androidx.credentials.provider.RemoteEntry
import org.json.JSONObject
+import java.time.Instant
// TODO: remove all !! checks
fun getAppLabel(
@@ -420,7 +421,7 @@
id = it.providerFlattenedComponentName,
displayName = providerLabel,
icon = providerIcon,
- createOptions = toCreationOptionInfoList(
+ sortedCreateOptions = toSortedCreationOptionInfoList(
it.providerFlattenedComponentName, it.saveEntries, context
),
remoteEntry = toRemoteInfo(it.providerFlattenedComponentName, it.remoteEntry),
@@ -481,8 +482,10 @@
CredentialType.PASSWORD,
appLabel,
context.getDrawable(R.drawable.ic_password_24) ?: return null,
- preferImmediatelyAvailableCredentials = false,
+ preferImmediatelyAvailableCredentials =
+ createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
appPreferredDefaultProviderId = appPreferredDefaultProviderId,
+ userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(),
)
is CreatePublicKeyCredentialRequest -> {
newRequestDisplayInfoFromPasskeyJson(
@@ -492,6 +495,7 @@
preferImmediatelyAvailableCredentials =
createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
appPreferredDefaultProviderId = appPreferredDefaultProviderId,
+ userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(),
)
}
is CreateCustomCredentialRequest -> {
@@ -506,8 +510,10 @@
appName = appLabel,
typeIcon = displayInfo.credentialTypeIcon?.loadDrawable(context)
?: context.getDrawable(R.drawable.ic_other_sign_in_24) ?: return null,
- preferImmediatelyAvailableCredentials = false,
+ preferImmediatelyAvailableCredentials =
+ createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
appPreferredDefaultProviderId = appPreferredDefaultProviderId,
+ userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(),
)
}
else -> null
@@ -518,13 +524,13 @@
enabledProviders: List<EnabledProviderInfo>,
disabledProviders: List<DisabledProviderInfo>?,
defaultProviderIdPreferredByApp: String?,
- defaultProviderIdSetByUser: String?,
+ defaultProviderIdsSetByUser: Set<String>,
requestDisplayInfo: RequestDisplayInfo,
isOnPasskeyIntroStateAlready: Boolean,
isPasskeyFirstUse: Boolean,
): CreateCredentialUiState? {
- var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
var remoteEntry: RemoteInfo? = null
+ var remoteEntryProvider: EnabledProviderInfo? = null
var defaultProviderPreferredByApp: EnabledProviderInfo? = null
var defaultProviderSetByUser: EnabledProviderInfo? = null
var createOptionsPairs:
@@ -535,14 +541,24 @@
defaultProviderPreferredByApp = enabledProvider
}
}
- if (defaultProviderIdSetByUser != null) {
- if (enabledProvider.id == defaultProviderIdSetByUser) {
+ if (enabledProvider.sortedCreateOptions.isNotEmpty() &&
+ defaultProviderIdsSetByUser.contains(enabledProvider.id)) {
+ if (defaultProviderSetByUser == null) {
defaultProviderSetByUser = enabledProvider
+ } else {
+ val newLastUsedTime = enabledProvider.sortedCreateOptions.firstOrNull()
+ ?.lastUsedTime
+ val curLastUsedTime = defaultProviderSetByUser?.sortedCreateOptions
+ ?.firstOrNull()?.lastUsedTime ?: Instant.MIN
+ if (newLastUsedTime != null) {
+ if (curLastUsedTime == null || newLastUsedTime > curLastUsedTime) {
+ defaultProviderSetByUser = enabledProvider
+ }
+ }
}
}
- if (enabledProvider.createOptions.isNotEmpty()) {
- lastSeenProviderWithNonEmptyCreateOptions = enabledProvider
- enabledProvider.createOptions.forEach {
+ if (enabledProvider.sortedCreateOptions.isNotEmpty()) {
+ enabledProvider.sortedCreateOptions.forEach {
createOptionsPairs.add(Pair(it, enabledProvider))
}
}
@@ -554,6 +570,7 @@
return null
}
remoteEntry = currRemoteEntry
+ remoteEntryProvider = enabledProvider
}
}
val defaultProvider = defaultProviderPreferredByApp ?: defaultProviderSetByUser
@@ -561,27 +578,26 @@
createOptionSize = createOptionsPairs.size,
isOnPasskeyIntroStateAlready = isOnPasskeyIntroStateAlready,
requestDisplayInfo = requestDisplayInfo,
- defaultProvider = defaultProvider,
remoteEntry = remoteEntry,
isPasskeyFirstUse = isPasskeyFirstUse
) ?: return null
+ val sortedCreateOptionsPairs = createOptionsPairs.sortedWith(
+ compareByDescending { it.first.lastUsedTime }
+ )
return CreateCredentialUiState(
enabledProviders = enabledProviders,
disabledProviders = disabledProviders,
currentScreenState = initialScreenState,
requestDisplayInfo = requestDisplayInfo,
- sortedCreateOptionsPairs = createOptionsPairs.sortedWith(
- compareByDescending { it.first.lastUsedTime }
- ),
- hasDefaultProvider = defaultProvider != null,
+ sortedCreateOptionsPairs = sortedCreateOptionsPairs,
activeEntry = toActiveEntry(
- /*defaultProvider=*/defaultProvider,
- /*createOptionSize=*/createOptionsPairs.size,
- /*lastSeenProviderWithNonEmptyCreateOptions=*/
- lastSeenProviderWithNonEmptyCreateOptions,
- /*remoteEntry=*/remoteEntry
+ defaultProvider = defaultProvider,
+ sortedCreateOptionsPairs = sortedCreateOptionsPairs,
+ remoteEntry = remoteEntry,
+ remoteEntryProvider = remoteEntryProvider,
),
remoteEntry = remoteEntry,
+ foundCandidateFromUserDefaultProvider = defaultProviderSetByUser != null,
)
}
@@ -589,59 +605,43 @@
createOptionSize: Int,
isOnPasskeyIntroStateAlready: Boolean,
requestDisplayInfo: RequestDisplayInfo,
- defaultProvider: EnabledProviderInfo?,
remoteEntry: RemoteInfo?,
isPasskeyFirstUse: Boolean,
): CreateScreenState? {
- return if (isPasskeyFirstUse && requestDisplayInfo.type ==
- CredentialType.PASSKEY && !isOnPasskeyIntroStateAlready) {
+ return if (isPasskeyFirstUse && requestDisplayInfo.type == CredentialType.PASSKEY &&
+ !isOnPasskeyIntroStateAlready) {
CreateScreenState.PASSKEY_INTRO
- } else if ((defaultProvider == null || defaultProvider.createOptions.isEmpty()) &&
- createOptionSize > 1) {
- CreateScreenState.PROVIDER_SELECTION
- } else if (((defaultProvider == null || defaultProvider.createOptions.isEmpty()) &&
- createOptionSize == 1) || (defaultProvider != null &&
- defaultProvider.createOptions.isNotEmpty())) {
- CreateScreenState.CREATION_OPTION_SELECTION
} else if (createOptionSize == 0 && remoteEntry != null) {
CreateScreenState.EXTERNAL_ONLY_SELECTION
} else {
- Log.d(
- Constants.LOG_TAG,
- "Unexpected failure: the screen state failed to instantiate" +
- " because the provider list is empty."
- )
- null
+ CreateScreenState.CREATION_OPTION_SELECTION
}
}
private fun toActiveEntry(
defaultProvider: EnabledProviderInfo?,
- createOptionSize: Int,
- lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo?,
+ sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
remoteEntry: RemoteInfo?,
+ remoteEntryProvider: EnabledProviderInfo?,
): ActiveEntry? {
return if (
- defaultProvider != null && defaultProvider.createOptions.isEmpty() &&
- remoteEntry != null
+ sortedCreateOptionsPairs.isEmpty() && remoteEntry != null &&
+ remoteEntryProvider != null
) {
- ActiveEntry(defaultProvider, remoteEntry)
- } else if (
- defaultProvider != null && defaultProvider.createOptions.isNotEmpty()
- ) {
- ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
- } else if (createOptionSize == 1) {
- ActiveEntry(
- lastSeenProviderWithNonEmptyCreateOptions!!,
- lastSeenProviderWithNonEmptyCreateOptions.createOptions.first()
- )
+ ActiveEntry(remoteEntryProvider, remoteEntry)
+ } else if (defaultProvider != null &&
+ defaultProvider.sortedCreateOptions.isNotEmpty()) {
+ ActiveEntry(defaultProvider, defaultProvider.sortedCreateOptions.first())
+ } else if (sortedCreateOptionsPairs.isNotEmpty()) {
+ val (topEntry, topEntryProvider) = sortedCreateOptionsPairs.first()
+ ActiveEntry(topEntryProvider, topEntry)
} else null
}
/**
* Note: caller required handle empty list due to parsing error.
*/
- private fun toCreationOptionInfoList(
+ private fun toSortedCreationOptionInfoList(
providerId: String,
creationEntries: List<Entry>,
context: Context,
@@ -664,7 +664,9 @@
footerDescription = createEntry.description?.toString()
))
}
- return result
+ return result.sortedWith(
+ compareByDescending { it.lastUsedTime }
+ )
}
private fun toRemoteInfo(
@@ -690,6 +692,7 @@
context: Context,
preferImmediatelyAvailableCredentials: Boolean,
appPreferredDefaultProviderId: String?,
+ userSetDefaultProviderIds: Set<String>,
): RequestDisplayInfo? {
val json = JSONObject(requestJson)
var passkeyUsername = ""
@@ -711,6 +714,7 @@
context.getDrawable(R.drawable.ic_passkey_24) ?: return null,
preferImmediatelyAvailableCredentials,
appPreferredDefaultProviderId,
+ userSetDefaultProviderIds,
)
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
index a17f2c8..bfcca49 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
@@ -23,15 +23,6 @@
val sharedPreferences: SharedPreferences = context.getSharedPreferences(
context.packageName, Context.MODE_PRIVATE)
- fun setDefaultProvider(
- providerId: String
- ) {
- sharedPreferences.edit().apply {
- putString(DEFAULT_PROVIDER, providerId)
- apply()
- }
- }
-
fun setIsPasskeyFirstUse(
isFirstUse: Boolean
) {
@@ -41,16 +32,11 @@
}
}
- fun getDefaultProviderId(): String? {
- return sharedPreferences.getString(DEFAULT_PROVIDER, null)
- }
-
fun getIsPasskeyFirstUse(): Boolean {
return sharedPreferences.getBoolean(IS_PASSKEY_FIRST_USE, true)
}
companion object {
- const val DEFAULT_PROVIDER = "default_provider"
// This first use value only applies to passkeys, not related with if generally
// credential manager is first use or not
const val IS_PASSKEY_FIRST_USE = "is_passkey_first_use"
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 9d871ed..d16120f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -88,27 +88,11 @@
onLearnMore = viewModel::createFlowOnLearnMore,
onLog = { viewModel.logUiEvent(it) },
)
- CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
- requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
- disabledProviderList = createCredentialUiState
- .disabledProviders,
- sortedCreateOptionsPairs =
- createCredentialUiState.sortedCreateOptionsPairs,
- hasRemoteEntry = createCredentialUiState.remoteEntry != null,
- onOptionSelected =
- viewModel::createFlowOnEntrySelectedFromFirstUseScreen,
- onDisabledProvidersSelected =
- viewModel::createFlowOnDisabledProvidersSelected,
- onMoreOptionsSelected =
- viewModel::createFlowOnMoreOptionsSelectedOnProviderSelection,
- onLog = { viewModel.logUiEvent(it) },
- )
CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
enabledProviderList = createCredentialUiState.enabledProviders,
providerInfo = createCredentialUiState
.activeEntry?.activeProvider!!,
- hasDefaultProvider = createCredentialUiState.hasDefaultProvider,
createOptionInfo =
createCredentialUiState.activeEntry.activeEntryInfo
as CreateOptionInfo,
@@ -121,21 +105,15 @@
CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
enabledProviderList = createCredentialUiState.enabledProviders,
- disabledProviderList = createCredentialUiState
- .disabledProviders,
+ disabledProviderList = createCredentialUiState.disabledProviders,
sortedCreateOptionsPairs =
createCredentialUiState.sortedCreateOptionsPairs,
- hasDefaultProvider = createCredentialUiState.hasDefaultProvider,
- isFromProviderSelection =
- createCredentialUiState.isFromProviderSelection!!,
- onBackProviderSelectionButtonSelected =
- viewModel::createFlowOnBackProviderSelectionButtonSelected,
onBackCreationSelectionButtonSelected =
viewModel::createFlowOnBackCreationSelectionButtonSelected,
onOptionSelected =
viewModel::createFlowOnEntrySelectedFromMoreOptionScreen,
onDisabledProvidersSelected =
- viewModel::createFlowOnDisabledProvidersSelected,
+ viewModel::createFlowOnLaunchSettings,
onRemoteEntrySelected = viewModel::createFlowOnEntrySelected,
onLog = { viewModel.logUiEvent(it) },
)
@@ -144,11 +122,11 @@
viewModel.onIllegalUiState("Expect active entry to be non-null" +
" upon default provider dialog.")
} else {
- DefaultProviderConfirmationCard(
+ NonDefaultUsageConfirmationCard(
selectedEntry = createCredentialUiState.activeEntry,
onIllegalScreenState = viewModel::onIllegalUiState,
- onChangeDefaultSelected =
- viewModel::createFlowOnChangeDefaultSelected,
+ onLaunchSettings =
+ viewModel::createFlowOnLaunchSettings,
onUseOnceSelected = viewModel::createFlowOnUseOnceSelected,
onLog = { viewModel.logUiEvent(it) },
)
@@ -263,94 +241,11 @@
}
@Composable
-fun ProviderSelectionCard(
- requestDisplayInfo: RequestDisplayInfo,
- disabledProviderList: List<DisabledProviderInfo>?,
- sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
- hasRemoteEntry: Boolean,
- onOptionSelected: (ActiveEntry) -> Unit,
- onDisabledProvidersSelected: () -> Unit,
- onMoreOptionsSelected: () -> Unit,
- onLog: @Composable (UiEventEnum) -> Unit,
-) {
- SheetContainerCard {
- item { HeadlineIcon(bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()) }
- item { Divider(thickness = 16.dp, color = Color.Transparent) }
- item {
- HeadlineText(
- text = stringResource(
- R.string.choose_provider_title,
- when (requestDisplayInfo.type) {
- CredentialType.PASSKEY ->
- stringResource(R.string.passkeys)
- CredentialType.PASSWORD ->
- stringResource(R.string.passwords)
- CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info)
- }
- )
- )
- }
- item { Divider(thickness = 24.dp, color = Color.Transparent) }
-
- item {
- Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
- BodyMediumText(text = stringResource(R.string.choose_provider_body))
- }
- }
- item { Divider(thickness = 16.dp, color = Color.Transparent) }
- item {
- CredentialContainerCard {
- Column(
- verticalArrangement = Arrangement.spacedBy(2.dp)
- ) {
- sortedCreateOptionsPairs.forEach { entry ->
- MoreOptionsInfoRow(
- requestDisplayInfo = requestDisplayInfo,
- providerInfo = entry.second,
- createOptionInfo = entry.first,
- onOptionSelected = {
- onOptionSelected(
- ActiveEntry(
- entry.second,
- entry.first
- )
- )
- }
- )
- }
- MoreOptionsDisabledProvidersRow(
- disabledProviders = disabledProviderList,
- onDisabledProvidersSelected = onDisabledProvidersSelected,
- )
- }
- }
- }
- if (hasRemoteEntry) {
- item { Divider(thickness = 24.dp, color = Color.Transparent) }
- item {
- CtaButtonRow(
- leftButton = {
- ActionButton(
- stringResource(R.string.string_more_options),
- onMoreOptionsSelected
- )
- }
- )
- }
- }
- }
- onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_PROVIDER_SELECTION)
-}
-
-@Composable
fun MoreOptionsSelectionCard(
requestDisplayInfo: RequestDisplayInfo,
enabledProviderList: List<EnabledProviderInfo>,
disabledProviderList: List<DisabledProviderInfo>?,
sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
- hasDefaultProvider: Boolean,
- isFromProviderSelection: Boolean,
- onBackProviderSelectionButtonSelected: () -> Unit,
onBackCreationSelectionButtonSelected: () -> Unit,
onOptionSelected: (ActiveEntry) -> Unit,
onDisabledProvidersSelected: () -> Unit,
@@ -369,9 +264,7 @@
CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info)
}
),
- onNavigationIconClicked =
- if (isFromProviderSelection) onBackProviderSelectionButtonSelected
- else onBackCreationSelectionButtonSelected,
+ onNavigationIconClicked = onBackCreationSelectionButtonSelected,
bottomPadding = 16.dp,
)
}) {
@@ -379,30 +272,26 @@
item {
CredentialContainerCard {
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
- // Only in the flows with default provider(not first time use) we can show the
- // createOptions here, or they will be shown on ProviderSelectionCard
- if (hasDefaultProvider) {
- sortedCreateOptionsPairs.forEach { entry ->
- MoreOptionsInfoRow(
- requestDisplayInfo = requestDisplayInfo,
- providerInfo = entry.second,
- createOptionInfo = entry.first,
- onOptionSelected = {
- onOptionSelected(
- ActiveEntry(
- entry.second,
- entry.first
- )
+ sortedCreateOptionsPairs.forEach { entry ->
+ MoreOptionsInfoRow(
+ requestDisplayInfo = requestDisplayInfo,
+ providerInfo = entry.second,
+ createOptionInfo = entry.first,
+ onOptionSelected = {
+ onOptionSelected(
+ ActiveEntry(
+ entry.second,
+ entry.first
)
- }
- )
- }
- MoreOptionsDisabledProvidersRow(
- disabledProviders = disabledProviderList,
- onDisabledProvidersSelected =
- onDisabledProvidersSelected,
+ )
+ }
)
}
+ MoreOptionsDisabledProvidersRow(
+ disabledProviders = disabledProviderList,
+ onDisabledProvidersSelected =
+ onDisabledProvidersSelected,
+ )
enabledProviderList.forEach {
if (it.remoteEntry != null) {
RemoteEntryRow(
@@ -420,10 +309,10 @@
}
@Composable
-fun DefaultProviderConfirmationCard(
+fun NonDefaultUsageConfirmationCard(
selectedEntry: ActiveEntry,
onIllegalScreenState: (String) -> Unit,
- onChangeDefaultSelected: () -> Unit,
+ onLaunchSettings: () -> Unit,
onUseOnceSelected: () -> Unit,
onLog: @Composable (UiEventEnum) -> Unit,
) {
@@ -454,14 +343,14 @@
CtaButtonRow(
leftButton = {
ActionButton(
- stringResource(R.string.use_once),
- onClick = onUseOnceSelected
+ stringResource(R.string.settings),
+ onClick = onLaunchSettings,
)
},
rightButton = {
ConfirmButton(
- stringResource(R.string.set_as_default),
- onClick = onChangeDefaultSelected
+ stringResource(R.string.use_once),
+ onClick = onUseOnceSelected,
)
},
)
@@ -479,7 +368,6 @@
onOptionSelected: (BaseEntry) -> Unit,
onConfirm: () -> Unit,
onMoreOptionsSelected: () -> Unit,
- hasDefaultProvider: Boolean,
onLog: @Composable (UiEventEnum) -> Unit,
) {
SheetContainerCard {
@@ -527,16 +415,9 @@
if (enabledProvider.remoteEntry != null) {
remoteEntry = enabledProvider.remoteEntry
}
- createOptionsSize += enabledProvider.createOptions.size
+ createOptionsSize += enabledProvider.sortedCreateOptions.size
}
- val shouldShowMoreOptionsButton = if (!hasDefaultProvider) {
- // User has already been presented with all options on the default provider
- // selection screen. Don't show them again. Therefore, only show the more option
- // button if remote option is present.
- remoteEntry != null
- } else {
- createOptionsSize > 1 || remoteEntry != null
- }
+ val shouldShowMoreOptionsButton = createOptionsSize > 1 || remoteEntry != null
item {
CtaButtonRow(
leftButton = if (shouldShowMoreOptionsButton) {
@@ -584,7 +465,17 @@
SheetContainerCard {
item { HeadlineIcon(imageVector = Icons.Outlined.QrCodeScanner) }
item { Divider(thickness = 16.dp, color = Color.Transparent) }
- item { HeadlineText(text = stringResource(R.string.create_passkey_in_other_device_title)) }
+ item {
+ HeadlineText(
+ text = stringResource(
+ when (requestDisplayInfo.type) {
+ CredentialType.PASSKEY -> R.string.create_passkey_in_other_device_title
+ CredentialType.PASSWORD -> R.string.save_password_on_other_device_title
+ else -> R.string.save_sign_in_on_other_device_title
+ }
+ )
+ )
+ }
item { Divider(thickness = 24.dp, color = Color.Transparent) }
item {
CredentialContainerCard {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 225dbf2..fe1ce1b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -29,12 +29,9 @@
val currentScreenState: CreateScreenState,
val requestDisplayInfo: RequestDisplayInfo,
val sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
- // Should not change with the real time update of default provider, only determine whether
- // we're showing provider selection page at the beginning
- val hasDefaultProvider: Boolean,
val activeEntry: ActiveEntry? = null,
val remoteEntry: RemoteInfo? = null,
- val isFromProviderSelection: Boolean? = null,
+ val foundCandidateFromUserDefaultProvider: Boolean,
)
internal fun hasContentToDisplay(state: CreateCredentialUiState): Boolean {
@@ -50,11 +47,12 @@
)
class EnabledProviderInfo(
- icon: Drawable,
- id: String,
- displayName: String,
- var createOptions: List<CreateOptionInfo>,
- var remoteEntry: RemoteInfo?,
+ icon: Drawable,
+ id: String,
+ displayName: String,
+ // Sorted by last used time
+ var sortedCreateOptions: List<CreateOptionInfo>,
+ var remoteEntry: RemoteInfo?,
) : ProviderInfo(icon, id, displayName)
class DisabledProviderInfo(
@@ -108,6 +106,7 @@
val typeIcon: Drawable,
val preferImmediatelyAvailableCredentials: Boolean,
val appPreferredDefaultProviderId: String?,
+ val userSetDefaultProviderIds: Set<String>,
)
/**
@@ -123,7 +122,6 @@
enum class CreateScreenState {
PASSKEY_INTRO,
MORE_ABOUT_PASSKEYS_INTRO,
- PROVIDER_SELECTION,
CREATION_OPTION_SELECTION,
MORE_OPTIONS_SELECTION,
DEFAULT_PROVIDER_CONFIRMATION,
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
index 4c5875b..7b17cbd 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
@@ -16,14 +16,14 @@
package com.android.packageinstaller;
+import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
+
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.Nullable;
-import java.io.File;
-
/**
* Trampoline activity. Calls PackageInstallerActivity and deletes staged install file onResult.
*/
@@ -52,8 +52,13 @@
super.onDestroy();
if (isFinishing()) {
- File sourceFile = new File(getIntent().getData().getPath());
- new Thread(sourceFile::delete).start();
+ // While we expect PIA/InstallStaging to abandon/commit the session, still there
+ // might be cases when the session becomes orphan.
+ int sessionId = getIntent().getIntExtra(EXTRA_STAGED_SESSION_ID, 0);
+ try {
+ getPackageManager().getPackageInstaller().abandonSession(sessionId);
+ } catch (SecurityException ignored) {
+ }
}
}
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
index c6217ec..7bea339 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -16,17 +16,17 @@
package com.android.packageinstaller;
+import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
+
import android.app.PendingIntent;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.InstallInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
-import android.os.Process;
import android.util.Log;
import android.view.View;
import android.widget.Button;
@@ -34,10 +34,7 @@
import androidx.annotation.Nullable;
import java.io.File;
-import java.io.FileInputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
/**
* Send package to the package manager and handle results from package manager. Once the
@@ -77,7 +74,7 @@
.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
mPackageURI = getIntent().getData();
- if ("package".equals(mPackageURI.getScheme())) {
+ if (PackageInstallerActivity.SCHEME_PACKAGE.equals(mPackageURI.getScheme())) {
try {
getPackageManager().installExistingPackage(appInfo.packageName);
launchSuccess();
@@ -86,6 +83,8 @@
PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
}
} else {
+ // ContentResolver.SCHEME_FILE
+ // STAGED_SESSION_ID extra contains an ID of a previously staged install session.
final File sourceFile = new File(mPackageURI.getPath());
PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, appInfo, sourceFile);
@@ -122,41 +121,6 @@
// Does not happen
}
} else {
- PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
- PackageInstaller.SessionParams.MODE_FULL_INSTALL);
- final Uri referrerUri = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER);
- params.setPackageSource(
- referrerUri != null ? PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
- : PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE);
- params.setInstallAsInstantApp(false);
- params.setReferrerUri(referrerUri);
- params.setOriginatingUri(getIntent()
- .getParcelableExtra(Intent.EXTRA_ORIGINATING_URI));
- params.setOriginatingUid(getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
- Process.INVALID_UID));
- params.setInstallerPackageName(getIntent().getStringExtra(
- Intent.EXTRA_INSTALLER_PACKAGE_NAME));
- params.setInstallReason(PackageManager.INSTALL_REASON_USER);
-
- File file = new File(mPackageURI.getPath());
- try {
- final InstallInfo result = getPackageManager().getPackageInstaller()
- .readInstallInfo(file, 0);
- params.setAppPackageName(result.getPackageName());
- params.setInstallLocation(result.getInstallLocation());
- try {
- params.setSize(result.calculateInstalledSize(params));
- } catch (IOException e) {
- e.printStackTrace();
- params.setSize(file.length());
- }
- } catch (PackageInstaller.PackageParsingException e) {
-
- Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.", e);
- Log.e(LOG_TAG,
- "Cannot calculate installed size " + file + ". Try only apk size.");
- params.setSize(file.length());
- }
try {
mInstallId = InstallEventReceiver
.addObserver(this, EventResultPersister.GENERATE_NEW_ID,
@@ -166,9 +130,14 @@
PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
}
- try {
- mSessionId = getPackageManager().getPackageInstaller().createSession(params);
- } catch (IOException e) {
+ mSessionId = getIntent().getIntExtra(EXTRA_STAGED_SESSION_ID, 0);
+ // Try to open session previously staged in InstallStaging.
+ try (PackageInstaller.Session ignored =
+ getPackageManager().getPackageInstaller().openSession(
+ mSessionId)) {
+ Log.d(LOG_TAG, "Staged session is valid, proceeding with the install");
+ } catch (IOException | SecurityException e) {
+ Log.e(LOG_TAG, "Invalid session id passed", e);
launchFailure(PackageInstaller.STATUS_FAILURE,
PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
}
@@ -293,57 +262,9 @@
@Override
protected PackageInstaller.Session doInBackground(Void... params) {
- PackageInstaller.Session session;
try {
- session = getPackageManager().getPackageInstaller().openSession(mSessionId);
+ return getPackageManager().getPackageInstaller().openSession(mSessionId);
} catch (IOException e) {
- synchronized (this) {
- isDone = true;
- notifyAll();
- }
- return null;
- }
-
- session.setStagingProgress(0);
-
- try {
- File file = new File(mPackageURI.getPath());
-
- try (InputStream in = new FileInputStream(file)) {
- long sizeBytes = file.length();
- long totalRead = 0;
- try (OutputStream out = session
- .openWrite("PackageInstaller", 0, sizeBytes)) {
- byte[] buffer = new byte[1024 * 1024];
- while (true) {
- int numRead = in.read(buffer);
-
- if (numRead == -1) {
- session.fsync(out);
- break;
- }
-
- if (isCancelled()) {
- session.close();
- break;
- }
-
- out.write(buffer, 0, numRead);
- if (sizeBytes > 0) {
- totalRead += numRead;
- float fraction = ((float) totalRead / (float) sizeBytes);
- session.setStagingProgress(fraction);
- }
- }
- }
- }
-
- return session;
- } catch (IOException | SecurityException e) {
- Log.e(LOG_TAG, "Could not write package", e);
-
- session.close();
-
return null;
} finally {
synchronized (this) {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
index 68de2f6..097e47f 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
@@ -16,6 +16,10 @@
package com.android.packageinstaller;
+import static android.content.res.AssetFileDescriptor.UNKNOWN_LENGTH;
+
+import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
+
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -23,40 +27,49 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.util.Log;
import android.view.View;
+import android.widget.ProgressBar;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
- * If a package gets installed from an content URI this step loads the package and turns it into
- * and installation from a file. Then it re-starts the installation as usual.
+ * If a package gets installed from a content URI this step stages the installation session
+ * reading bytes from the URI.
*/
public class InstallStaging extends AlertActivity {
private static final String LOG_TAG = InstallStaging.class.getSimpleName();
- private static final String STAGED_FILE = "STAGED_FILE";
+ private static final String STAGED_SESSION_ID = "STAGED_SESSION_ID";
+
+ private @Nullable PackageInstaller mInstaller;
/** Currently running task that loads the file from the content URI into a file */
private @Nullable StagingAsyncTask mStagingTask;
- /** The file the package is in */
- private @Nullable File mStagedFile;
+ /** The session the package is in */
+ private int mStagedSessionId;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ mInstaller = getPackageManager().getPackageInstaller();
+
setFinishOnTouchOutside(true);
mAlert.setIcon(R.drawable.ic_file_download);
mAlert.setTitle(getString(R.string.app_name_unknown));
@@ -66,6 +79,9 @@
if (mStagingTask != null) {
mStagingTask.cancel(true);
}
+
+ cleanupStagingSession();
+
setResult(RESULT_CANCELED);
finish();
}, null);
@@ -73,11 +89,7 @@
requireViewById(R.id.staging).setVisibility(View.VISIBLE);
if (savedInstanceState != null) {
- mStagedFile = new File(savedInstanceState.getString(STAGED_FILE));
-
- if (!mStagedFile.exists()) {
- mStagedFile = null;
- }
+ mStagedSessionId = savedInstanceState.getInt(STAGED_SESSION_ID, 0);
}
}
@@ -85,21 +97,41 @@
protected void onResume() {
super.onResume();
- // This is the first onResume in a single life of the activity
+ // This is the first onResume in a single life of the activity.
if (mStagingTask == null) {
- // File does not exist, or became invalid
- if (mStagedFile == null) {
- // Create file delayed to be able to show error
+ if (mStagedSessionId > 0) {
+ final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(
+ mStagedSessionId);
+ if (info == null || !info.isActive() || info.getResolvedBaseApkPath() == null) {
+ Log.w(LOG_TAG, "Session " + mStagedSessionId + " in funky state; ignoring");
+ if (info != null) {
+ cleanupStagingSession();
+ }
+ mStagedSessionId = 0;
+ }
+ }
+
+ // Session does not exist, or became invalid.
+ if (mStagedSessionId <= 0) {
+ // Create session here to be able to show error.
+ final Uri packageUri = getIntent().getData();
+ final AssetFileDescriptor afd = openAssetFileDescriptor(packageUri);
try {
- mStagedFile = TemporaryFileManager.getStagedFile(this);
+ ParcelFileDescriptor pfd = afd != null ? afd.getParcelFileDescriptor() : null;
+ PackageInstaller.SessionParams params = createSessionParams(
+ mInstaller, getIntent(), pfd, packageUri.toString());
+ mStagedSessionId = mInstaller.createSession(params);
} catch (IOException e) {
+ Log.w(LOG_TAG, "Failed to create a staging session", e);
showError();
return;
+ } finally {
+ PackageUtil.safeClose(afd);
}
}
mStagingTask = new StagingAsyncTask();
- mStagingTask.execute(getIntent().getData());
+ mStagingTask.execute();
}
}
@@ -107,7 +139,7 @@
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- outState.putString(STAGED_FILE, mStagedFile.getPath());
+ outState.putInt(STAGED_SESSION_ID, mStagedSessionId);
}
@Override
@@ -119,6 +151,65 @@
super.onDestroy();
}
+ private AssetFileDescriptor openAssetFileDescriptor(Uri uri) {
+ try {
+ return getContentResolver().openAssetFileDescriptor(uri, "r");
+ } catch (Exception e) {
+ Log.w(LOG_TAG, "Failed to open asset file descriptor", e);
+ return null;
+ }
+ }
+
+ private static PackageInstaller.SessionParams createSessionParams(
+ @NonNull PackageInstaller installer, @NonNull Intent intent,
+ @Nullable ParcelFileDescriptor pfd, @NonNull String debugPathName) {
+ PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ final Uri referrerUri = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
+ params.setPackageSource(
+ referrerUri != null ? PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
+ : PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE);
+ params.setInstallAsInstantApp(false);
+ params.setReferrerUri(referrerUri);
+ params.setOriginatingUri(intent
+ .getParcelableExtra(Intent.EXTRA_ORIGINATING_URI));
+ params.setOriginatingUid(intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
+ Process.INVALID_UID));
+ params.setInstallerPackageName(intent.getStringExtra(
+ Intent.EXTRA_INSTALLER_PACKAGE_NAME));
+ params.setInstallReason(PackageManager.INSTALL_REASON_USER);
+
+ if (pfd != null) {
+ try {
+ final PackageInstaller.InstallInfo result = installer.readInstallInfo(pfd,
+ debugPathName, 0);
+ params.setAppPackageName(result.getPackageName());
+ params.setInstallLocation(result.getInstallLocation());
+ params.setSize(result.calculateInstalledSize(params, pfd));
+ } catch (PackageInstaller.PackageParsingException | IOException e) {
+ Log.e(LOG_TAG, "Cannot parse package " + debugPathName + ". Assuming defaults.", e);
+ Log.e(LOG_TAG,
+ "Cannot calculate installed size " + debugPathName
+ + ". Try only apk size.");
+ params.setSize(pfd.getStatSize());
+ }
+ } else {
+ Log.e(LOG_TAG, "Cannot parse package " + debugPathName + ". Assuming defaults.");
+ }
+ return params;
+ }
+
+ private void cleanupStagingSession() {
+ if (mStagedSessionId > 0) {
+ try {
+ mInstaller.abandonSession(mStagedSessionId);
+ } catch (SecurityException ignored) {
+
+ }
+ mStagedSessionId = 0;
+ }
+ }
+
/**
* Show an error message and set result as error.
*/
@@ -165,58 +256,109 @@
}
}
- private final class StagingAsyncTask extends AsyncTask<Uri, Void, Boolean> {
- @Override
- protected Boolean doInBackground(Uri... params) {
- if (params == null || params.length <= 0) {
- return false;
- }
- Uri packageUri = params[0];
- try (InputStream in = getContentResolver().openInputStream(packageUri)) {
- // Despite the comments in ContentResolver#openInputStream the returned stream can
- // be null.
- if (in == null) {
- return false;
- }
+ private final class StagingAsyncTask extends
+ AsyncTask<Void, Integer, PackageInstaller.SessionInfo> {
+ private ProgressBar mProgressBar = null;
- try (OutputStream out = new FileOutputStream(mStagedFile)) {
- byte[] buffer = new byte[1024 * 1024];
- int bytesRead;
- while ((bytesRead = in.read(buffer)) >= 0) {
- // Be nice and respond to a cancellation
- if (isCancelled()) {
- return false;
- }
- out.write(buffer, 0, bytesRead);
- }
- }
- } catch (IOException | SecurityException | IllegalStateException
- | IllegalArgumentException e) {
- Log.w(LOG_TAG, "Error staging apk from content URI", e);
- return false;
+ private long getContentSizeBytes() {
+ try (AssetFileDescriptor afd = openAssetFileDescriptor(getIntent().getData())) {
+ return afd != null ? afd.getLength() : UNKNOWN_LENGTH;
+ } catch (IOException ignored) {
+ return UNKNOWN_LENGTH;
}
- return true;
}
@Override
- protected void onPostExecute(Boolean success) {
- if (success) {
- // Now start the installation again from a file
- Intent installIntent = new Intent(getIntent());
- installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class);
- installIntent.setData(Uri.fromFile(mStagedFile));
+ protected void onPreExecute() {
+ final long sizeBytes = getContentSizeBytes();
- if (installIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
- installIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+ mProgressBar = sizeBytes > 0 ? requireViewById(R.id.progress_indeterminate) : null;
+ if (mProgressBar != null) {
+ mProgressBar.setProgress(0);
+ mProgressBar.setMax(100);
+ mProgressBar.setIndeterminate(false);
+ }
+ }
+
+ @Override
+ protected PackageInstaller.SessionInfo doInBackground(Void... params) {
+ Uri packageUri = getIntent().getData();
+ try (PackageInstaller.Session session = mInstaller.openSession(mStagedSessionId);
+ InputStream in = getContentResolver().openInputStream(packageUri)) {
+ session.setStagingProgress(0);
+
+ if (in == null) {
+ return null;
}
- installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
- startActivity(installIntent);
+ long sizeBytes = getContentSizeBytes();
- InstallStaging.this.finish();
- } else {
- showError();
+ long totalRead = 0;
+ try (OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes)) {
+ byte[] buffer = new byte[1024 * 1024];
+ while (true) {
+ int numRead = in.read(buffer);
+
+ if (numRead == -1) {
+ session.fsync(out);
+ break;
+ }
+
+ if (isCancelled()) {
+ break;
+ }
+
+ out.write(buffer, 0, numRead);
+ if (sizeBytes > 0) {
+ totalRead += numRead;
+ float fraction = ((float) totalRead / (float) sizeBytes);
+ session.setStagingProgress(fraction);
+ publishProgress((int) (fraction * 100.0));
+ }
+ }
+ }
+
+ return mInstaller.getSessionInfo(mStagedSessionId);
+ } catch (IOException | SecurityException | IllegalStateException
+ | IllegalArgumentException e) {
+ Log.w(LOG_TAG, "Error staging apk from content URI", e);
+ return null;
}
}
+
+ @Override
+ protected void onProgressUpdate(Integer... progress) {
+ if (mProgressBar != null && progress != null && progress.length > 0) {
+ mProgressBar.setProgress(progress[0], true);
+ }
+ }
+
+ @Override
+ protected void onPostExecute(PackageInstaller.SessionInfo sessionInfo) {
+ if (sessionInfo == null || !sessionInfo.isActive()
+ || sessionInfo.getResolvedBaseApkPath() == null) {
+ Log.w(LOG_TAG, "Session info is invalid: " + sessionInfo);
+ cleanupStagingSession();
+ showError();
+ return;
+ }
+
+ // Pass the staged session to the installer.
+ Intent installIntent = new Intent(getIntent());
+ installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class);
+ installIntent.setData(Uri.fromFile(new File(sessionInfo.getResolvedBaseApkPath())));
+
+ installIntent.putExtra(EXTRA_STAGED_SESSION_ID, mStagedSessionId);
+
+ if (installIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
+ installIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+ }
+
+ installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+
+ startActivity(installIntent);
+
+ InstallStaging.this.finish();
+ }
}
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index ac32020..3f98867 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -77,7 +77,21 @@
}
final ApplicationInfo sourceInfo = getSourceInfo(callingPackage);
- final int originatingUid = getOriginatingUid(sourceInfo);
+ // Uid of the source package, coming from ActivityManager
+ int callingUid = getLaunchedFromUid();
+ if (callingUid == Process.INVALID_UID) {
+ // Cannot reach ActivityManager. Aborting install.
+ Log.e(LOG_TAG, "Could not determine the launching uid.");
+ }
+ // Uid of the source package, with a preference to uid from ApplicationInfo
+ final int originatingUid = sourceInfo != null ? sourceInfo.uid : callingUid;
+
+ if (callingUid == Process.INVALID_UID && sourceInfo == null) {
+ mAbortInstall = true;
+ }
+
+ boolean isDocumentsManager = checkPermission(Manifest.permission.MANAGE_DOCUMENTS,
+ -1, callingUid) == PackageManager.PERMISSION_GRANTED;
boolean isTrustedSource = false;
if (sourceInfo != null && sourceInfo.isPrivilegedApp()) {
isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false) || (
@@ -86,7 +100,8 @@
== PackageManager.PERMISSION_GRANTED);
}
- if (!isTrustedSource && originatingUid != Process.INVALID_UID) {
+ if (!isTrustedSource && !isSystemDownloadsProvider(callingUid) && !isDocumentsManager
+ && originatingUid != Process.INVALID_UID) {
final int targetSdkVersion = getMaxTargetSdkVersionForUid(this, originatingUid);
if (targetSdkVersion < 0) {
Log.w(LOG_TAG, "Cannot get target sdk version for uid " + originatingUid);
@@ -144,14 +159,15 @@
if (packageUri != null
&& packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT)
- && canPackageQuery(originatingUid, packageUri)) {
+ && canPackageQuery(callingUid, packageUri)) {
// [IMPORTANT] This path is deprecated, but should still work. Only necessary
// features should be added.
- // Copy file to prevent it from being changed underneath this process
+ // Stage a session with this file to prevent it from being changed underneath
+ // this process.
nextActivity.setClass(this, InstallStaging.class);
- } else if (packageUri != null && packageUri.getScheme().equals(
- PackageInstallerActivity.SCHEME_PACKAGE)) {
+ } else if (packageUri != null && PackageInstallerActivity.SCHEME_PACKAGE.equals(
+ packageUri.getScheme())) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Intent result = new Intent();
@@ -212,41 +228,6 @@
return null;
}
- /**
- * Get the originating uid if possible, or {@link Process#INVALID_UID} if not available
- *
- * @param sourceInfo The source of this installation
- * @return The UID of the installation source or INVALID_UID
- */
- private int getOriginatingUid(@Nullable ApplicationInfo sourceInfo) {
- // The originating uid from the intent. We only trust/use this if it comes from either
- // the document manager app or the downloads provider
- final int uidFromIntent = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
- Process.INVALID_UID);
-
- final int callingUid;
- if (sourceInfo != null) {
- callingUid = sourceInfo.uid;
- } else {
- callingUid = getLaunchedFromUid();
- if (callingUid == Process.INVALID_UID) {
- // Cannot reach ActivityManager. Aborting install.
- Log.e(LOG_TAG, "Could not determine the launching uid.");
- mAbortInstall = true;
- return Process.INVALID_UID;
- }
- }
- if (checkPermission(Manifest.permission.MANAGE_DOCUMENTS, -1, callingUid)
- == PackageManager.PERMISSION_GRANTED) {
- return uidFromIntent;
- }
- if (isSystemDownloadsProvider(callingUid)) {
- return uidFromIntent;
- }
- // We don't trust uid from the intent. Use the calling uid instead.
- return callingUid;
- }
-
private boolean isSystemDownloadsProvider(int uid) {
final ProviderInfo downloadProviderPackage = getPackageManager().resolveContentProvider(
DOWNLOADS_AUTHORITY, 0);
@@ -260,8 +241,7 @@
}
@NonNull
- private boolean canPackageQuery(int originatingUid, Uri packageUri) {
- String callingPackage = mPackageManager.getPackagesForUid(originatingUid)[0];
+ private boolean canPackageQuery(int callingUid, Uri packageUri) {
ProviderInfo info = mPackageManager.resolveContentProvider(packageUri.getAuthority(),
PackageManager.ComponentInfoFlags.of(0));
if (info == null) {
@@ -269,11 +249,20 @@
}
String targetPackage = info.packageName;
- try {
- return mPackageManager.canPackageQuery(callingPackage, targetPackage);
- } catch (PackageManager.NameNotFoundException e) {
+ String[] callingPackages = mPackageManager.getPackagesForUid(callingUid);
+ if (callingPackages == null) {
return false;
}
+ for (String callingPackage: callingPackages) {
+ try {
+ if (mPackageManager.canPackageQuery(callingPackage, targetPackage)) {
+ return true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // no-op
+ }
+ }
+ return false;
}
private boolean isCallerSessionOwner(int originatingUid, int sessionId) {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index 3ba2acb..7e294ee 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -82,6 +82,7 @@
static final String EXTRA_CALLING_PACKAGE = "EXTRA_CALLING_PACKAGE";
static final String EXTRA_CALLING_ATTRIBUTION_TAG = "EXTRA_CALLING_ATTRIBUTION_TAG";
static final String EXTRA_ORIGINAL_SOURCE_INFO = "EXTRA_ORIGINAL_SOURCE_INFO";
+ static final String EXTRA_STAGED_SESSION_ID = "EXTRA_STAGED_SESSION_ID";
private static final String ALLOW_UNKNOWN_SOURCES_KEY =
PackageInstallerActivity.class.getName() + "ALLOW_UNKNOWN_SOURCES_KEY";
@@ -403,6 +404,10 @@
mReferrerURI = null;
mPendingUserActionReason = info.getPendingUserActionReason();
} else {
+ // Two possible callers:
+ // 1. InstallStart with "SCHEME_PACKAGE".
+ // 2. InstallStaging with "SCHEME_FILE" and EXTRA_STAGED_SESSION_ID with staged
+ // session id.
mSessionId = -1;
packageSource = intent.getData();
mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
@@ -721,14 +726,16 @@
}
private void startInstall() {
+ String installerPackageName = getIntent().getStringExtra(
+ Intent.EXTRA_INSTALLER_PACKAGE_NAME);
+ int stagedSessionId = getIntent().getIntExtra(EXTRA_STAGED_SESSION_ID, 0);
+
// Start subactivity to actually install the application
Intent newIntent = new Intent();
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
mPkgInfo.applicationInfo);
newIntent.setData(mPackageURI);
newIntent.setClass(this, InstallInstalling.class);
- String installerPackageName = getIntent().getStringExtra(
- Intent.EXTRA_INSTALLER_PACKAGE_NAME);
if (mOriginatingURI != null) {
newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
}
@@ -745,6 +752,9 @@
if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
}
+ if (stagedSessionId > 0) {
+ newIntent.putExtra(EXTRA_STAGED_SESSION_ID, stagedSessionId);
+ }
newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
if (mLocalLOGV) Log.i(TAG, "downloaded app uri=" + mPackageURI);
startActivity(newIntent);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
index 698159f..0270591 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
@@ -33,7 +33,9 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import java.io.Closeable;
import java.io.File;
+import java.io.IOException;
/**
* This is a utility class for defining some utility methods and constants
@@ -190,4 +192,19 @@
}
return targetSdkVersion;
}
+
+
+ /**
+ * Quietly close a closeable resource (e.g. a stream or file). The input may already
+ * be closed and it may even be null.
+ */
+ static void safeClose(Closeable resource) {
+ if (resource != null) {
+ try {
+ resource.close();
+ } catch (IOException ioe) {
+ // Catch and discard the error
+ }
+ }
+ }
}
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 01f92c4..19b7e85 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 @@
@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 @@
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 3c45112..dae48db 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 @Nullable String getSummary() {
+ return this.summary;
+ }
+
@Override
public Drawable loadIcon() {
final IconDrawableFactory factory = IconDrawableFactory.newInstance(mContext);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index c9e8312..6cf6825 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -50,6 +50,7 @@
import android.util.Log;
import androidx.annotation.DoNotInline;
+import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.android.internal.annotations.VisibleForTesting;
@@ -638,6 +639,11 @@
}
@Override
+ public void onSessionReleased(@NonNull RoutingSessionInfo session) {
+ refreshDevices();
+ }
+
+ @Override
public void onRouteListingPreferenceUpdated(
String packageName,
RouteListingPreference routeListingPreference) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 270fda8..0969327 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -141,6 +141,56 @@
}
@Test
+ public void onSessionReleased_shouldUpdateConnectedDevice() {
+ final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
+ final RoutingSessionInfo sessionInfo1 = mock(RoutingSessionInfo.class);
+ routingSessionInfos.add(sessionInfo1);
+ final RoutingSessionInfo sessionInfo2 = mock(RoutingSessionInfo.class);
+ routingSessionInfos.add(sessionInfo2);
+
+ final List<String> selectedRoutesSession1 = new ArrayList<>();
+ selectedRoutesSession1.add(TEST_ID_1);
+ when(sessionInfo1.getSelectedRoutes()).thenReturn(selectedRoutesSession1);
+
+ final List<String> selectedRoutesSession2 = new ArrayList<>();
+ selectedRoutesSession2.add(TEST_ID_2);
+ when(sessionInfo2.getSelectedRoutes()).thenReturn(selectedRoutesSession2);
+
+ mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
+
+ final MediaRoute2Info info1 = mock(MediaRoute2Info.class);
+ when(info1.getId()).thenReturn(TEST_ID_1);
+ when(info1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+ final MediaRoute2Info info2 = mock(MediaRoute2Info.class);
+ when(info2.getId()).thenReturn(TEST_ID_2);
+ when(info2.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+ final List<MediaRoute2Info> routes = new ArrayList<>();
+ routes.add(info1);
+ routes.add(info2);
+ mShadowRouter2Manager.setAllRoutes(routes);
+ mShadowRouter2Manager.setTransferableRoutes(routes);
+
+ final MediaDevice mediaDevice1 = mInfoMediaManager.findMediaDevice(TEST_ID_1);
+ assertThat(mediaDevice1).isNull();
+ final MediaDevice mediaDevice2 = mInfoMediaManager.findMediaDevice(TEST_ID_2);
+ assertThat(mediaDevice2).isNull();
+
+ mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
+ final MediaDevice infoDevice1 = mInfoMediaManager.mMediaDevices.get(0);
+ assertThat(infoDevice1.getId()).isEqualTo(TEST_ID_1);
+ final MediaDevice infoDevice2 = mInfoMediaManager.mMediaDevices.get(1);
+ assertThat(infoDevice2.getId()).isEqualTo(TEST_ID_2);
+ // The active routing session is the last one in the list, which maps to infoDevice2.
+ assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice2);
+
+ routingSessionInfos.remove(sessionInfo2);
+ mInfoMediaManager.mMediaRouterCallback.onSessionReleased(sessionInfo2);
+ assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice1);
+ }
+
+ @Test
public void onRouteAdded_buildAllRoutes_shouldAddMediaDevice() {
final MediaRoute2Info info = mock(MediaRoute2Info.class);
when(info.getId()).thenReturn(TEST_ID);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index e4cc9f1..6a5535d 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 @@
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 4b72063..85623b2 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 @@
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 d1bd5e6..46b45d1 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 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 @@
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 @@
}
} 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 @@
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 @@
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 @@
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 36aa2ac..2e49dd5 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -97,7 +97,8 @@
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
@@ -840,7 +841,8 @@
Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT,
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
Settings.Secure.UI_TRANSLATION_ENABLED,
- Settings.Secure.CREDENTIAL_SERVICE);
+ Settings.Secure.CREDENTIAL_SERVICE,
+ Settings.Secure.CREDENTIAL_SERVICE_PRIMARY);
@Test
public void systemSettingsBackedUpOrDenied() {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index a110f56..8b3fd41 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -17,12 +17,12 @@
*/
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.shell"
- coreApp="true"
- android:sharedUserId="android.uid.shell"
- >
+ package="com.android.shell"
+ coreApp="true"
+ android:sharedUserId="android.uid.shell"
+ >
- <!-- Standard permissions granted to the shell. -->
+ <!-- Standard permissions granted to the shell. -->
<uses-permission android:name="android.permission.MANAGE_HEALTH_PERMISSIONS" />
<uses-permission android:name="android.permission.MANAGE_HEALTH_DATA" />
<uses-permission android:name="android.permission.health.READ_EXERCISE_ROUTE" />
@@ -125,7 +125,7 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
- <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- BLUETOOTH_PRIVILEGED is needed for testing purposes only. -->
<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
@@ -136,7 +136,7 @@
<uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
<uses-permission android:name="android.permission.MANAGE_USB" />
<uses-permission android:name="android.permission.USE_RESERVED_DISK" />
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- System tool permissions granted to the shell. -->
<uses-permission android:name="android.permission.REAL_GET_TASKS" />
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
@@ -231,16 +231,16 @@
<uses-permission android:name="android.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS" />
<uses-permission android:name="android.permission.CLEAR_FREEZE_PERIOD" />
<uses-permission android:name="android.permission.MODIFY_QUIET_MODE" />
- <uses-permission android:name="android.permission.ACCESS_LOWPAN_STATE"/>
- <uses-permission android:name="android.permission.CHANGE_LOWPAN_STATE"/>
- <uses-permission android:name="android.permission.READ_LOWPAN_CREDENTIAL"/>
+ <uses-permission android:name="android.permission.ACCESS_LOWPAN_STATE" />
+ <uses-permission android:name="android.permission.CHANGE_LOWPAN_STATE" />
+ <uses-permission android:name="android.permission.READ_LOWPAN_CREDENTIAL" />
<uses-permission android:name="android.permission.BLUETOOTH_STACK" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.RETRIEVE_WINDOW_TOKEN" />
<uses-permission android:name="android.permission.FRAME_STATS" />
<uses-permission android:name="android.permission.BIND_APPWIDGET" />
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
- <uses-permission android:name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/>
+ <uses-permission android:name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS" />
<uses-permission android:name="android.permission.CHANGE_APP_IDLE_STATE" />
<uses-permission android:name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
@@ -304,7 +304,7 @@
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.BACKGROUND_CAMERA" />
<uses-permission android:name="android.permission.SYSTEM_CAMERA" />
- <!-- Permissions needed to test onCameraOpened/Closed callbacks -->
+ <!-- Permissions needed to test onCameraOpened/Closed callbacks -->
<uses-permission android:name="android.permission.CAMERA_OPEN_CLOSE_LISTENER" />
<!-- Permissions needed for CTS camera test: RecordingTest.java when assuming shell id -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
@@ -344,7 +344,7 @@
<uses-permission android:name="android.permission.LOADER_USAGE_STATS" />
<!-- Permission required for storage tests - FuseDaemonHostTest -->
- <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<!-- Permission needed to run network tests in CTS -->
<uses-permission android:name="android.permission.MANAGE_TEST_NETWORKS" />
@@ -386,54 +386,54 @@
<uses-permission android:name="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE" />
<!-- Permission required for CTS test - CrossProfileAppsHostSideTest -->
- <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES" />
<!-- Permission required for CTS test - CrossProfileAppsHostSideTest -->
- <uses-permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES"/>
+ <uses-permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES" />
<!-- permissions required for CTS test - PhoneStateListenerTest -->
<uses-permission android:name="android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH" />
<!-- Permissions required for granting and logging -->
- <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
- <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
- <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"/>
- <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
+ <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" />
+ <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+ <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG" />
+ <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD" />
<!-- Permission required for CTS test - BatterySaverTest -->
- <uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
+ <uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE" />
<!-- Permission required for CTS test - UiModeManagerTest -->
- <uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED"/>
- <uses-permission android:name="android.permission.READ_PROJECTION_STATE"/>
+ <uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED" />
+ <uses-permission android:name="android.permission.READ_PROJECTION_STATE" />
<!-- Permission required for CTS tests - UiModeManagerTest, CarModeInCallServiceTest -->
- <uses-permission android:name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION"/>
+ <uses-permission android:name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION" />
<!-- Permission required for CTS test - SystemConfigTest -->
- <uses-permission android:name="android.permission.READ_CARRIER_APP_INFO"/>
+ <uses-permission android:name="android.permission.READ_CARRIER_APP_INFO" />
<!-- Permission required for CTS test - CarModeInCallServiceTest -->
- <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
+ <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE" />
<!-- Permission requried for CTS test - CellBroadcastIntentsTest -->
- <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS"/>
+ <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS" />
<!-- Permission required for CTS test - TetheringManagerTest -->
- <uses-permission android:name="android.permission.TETHER_PRIVILEGED"/>
+ <uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
<!-- Permission required for CTS test - CtsOsTestCases -->
- <uses-permission android:name="android.permission.MANAGE_CRATES"/>
+ <uses-permission android:name="android.permission.MANAGE_CRATES" />
<!-- Allows setting brightness from the shell -->
- <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/>
+ <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
<!-- Permission required for CTS test - ShortcutManagerUsageTest -->
- <uses-permission android:name="android.permission.ACCESS_SHORTCUTS"/>
+ <uses-permission android:name="android.permission.ACCESS_SHORTCUTS" />
<!-- Permissions required for CTS test - UsageStatsTest -->
- <uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS"/>
- <uses-permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"/>
+ <uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS" />
+ <uses-permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS" />
<!-- Permission needed for CTS test - MusicRecognitionManagerTest -->
<uses-permission android:name="android.permission.MANAGE_MUSIC_RECOGNITION" />
@@ -442,8 +442,8 @@
<uses-permission android:name="android.permission.MANAGE_SPEECH_RECOGNITION" />
<!-- Permissions required to test ambient display. -->
- <uses-permission android:name="android.permission.READ_DREAM_STATE"/>
- <uses-permission android:name="android.permission.WRITE_DREAM_STATE"/>
+ <uses-permission android:name="android.permission.READ_DREAM_STATE" />
+ <uses-permission android:name="android.permission.WRITE_DREAM_STATE" />
<!-- Permission required for CTS test - CtsLightsManagerTest -->
<uses-permission android:name="android.permission.CONTROL_DEVICE_LIGHTS" />
@@ -470,7 +470,7 @@
<uses-permission android:name="android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE" />
<!-- Permission required for testing system audio effect APIs. -->
- <uses-permission android:name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS"/>
+ <uses-permission android:name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS" />
<!-- Permission required for running networking unit tests -->
<uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
@@ -495,7 +495,7 @@
<uses-permission android:name="android.permission.TV_INPUT_HARDWARE" />
<uses-permission android:name="android.permission.TIS_EXTENSION_INTERFACE" />
<uses-permission android:name="com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS" />
- <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA"/>
+ <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
<!-- Permission needed for CTS test - PrivilegedLocationPermissionTest -->
<uses-permission android:name="android.permission.LOCATION_HARDWARE" />
@@ -560,14 +560,14 @@
<uses-permission android:name="android.permission.BIND_CARRIER_SERVICES" />
<!-- Allows overriding the system's device state from the shell -->
- <uses-permission android:name="android.permission.CONTROL_DEVICE_STATE"/>
+ <uses-permission android:name="android.permission.CONTROL_DEVICE_STATE" />
<!-- Permissions required for CTS tests to close system dialogs -->
<uses-permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" />
<!-- Permissions required for CTS test - HideOverlayWindowsTest -->
- <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
- <uses-permission android:name="android.permission.SYSTEM_APPLICATION_OVERLAY"/>
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+ <uses-permission android:name="android.permission.SYSTEM_APPLICATION_OVERLAY" />
<!-- Permission required for CTS test - CtsHdmiCecHostTestCases -->
<uses-permission android:name="android.permission.HDMI_CEC" />
@@ -630,21 +630,21 @@
<uses-permission android:name="android.permission.UPDATE_FONTS" />
<!-- Permission required for Launcher testing - DigitalWellbeingToastTest -->
- <uses-permission android:name="android.permission.GET_TOP_ACTIVITY_INFO"/>
+ <uses-permission android:name="android.permission.GET_TOP_ACTIVITY_INFO" />
<!-- Permission required for hotword detection service CTS tests -->
<uses-permission android:name="android.permission.MANAGE_HOTWORD_DETECTION" />
<uses-permission android:name="android.permission.BIND_HOTWORD_DETECTION_SERVICE" />
<!-- Permission required for CTS test - CtsVoiceInteractionTestCases -->
- <uses-permission android:name="android.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER"/>
+ <uses-permission android:name="android.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER" />
<uses-permission android:name="android.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE" />
<!-- Permission required for CTS test - KeyguardLockedStateApiTest -->
<uses-permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" />
- <uses-permission android:name="android.permission.MANAGE_APP_HIBERNATION"/>
+ <uses-permission android:name="android.permission.MANAGE_APP_HIBERNATION" />
<!-- Permission required for CTS test - MediaCodecResourceTest -->
<uses-permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID" />
@@ -820,8 +820,8 @@
<uses-permission android:name="android.permission.DELETE_STAGED_HEALTH_CONNECT_REMOTE_DATA" />
<uses-permission android:name="android.permission.STAGE_HEALTH_CONNECT_REMOTE_DATA" />
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED"/>
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
<!-- Permissions required for CTS test - CtsBroadcastRadioTestCases -->
<uses-permission android:name="android.permission.ACCESS_BROADCAST_RADIO" />
@@ -832,12 +832,17 @@
<!-- Permission required for CTS test - CtsTelephonyProviderTestCases -->
<uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />
- <uses-permission android:name="android.permission.LOG_FOREGROUND_RESOURCE_USE"/>
+ <uses-permission android:name="android.permission.LOG_FOREGROUND_RESOURCE_USE" />
+ <!-- Permission required for GTS test - GtsAttestationVerificationDeviceSideTestCases -->
+ <uses-permission android:name="android.permission.USE_ATTESTATION_VERIFICATION_SERVICE" />
+ <!-- Permission required for GTS test - GtsCredentialsTestCases -->
+ <uses-permission android:name="android.permission.LAUNCH_CREDENTIAL_SELECTOR" />
- <application android:label="@string/app_label"
- android:theme="@android:style/Theme.DeviceDefault.DayNight"
- android:defaultToDeviceProtectedStorage="true"
- android:directBootAware="true">
+ <application
+ android:label="@string/app_label"
+ android:theme="@android:style/Theme.DeviceDefault.DayNight"
+ android:defaultToDeviceProtectedStorage="true"
+ android:directBootAware="true">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.android.shell"
@@ -860,10 +865,11 @@
</intent-filter>
</provider>
- <provider android:name=".HeapDumpProvider"
- android:authorities="com.android.shell.heapdump"
- android:grantUriPermissions="true"
- android:exported="false" />
+ <provider
+ android:name=".HeapDumpProvider"
+ android:authorities="com.android.shell.heapdump"
+ android:grantUriPermissions="true"
+ android:exported="false" />
<activity
android:name=".BugreportWarningActivity"
@@ -872,13 +878,14 @@
android:excludeFromRecents="true"
android:exported="false" />
- <activity android:name=".HeapDumpActivity"
- android:theme="@*android:style/Theme.Translucent.NoTitleBar"
- android:label="@*android:string/dump_heap_title"
- android:finishOnCloseSystemDialogs="true"
- android:noHistory="true"
- android:excludeFromRecents="true"
- android:exported="false" />
+ <activity
+ android:name=".HeapDumpActivity"
+ android:theme="@*android:style/Theme.Translucent.NoTitleBar"
+ android:label="@*android:string/dump_heap_title"
+ android:finishOnCloseSystemDialogs="true"
+ android:noHistory="true"
+ android:excludeFromRecents="true"
+ android:exported="false" />
<receiver
android:name=".BugreportRequestedReceiver"
@@ -903,7 +910,7 @@
<receiver
android:name=".ProfcollectUploadReceiver"
android:exported="true"
- android:permission="android.permission.TRIGGER_SHELL_PROFCOLLECT_UPLOAD" >
+ android:permission="android.permission.TRIGGER_SHELL_PROFCOLLECT_UPLOAD">
<intent-filter>
<action android:name="com.android.shell.action.PROFCOLLECT_UPLOAD" />
</intent-filter>
@@ -912,6 +919,6 @@
<service
android:name=".BugreportProgressService"
android:foregroundServiceType="systemExempted"
- android:exported="false"/>
+ android:exported="false" />
</application>
</manifest>
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index e6bbf97..6a7b8cd 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -158,6 +158,7 @@
"SystemUIAnimationLib",
"SystemUICommon",
"SystemUICustomizationLib",
+ "SystemUILogLib",
"SystemUIPluginLib",
"SystemUISharedLib",
"SystemUI-statsd",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 6e55000..a27f113 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -82,6 +82,7 @@
<uses-permission android:name="android.permission.CONTROL_VPN" />
<uses-permission android:name="android.permission.PEERS_MAC_ADDRESS"/>
<uses-permission android:name="android.permission.READ_WIFI_CREDENTIAL"/>
+ <uses-permission android:name="android.permission.LOCATION_HARDWARE" />
<!-- Physical hardware -->
<uses-permission android:name="android.permission.MANAGE_USB" />
<uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
@@ -992,6 +993,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 77ddc6e..1ce3472 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -55,6 +55,7 @@
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/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 5b5871f..8eb012d 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -43,6 +43,7 @@
"androidx.core_core-ktx",
"androidx.annotation_annotation",
"SystemUIShaderLib",
+ "animationlib",
],
manifest: "AndroidManifest.xml",
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 94b3740..4037fd4 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -39,9 +39,9 @@
import android.view.animation.PathInterpolator
import androidx.annotation.BinderThread
import androidx.annotation.UiThread
+import com.android.app.animation.Interpolators
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
-import java.lang.IllegalArgumentException
import kotlin.math.roundToInt
private const val TAG = "ActivityLaunchAnimator"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 42a8636..48dd08f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -33,10 +33,10 @@
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
import android.widget.FrameLayout
+import com.android.app.animation.Interpolators
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor.CujType
import com.android.systemui.util.registerAnimationOnBackInvoked
-import java.lang.IllegalArgumentException
import kotlin.math.roundToInt
private const val TAG = "DialogLaunchAnimator"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
deleted file mode 100644
index 9dbb920..0000000
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2016 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.animation;
-
-import android.graphics.Path;
-import android.util.MathUtils;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.BounceInterpolator;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
-import android.view.animation.PathInterpolator;
-
-/**
- * Utility class to receive interpolators from.
- *
- * Make sure that changes made to this class are also reflected in {@link InterpolatorsAndroidX}.
- * Please consider using the androidx dependencies featuring better testability altogether.
- */
-public class Interpolators {
-
- /*
- * ============================================================================================
- * Emphasized interpolators.
- * ============================================================================================
- */
-
- /**
- * The default emphasized interpolator. Used for hero / emphasized movement of content.
- */
- public static final Interpolator EMPHASIZED = createEmphasizedInterpolator();
-
- /**
- * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
- * is disappearing e.g. when moving off screen.
- */
- public static final Interpolator EMPHASIZED_ACCELERATE = new PathInterpolator(
- 0.3f, 0f, 0.8f, 0.15f);
-
- /**
- * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that
- * is appearing e.g. when coming from off screen
- */
- public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
- 0.05f, 0.7f, 0.1f, 1f);
-
-
- /*
- * ============================================================================================
- * Standard interpolators.
- * ============================================================================================
- */
-
- /**
- * The standard interpolator that should be used on every normal animation
- */
- public static final Interpolator STANDARD = new PathInterpolator(
- 0.2f, 0f, 0f, 1f);
-
- /**
- * The standard accelerating interpolator that should be used on every regular movement of
- * content that is disappearing e.g. when moving off screen.
- */
- public static final Interpolator STANDARD_ACCELERATE = new PathInterpolator(
- 0.3f, 0f, 1f, 1f);
-
- /**
- * The standard decelerating interpolator that should be used on every regular movement of
- * content that is appearing e.g. when coming from off screen.
- */
- public static final Interpolator STANDARD_DECELERATE = new PathInterpolator(
- 0f, 0f, 0f, 1f);
-
- /*
- * ============================================================================================
- * Legacy
- * ============================================================================================
- */
-
- /**
- * The default legacy interpolator as defined in Material 1. Also known as FAST_OUT_SLOW_IN.
- */
- public static final Interpolator LEGACY = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
-
- /**
- * The default legacy accelerating interpolator as defined in Material 1.
- * Also known as FAST_OUT_LINEAR_IN.
- */
- public static final Interpolator LEGACY_ACCELERATE = new PathInterpolator(0.4f, 0f, 1f, 1f);
-
- /**
- * The default legacy decelerating interpolator as defined in Material 1.
- * Also known as LINEAR_OUT_SLOW_IN.
- */
- public static final Interpolator LEGACY_DECELERATE = new PathInterpolator(0f, 0f, 0.2f, 1f);
-
- /**
- * Linear interpolator. Often used if the interpolator is for different properties who need
- * different interpolations.
- */
- public static final Interpolator LINEAR = new LinearInterpolator();
-
- /*
- * ============================================================================================
- * Custom interpolators
- * ============================================================================================
- */
-
- public static final Interpolator FAST_OUT_SLOW_IN = LEGACY;
- public static final Interpolator FAST_OUT_LINEAR_IN = LEGACY_ACCELERATE;
- public static final Interpolator LINEAR_OUT_SLOW_IN = LEGACY_DECELERATE;
-
- /**
- * Like {@link #FAST_OUT_SLOW_IN}, but used in case the animation is played in reverse (i.e. t
- * goes from 1 to 0 instead of 0 to 1).
- */
- public static final Interpolator FAST_OUT_SLOW_IN_REVERSE =
- new PathInterpolator(0.8f, 0f, 0.6f, 1f);
- public static final Interpolator SLOW_OUT_LINEAR_IN = new PathInterpolator(0.8f, 0f, 1f, 1f);
- public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
- public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
- public static final Interpolator ACCELERATE = new AccelerateInterpolator();
- public static final Interpolator ACCELERATE_DECELERATE = new AccelerateDecelerateInterpolator();
- public static final Interpolator DECELERATE_QUINT = new DecelerateInterpolator(2.5f);
- public static final Interpolator CUSTOM_40_40 = new PathInterpolator(0.4f, 0f, 0.6f, 1f);
- public static final Interpolator ICON_OVERSHOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f);
- public static final Interpolator ICON_OVERSHOT_LESS = new PathInterpolator(0.4f, 0f, 0.2f,
- 1.1f);
- public static final Interpolator PANEL_CLOSE_ACCELERATED = new PathInterpolator(0.3f, 0, 0.5f,
- 1);
- public static final Interpolator BOUNCE = new BounceInterpolator();
- /**
- * For state transitions on the control panel that lives in GlobalActions.
- */
- public static final Interpolator CONTROL_STATE = new PathInterpolator(0.4f, 0f, 0.2f,
- 1.0f);
-
- /**
- * Interpolator to be used when animating a move based on a click. Pair with enough duration.
- */
- public static final Interpolator TOUCH_RESPONSE =
- new PathInterpolator(0.3f, 0f, 0.1f, 1f);
-
- /**
- * Like {@link #TOUCH_RESPONSE}, but used in case the animation is played in reverse (i.e. t
- * goes from 1 to 0 instead of 0 to 1).
- */
- public static final Interpolator TOUCH_RESPONSE_REVERSE =
- new PathInterpolator(0.9f, 0f, 0.7f, 1f);
-
- /*
- * ============================================================================================
- * Functions / Utilities
- * ============================================================================================
- */
-
- /**
- * Calculate the amount of overshoot using an exponential falloff function with desired
- * properties, where the overshoot smoothly transitions at the 1.0f boundary into the
- * overshoot, retaining its acceleration.
- *
- * @param progress a progress value going from 0 to 1
- * @param overshootAmount the amount > 0 of overshoot desired. A value of 0.1 means the max
- * value of the overall progress will be at 1.1.
- * @param overshootStart the point in (0,1] where the result should reach 1
- * @return the interpolated overshoot
- */
- public static float getOvershootInterpolation(float progress, float overshootAmount,
- float overshootStart) {
- if (overshootAmount == 0.0f || overshootStart == 0.0f) {
- throw new IllegalArgumentException("Invalid values for overshoot");
- }
- float b = MathUtils.log((overshootAmount + 1) / (overshootAmount)) / overshootStart;
- return MathUtils.max(0.0f,
- (float) (1.0f - Math.exp(-b * progress)) * (overshootAmount + 1.0f));
- }
-
- /**
- * Similar to {@link #getOvershootInterpolation(float, float, float)} but the overshoot
- * starts immediately here, instead of first having a section of non-overshooting
- *
- * @param progress a progress value going from 0 to 1
- */
- public static float getOvershootInterpolation(float progress) {
- return MathUtils.max(0.0f, (float) (1.0f - Math.exp(-4 * progress)));
- }
-
- // Create the default emphasized interpolator
- private static PathInterpolator createEmphasizedInterpolator() {
- Path path = new Path();
- // Doing the same as fast_out_extra_slow_in
- path.moveTo(0f, 0f);
- path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f);
- path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f);
- return new PathInterpolator(path);
- }
-}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java b/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java
deleted file mode 100644
index 8da87feb..0000000
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.animation;
-
-import android.graphics.Path;
-import android.util.MathUtils;
-
-import androidx.core.animation.AccelerateDecelerateInterpolator;
-import androidx.core.animation.AccelerateInterpolator;
-import androidx.core.animation.BounceInterpolator;
-import androidx.core.animation.DecelerateInterpolator;
-import androidx.core.animation.Interpolator;
-import androidx.core.animation.LinearInterpolator;
-import androidx.core.animation.PathInterpolator;
-
-/**
- * Utility class to receive interpolators from. (androidx compatible version)
- *
- * This is the androidx compatible version of {@link Interpolators}. Make sure that changes made to
- * this class are also reflected in {@link Interpolators}.
- *
- * Using the androidx versions of {@link androidx.core.animation.ValueAnimator} or
- * {@link androidx.core.animation.ObjectAnimator} improves animation testability. This file provides
- * the androidx compatible versions of the interpolators defined in {@link Interpolators}.
- * AnimatorTestRule can be used in Tests to manipulate the animation under test (e.g. artificially
- * advancing the time).
- */
-public class InterpolatorsAndroidX {
-
- /*
- * ============================================================================================
- * Emphasized interpolators.
- * ============================================================================================
- */
-
- /**
- * The default emphasized interpolator. Used for hero / emphasized movement of content.
- */
- public static final Interpolator EMPHASIZED = createEmphasizedInterpolator();
-
- /**
- * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
- * is disappearing e.g. when moving off screen.
- */
- public static final Interpolator EMPHASIZED_ACCELERATE = new PathInterpolator(
- 0.3f, 0f, 0.8f, 0.15f);
-
- /**
- * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that
- * is appearing e.g. when coming from off screen
- */
- public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
- 0.05f, 0.7f, 0.1f, 1f);
-
-
- /*
- * ============================================================================================
- * Standard interpolators.
- * ============================================================================================
- */
-
- /**
- * The standard interpolator that should be used on every normal animation
- */
- public static final Interpolator STANDARD = new PathInterpolator(
- 0.2f, 0f, 0f, 1f);
-
- /**
- * The standard accelerating interpolator that should be used on every regular movement of
- * content that is disappearing e.g. when moving off screen.
- */
- public static final Interpolator STANDARD_ACCELERATE = new PathInterpolator(
- 0.3f, 0f, 1f, 1f);
-
- /**
- * The standard decelerating interpolator that should be used on every regular movement of
- * content that is appearing e.g. when coming from off screen.
- */
- public static final Interpolator STANDARD_DECELERATE = new PathInterpolator(
- 0f, 0f, 0f, 1f);
-
- /*
- * ============================================================================================
- * Legacy
- * ============================================================================================
- */
-
- /**
- * The default legacy interpolator as defined in Material 1. Also known as FAST_OUT_SLOW_IN.
- */
- public static final Interpolator LEGACY = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
-
- /**
- * The default legacy accelerating interpolator as defined in Material 1.
- * Also known as FAST_OUT_LINEAR_IN.
- */
- public static final Interpolator LEGACY_ACCELERATE = new PathInterpolator(0.4f, 0f, 1f, 1f);
-
- /**
- * The default legacy decelerating interpolator as defined in Material 1.
- * Also known as LINEAR_OUT_SLOW_IN.
- */
- public static final Interpolator LEGACY_DECELERATE = new PathInterpolator(0f, 0f, 0.2f, 1f);
-
- /**
- * Linear interpolator. Often used if the interpolator is for different properties who need
- * different interpolations.
- */
- public static final Interpolator LINEAR = new LinearInterpolator();
-
- /*
- * ============================================================================================
- * Custom interpolators
- * ============================================================================================
- */
-
- public static final Interpolator FAST_OUT_SLOW_IN = LEGACY;
- public static final Interpolator FAST_OUT_LINEAR_IN = LEGACY_ACCELERATE;
- public static final Interpolator LINEAR_OUT_SLOW_IN = LEGACY_DECELERATE;
-
- /**
- * Like {@link #FAST_OUT_SLOW_IN}, but used in case the animation is played in reverse (i.e. t
- * goes from 1 to 0 instead of 0 to 1).
- */
- public static final Interpolator FAST_OUT_SLOW_IN_REVERSE =
- new PathInterpolator(0.8f, 0f, 0.6f, 1f);
- public static final Interpolator SLOW_OUT_LINEAR_IN = new PathInterpolator(0.8f, 0f, 1f, 1f);
- public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
- public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
- public static final Interpolator ACCELERATE = new AccelerateInterpolator();
- public static final Interpolator ACCELERATE_DECELERATE = new AccelerateDecelerateInterpolator();
- public static final Interpolator DECELERATE_QUINT = new DecelerateInterpolator(2.5f);
- public static final Interpolator CUSTOM_40_40 = new PathInterpolator(0.4f, 0f, 0.6f, 1f);
- public static final Interpolator ICON_OVERSHOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f);
- public static final Interpolator ICON_OVERSHOT_LESS = new PathInterpolator(0.4f, 0f, 0.2f,
- 1.1f);
- public static final Interpolator PANEL_CLOSE_ACCELERATED = new PathInterpolator(0.3f, 0, 0.5f,
- 1);
- public static final Interpolator BOUNCE = new BounceInterpolator();
- /**
- * For state transitions on the control panel that lives in GlobalActions.
- */
- public static final Interpolator CONTROL_STATE = new PathInterpolator(0.4f, 0f, 0.2f,
- 1.0f);
-
- /**
- * Interpolator to be used when animating a move based on a click. Pair with enough duration.
- */
- public static final Interpolator TOUCH_RESPONSE =
- new PathInterpolator(0.3f, 0f, 0.1f, 1f);
-
- /**
- * Like {@link #TOUCH_RESPONSE}, but used in case the animation is played in reverse (i.e. t
- * goes from 1 to 0 instead of 0 to 1).
- */
- public static final Interpolator TOUCH_RESPONSE_REVERSE =
- new PathInterpolator(0.9f, 0f, 0.7f, 1f);
-
- /*
- * ============================================================================================
- * Functions / Utilities
- * ============================================================================================
- */
-
- /**
- * Calculate the amount of overshoot using an exponential falloff function with desired
- * properties, where the overshoot smoothly transitions at the 1.0f boundary into the
- * overshoot, retaining its acceleration.
- *
- * @param progress a progress value going from 0 to 1
- * @param overshootAmount the amount > 0 of overshoot desired. A value of 0.1 means the max
- * value of the overall progress will be at 1.1.
- * @param overshootStart the point in (0,1] where the result should reach 1
- * @return the interpolated overshoot
- */
- public static float getOvershootInterpolation(float progress, float overshootAmount,
- float overshootStart) {
- if (overshootAmount == 0.0f || overshootStart == 0.0f) {
- throw new IllegalArgumentException("Invalid values for overshoot");
- }
- float b = MathUtils.log((overshootAmount + 1) / (overshootAmount)) / overshootStart;
- return MathUtils.max(0.0f,
- (float) (1.0f - Math.exp(-b * progress)) * (overshootAmount + 1.0f));
- }
-
- /**
- * Similar to {@link #getOvershootInterpolation(float, float, float)} but the overshoot
- * starts immediately here, instead of first having a section of non-overshooting
- *
- * @param progress a progress value going from 0 to 1
- */
- public static float getOvershootInterpolation(float progress) {
- return MathUtils.max(0.0f, (float) (1.0f - Math.exp(-4 * progress)));
- }
-
- // Create the default emphasized interpolator
- private static PathInterpolator createEmphasizedInterpolator() {
- Path path = new Path();
- // Doing the same as fast_out_extra_slow_in
- path.moveTo(0f, 0f);
- path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f);
- path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f);
- return new PathInterpolator(path);
- }
-}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
index 3417ffd..142fd21 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
@@ -28,7 +28,7 @@
import android.view.View
import android.view.ViewGroup
import android.view.animation.Interpolator
-import com.android.systemui.animation.Interpolators.LINEAR
+import com.android.app.animation.Interpolators.LINEAR
import kotlin.math.roundToInt
private const val TAG = "LaunchAnimator"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index 9346a2f..0010894 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -23,8 +23,8 @@
import android.graphics.Canvas
import android.graphics.Typeface
import android.graphics.fonts.Font
+import android.graphics.fonts.FontVariationAxis
import android.text.Layout
-import android.text.TextPaint
import android.util.LruCache
private const val DEFAULT_ANIMATION_DURATION: Long = 300
@@ -33,18 +33,39 @@
typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit
interface TypefaceVariantCache {
- fun getTypefaceForVariant(fvar: String, targetPaint: TextPaint): Typeface?
+ fun getTypefaceForVariant(fvar: String?): Typeface?
+
+ companion object {
+ fun createVariantTypeface(baseTypeface: Typeface, fVar: String?): Typeface {
+ if (fVar.isNullOrEmpty()) {
+ return baseTypeface
+ }
+
+ val axes = FontVariationAxis.fromFontVariationSettings(fVar).toMutableList()
+ axes.removeIf { !baseTypeface.isSupportedAxes(it.getOpenTypeTagValue()) }
+ if (axes.isEmpty()) {
+ return baseTypeface
+ }
+ return Typeface.createFromTypefaceWithVariation(baseTypeface, axes)
+ }
+ }
}
-class TypefaceVariantCacheImpl() : TypefaceVariantCache {
+class TypefaceVariantCacheImpl(
+ var baseTypeface: Typeface,
+) : TypefaceVariantCache {
private val cache = LruCache<String, Typeface>(TYPEFACE_CACHE_MAX_ENTRIES)
- override fun getTypefaceForVariant(fvar: String, targetPaint: TextPaint): Typeface? {
+ override fun getTypefaceForVariant(fvar: String?): Typeface? {
+ if (fvar == null) {
+ return baseTypeface
+ }
cache.get(fvar)?.let {
return it
}
- targetPaint.fontVariationSettings = fvar
- return targetPaint.typeface?.also { cache.put(fvar, it) }
+ return TypefaceVariantCache
+ .createVariantTypeface(baseTypeface, fvar)
+ .also { cache.put(fvar, it) }
}
}
@@ -78,7 +99,7 @@
layout: Layout,
private val invalidateCallback: () -> Unit,
) {
- var typefaceCache: TypefaceVariantCache = TypefaceVariantCacheImpl()
+ var typefaceCache: TypefaceVariantCache = TypefaceVariantCacheImpl(layout.paint.typeface)
get() = field
set(value) {
field = value
@@ -244,8 +265,7 @@
}
if (!fvar.isNullOrBlank()) {
- textInterpolator.targetPaint.typeface =
- typefaceCache.getTypefaceForVariant(fvar, textInterpolator.targetPaint)
+ textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
}
if (color != null) {
@@ -339,4 +359,3 @@
)
}
}
-
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
index 79189bc..8ed8d8f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
@@ -38,6 +38,8 @@
* Once you modified the style parameters, you have to call reshapeText to recalculate base text
* layout.
*
+ * Do not bypass the cache and update the typeface or font variation directly.
+ *
* @return a paint object
*/
val basePaint = TextPaint(layout.paint)
@@ -48,6 +50,8 @@
* Once you modified the style parameters, you have to call reshapeText to recalculate target
* text layout.
*
+ * Do not bypass the cache and update the typeface or font variation directly.
+ *
* @return a paint object
*/
val targetPaint = TextPaint(layout.paint)
@@ -217,14 +221,8 @@
run.fontRuns.forEach { fontRun ->
fontRun.baseFont =
fontInterpolator.lerp(fontRun.baseFont, fontRun.targetFont, progress)
- val fvar = run {
- val tmpFontVariationsArray = mutableListOf<FontVariationAxis>()
- fontRun.baseFont.axes.forEach {
- tmpFontVariationsArray.add(FontVariationAxis(it.tag, it.styleValue))
- }
- FontVariationAxis.toFontVariationSettings(tmpFontVariationsArray)
- }
- basePaint.typeface = typefaceCache.getTypefaceForVariant(fvar, basePaint)
+ val fvar = FontVariationAxis.toFontVariationSettings(fontRun.baseFont.axes)
+ basePaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
}
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index 58ffef2..8e79e3c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -25,6 +25,7 @@
import android.view.View
import android.view.ViewGroup
import android.view.animation.Interpolator
+import com.android.app.animation.Interpolators
import kotlin.math.max
import kotlin.math.min
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
index f3d8b17..dd32851 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
@@ -19,7 +19,7 @@
import android.util.DisplayMetrics
import android.view.animation.Interpolator
import android.window.BackEvent
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.util.dpToPx
/** Used to convert [BackEvent] into a [BackTransformation]. */
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 3a19990..8dd2c39 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -28,13 +28,13 @@
import android.util.AttributeSet
import android.util.MathUtils.constrainedMap
import android.widget.TextView
+import com.android.app.animation.Interpolators
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.animation.GlyphCallback
-import com.android.systemui.animation.Interpolators
import com.android.systemui.animation.TextAnimator
import com.android.systemui.customization.R
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
import java.io.PrintWriter
import java.util.Calendar
import java.util.Locale
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index b0c0240..aa1bb3f 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -23,6 +23,12 @@
import android.provider.Settings
import android.util.Log
import androidx.annotation.OpenForTesting
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogMessage
+import com.android.systemui.log.LogMessageImpl
+import com.android.systemui.log.MessageInitializer
+import com.android.systemui.log.MessagePrinter
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
@@ -32,12 +38,6 @@
import com.android.systemui.plugins.PluginLifecycleManager
import com.android.systemui.plugins.PluginListener
import com.android.systemui.plugins.PluginManager
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogMessage
-import com.android.systemui.plugins.log.LogMessageImpl
-import com.android.systemui.plugins.log.MessageInitializer
-import com.android.systemui.plugins.log.MessagePrinter
import com.android.systemui.util.Assert
import java.io.PrintWriter
import java.util.concurrent.ConcurrentHashMap
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 2cc3600..6aa74fb 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -24,6 +24,7 @@
import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting
import com.android.systemui.customization.R
+import com.android.systemui.log.LogBuffer
import com.android.systemui.plugins.ClockAnimations
import com.android.systemui.plugins.ClockConfig
import com.android.systemui.plugins.ClockController
@@ -32,7 +33,6 @@
import com.android.systemui.plugins.ClockFaceController
import com.android.systemui.plugins.ClockFaceEvents
import com.android.systemui.plugins.ClockSettings
-import com.android.systemui.plugins.log.LogBuffer
import java.io.PrintWriter
import java.util.Locale
import java.util.TimeZone
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
index 08ee602..6f363a4 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
@@ -20,6 +20,8 @@
object KeyguardPreviewConstants {
const val MESSAGE_ID_HIDE_SMART_SPACE = 1111
const val KEY_HIDE_SMART_SPACE = "hide_smart_space"
+ const val MESSAGE_ID_COLOR_OVERRIDE = 1234
+ const val KEY_COLOR_OVERRIDE = "color_override" // ColorInt Encoded as string
const val MESSAGE_ID_SLOT_SELECTED = 1337
const val KEY_SLOT_ID = "slot_id"
const val KEY_INITIALLY_SELECTED_SLOT_ID = "initially_selected_slot_id"
diff --git a/packages/SystemUI/log/.gitignore b/packages/SystemUI/log/.gitignore
new file mode 100644
index 0000000..f9a33db
--- /dev/null
+++ b/packages/SystemUI/log/.gitignore
@@ -0,0 +1,9 @@
+.idea/
+.gradle/
+gradle/
+build/
+gradlew*
+local.properties
+*.iml
+android.properties
+buildSrc
\ No newline at end of file
diff --git a/packages/SystemUI/log/Android.bp b/packages/SystemUI/log/Android.bp
new file mode 100644
index 0000000..627ac4b
--- /dev/null
+++ b/packages/SystemUI/log/Android.bp
@@ -0,0 +1,38 @@
+// 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+ name: "SystemUILogLib",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "androidx.core_core-ktx",
+ "androidx.annotation_annotation",
+ "error_prone_annotations",
+ "SystemUICommon",
+ ],
+ manifest: "AndroidManifest.xml",
+ kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SystemUI/log/AndroidManifest.xml b/packages/SystemUI/log/AndroidManifest.xml
new file mode 100644
index 0000000..4021e1a
--- /dev/null
+++ b/packages/SystemUI/log/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemui.log">
+
+
+</manifest>
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/ConstantStringsLogger.kt b/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLogger.kt
similarity index 95%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/log/ConstantStringsLogger.kt
rename to packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLogger.kt
index f95b187..bc35095 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/ConstantStringsLogger.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLogger.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.plugins.log
+package com.android.systemui.log
import com.google.errorprone.annotations.CompileTimeConstant
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/ConstantStringsLoggerImpl.kt b/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt
similarity index 96%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/log/ConstantStringsLoggerImpl.kt
rename to packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt
index 91b39e6..6fc52536 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/ConstantStringsLoggerImpl.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.plugins.log
+package com.android.systemui.log
import com.google.errorprone.annotations.CompileTimeConstant
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
similarity index 99%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
rename to packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
index 0a7ccc5..af1a11f 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.plugins.log
+package com.android.systemui.log
import android.os.Trace
import android.util.Log
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogLevel.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogLevel.kt
similarity index 95%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogLevel.kt
rename to packages/SystemUI/log/src/com/android/systemui/log/LogLevel.kt
index b036cf0..7d9647a 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogLevel.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogLevel.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.plugins.log
+package com.android.systemui.log
import android.util.Log
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessage.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogMessage.kt
similarity index 98%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessage.kt
rename to packages/SystemUI/log/src/com/android/systemui/log/LogMessage.kt
index 9468681..88c34f8 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessage.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogMessage.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.plugins.log
+package com.android.systemui.log
import java.io.PrintWriter
import java.text.SimpleDateFormat
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessageImpl.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt
similarity index 97%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessageImpl.kt
rename to packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt
index f2a6a91..5e10f78 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogMessageImpl.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.plugins.log
+package com.android.systemui.log
/** Recyclable implementation of [LogMessage]. */
data class LogMessageImpl(
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTracker.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt
similarity index 96%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTracker.kt
rename to packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt
index cfe894f..55f3a73 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTracker.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.plugins.log
+package com.android.systemui.log
/** Keeps track of which [LogBuffer] messages should also appear in logcat. */
interface LogcatEchoTracker {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
similarity index 98%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt
rename to packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
index 7a125ac..d0ad28f 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.plugins.log
+package com.android.systemui.log
import android.content.ContentResolver
import android.database.ContentObserver
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerProd.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt
similarity index 95%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerProd.kt
rename to packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt
index 3c8bda4..56966773 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerProd.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.plugins.log
+package com.android.systemui.log
/** Production version of [LogcatEchoTracker] that isn't configurable. */
class LogcatEchoTrackerProd : LogcatEchoTracker {
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index e306d4a..fec093b 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -32,12 +32,14 @@
"bcsmartspace/src/**/*.kt",
],
+ // If you add a static lib here, you may need to also add the package to the ClassLoaderFilter
+ // in PluginInstance. That will ensure that loaded plugins have access to the related classes.
static_libs: [
"androidx.annotation_annotation",
- "error_prone_annotations",
"PluginCoreLib",
"SystemUIAnimationLib",
"SystemUICommon",
+ "SystemUILogLib",
],
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index ca3e710..7bf139e 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -18,8 +18,8 @@
import android.graphics.drawable.Drawable
import android.view.View
import com.android.internal.annotations.Keep
+import com.android.systemui.log.LogBuffer
import com.android.systemui.plugins.annotations.ProvidesInterface
-import com.android.systemui.plugins.log.LogBuffer
import java.io.PrintWriter
import java.util.Locale
import java.util.TimeZone
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 436145e..3244eb4 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 @@
/**
* 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/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 4fc411e..4d289eb 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_keyboard_backlight.xml b/packages/SystemUI/res/drawable/ic_keyboard_backlight.xml
index d123caf..cff2839 100644
--- a/packages/SystemUI/res/drawable/ic_keyboard_backlight.xml
+++ b/packages/SystemUI/res/drawable/ic_keyboard_backlight.xml
@@ -2,11 +2,11 @@
android:viewportWidth="22" android:width="20.166666dp" xmlns:android="http://schemas.android.com/apk/res/android">
<group>
<clip-path android:pathData="M0,0.5h22v11h-22z"/>
- <path android:fillColor="#231F20" android:pathData="M6.397,9.908H0V11.5H6.397V9.908Z"/>
- <path android:fillColor="#231F20" android:pathData="M14.199,9.908H7.801V11.5H14.199V9.908Z"/>
- <path android:fillColor="#231F20" android:pathData="M11.858,0.5H10.142V6.434H11.858V0.5Z"/>
- <path android:fillColor="#231F20" android:pathData="M8.348,7.129L3.885,2.975L3.823,2.932L2.668,4.003L2.621,4.046L7.084,8.2L7.146,8.243L8.301,7.172L8.348,7.129Z"/>
- <path android:fillColor="#231F20" android:pathData="M18.224,2.975L18.177,2.932L13.653,7.129L14.807,8.2L14.854,8.243L19.379,4.046L18.224,2.975Z"/>
- <path android:fillColor="#231F20" android:pathData="M22,9.908H15.603V11.5H22V9.908Z"/>
+ <path android:fillColor="@android:color/white" android:pathData="M6.397,9.908H0V11.5H6.397V9.908Z"/>
+ <path android:fillColor="@android:color/white" android:pathData="M14.199,9.908H7.801V11.5H14.199V9.908Z"/>
+ <path android:fillColor="@android:color/white" android:pathData="M11.858,0.5H10.142V6.434H11.858V0.5Z"/>
+ <path android:fillColor="@android:color/white" android:pathData="M8.348,7.129L3.885,2.975L3.823,2.932L2.668,4.003L2.621,4.046L7.084,8.2L7.146,8.243L8.301,7.172L8.348,7.129Z"/>
+ <path android:fillColor="@android:color/white" android:pathData="M18.224,2.975L18.177,2.932L13.653,7.129L14.807,8.2L14.854,8.243L19.379,4.046L18.224,2.975Z"/>
+ <path android:fillColor="@android:color/white" android:pathData="M22,9.908H15.603V11.5H22V9.908Z"/>
</group>
</vector>
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 ee8d4883..a35504f 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 7590182..860fc7d 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 0000000..9f98f07
--- /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 0000000..fcb3ef4
--- /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 441f963..386c9d6 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -14,12 +14,17 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
+
+<!--
+keep split_shade_status_bar height constant to avoid requestLayout calls on each
+frame when animating QS <-> QQS transition
+-->
<com.android.systemui.util.NoRemeasureMotionLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/split_shade_status_bar"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="@dimen/qs_header_height"
android:minHeight="@dimen/large_screen_shade_header_min_height"
android:clickable="false"
android:focusable="true"
@@ -30,6 +35,14 @@
app:layoutDescription="@xml/combined_qs_header_scene">
<androidx.constraintlayout.widget.Guideline
+ android:id="@+id/qqs_header_bottom_guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ app:layout_constraintGuide_begin="@dimen/large_screen_shade_header_min_height"
+ />
+
+ <androidx.constraintlayout.widget.Guideline
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/begin_guide"
diff --git a/packages/SystemUI/res/layout/smart_action_button.xml b/packages/SystemUI/res/layout/smart_action_button.xml
index 488be3a..4e5785d 100644
--- a/packages/SystemUI/res/layout/smart_action_button.xml
+++ b/packages/SystemUI/res/layout/smart_action_button.xml
@@ -29,8 +29,8 @@
android:textSize="@dimen/smart_reply_button_font_size"
android:lineSpacingExtra="@dimen/smart_reply_button_line_spacing_extra"
android:textColor="@color/smart_reply_button_text"
- android:paddingLeft="@dimen/smart_reply_button_action_padding_left"
- android:paddingRight="@dimen/smart_reply_button_padding_horizontal"
+ android:paddingStart="@dimen/smart_reply_button_action_padding_left"
+ android:paddingEnd="@dimen/smart_reply_button_padding_horizontal"
android:drawablePadding="@dimen/smart_action_button_icon_padding"
android:textStyle="normal"
android:ellipsize="none"/>
diff --git a/packages/SystemUI/res/layout/smart_reply_button.xml b/packages/SystemUI/res/layout/smart_reply_button.xml
index ddf16e0..b24362f 100644
--- a/packages/SystemUI/res/layout/smart_reply_button.xml
+++ b/packages/SystemUI/res/layout/smart_reply_button.xml
@@ -31,7 +31,7 @@
android:textSize="@dimen/smart_reply_button_font_size"
android:lineSpacingExtra="@dimen/smart_reply_button_line_spacing_extra"
android:textColor="@color/smart_reply_button_text"
- android:paddingLeft="@dimen/smart_reply_button_padding_horizontal"
- android:paddingRight="@dimen/smart_reply_button_padding_horizontal"
+ android:paddingStart="@dimen/smart_reply_button_padding_horizontal"
+ android:paddingEnd="@dimen/smart_reply_button_padding_horizontal"
android:textStyle="normal"
android:ellipsize="none"/>
diff --git a/packages/SystemUI/res/layout/udfps_keyguard_view_internal.xml b/packages/SystemUI/res/layout/udfps_keyguard_view_internal.xml
index 1838663..191158e 100644
--- a/packages/SystemUI/res/layout/udfps_keyguard_view_internal.xml
+++ b/packages/SystemUI/res/layout/udfps_keyguard_view_internal.xml
@@ -20,7 +20,8 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/udfps_animation_view_internal"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:contentDescription="@string/accessibility_fingerprint_label">
<!-- Background protection -->
<ImageView
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 0000000..49c1c40
--- /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 0000000..9ea0d35
--- /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 0000000..f2b2593
--- /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/colors.xml b/packages/SystemUI/res/values/colors.xml
index cb8c2a7..db7eb7a 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -207,11 +207,6 @@
<color name="controls_task_view_bg">#CC191C1D</color>
<color name="control_popup_dim">#8A000000</color>
- <!-- Keyboard backlight indicator-->
- <color name="backlight_indicator_step_filled">#F6E388</color>
- <color name="backlight_indicator_step_empty">#494740</color>
- <color name="backlight_indicator_background">#32302A</color>
-
<!-- Docked misalignment message -->
<color name="misalignment_text_color">#F28B82</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 1602189..1252695 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -889,4 +889,8 @@
-->
<dimen name="shade_swipe_collapse_threshold">0.5</dimen>
<!-- [END] MULTI SHADE -->
+
+ <!-- Time (in ms) to delay the bouncer views from showing when passive auth may be used for
+ device entry. -->
+ <integer name="primary_bouncer_passive_auth_delay">250</integer>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 0aa880f..aff0e80 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -615,6 +615,7 @@
<dimen name="qs_header_carrier_separator_width">6dp</dimen>
<dimen name="qs_carrier_margin_width">4dp</dimen>
<dimen name="qs_footer_icon_size">20dp</dimen>
+ <dimen name="qs_header_height">120dp</dimen>
<dimen name="qs_header_row_min_height">48dp</dimen>
<dimen name="qs_header_non_clickable_element_height">24dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 499dfa4..eaeaabe 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -198,6 +198,9 @@
<item type="id" name="pm_lite"/>
<item type="id" name="settings_button_container"/>
+ <!--Keyboard Backlight Dialog -->
+ <item type="id" name="keyboard_backlight_dialog_container"/>
+
<item type="id" name="log_access_dialog_allow_button" />
<item type="id" name="log_access_dialog_deny_button" />
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 853930e..c57fef1 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 the 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">Select app</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/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index 00a0444..1950965 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -28,7 +28,7 @@
android:layout_height="@dimen/large_screen_shade_header_min_height"
app:layout_constraintStart_toStartOf="@id/begin_guide"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/qqs_header_bottom_guideline"
app:layout_constraintEnd_toStartOf="@id/date"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintHorizontal_chainStyle="packed"
@@ -62,7 +62,7 @@
app:layout_constraintStart_toEndOf="@id/date"
app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/qqs_header_bottom_guideline"
app:layout_constraintHorizontal_bias="1"
app:layout_constraintHorizontal_chainStyle="packed"
/>
@@ -77,7 +77,7 @@
app:layout_constraintStart_toEndOf="@id/statusIcons"
app:layout_constraintEnd_toEndOf="@id/end_guide"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/qqs_header_bottom_guideline"
app:layout_constraintHorizontal_bias="1"
app:layout_constraintHorizontal_chainStyle="packed"
/>
@@ -105,7 +105,7 @@
app:layout_constraintStart_toEndOf="@id/date"
app:layout_constraintEnd_toEndOf="@id/end_guide"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/qqs_header_bottom_guideline"
app:layout_constraintHorizontal_bias="1"
/>
</Constraint>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt
index 2d83458..a2b6e2c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt
@@ -16,27 +16,179 @@
package com.android.systemui.shared.condition
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.launch
+
/**
* A higher order [Condition] which combines multiple conditions with a specified
- * [Evaluator.ConditionOperand].
+ * [Evaluator.ConditionOperand]. Conditions are executed lazily as-needed.
+ *
+ * @param scope The [CoroutineScope] to execute in.
+ * @param conditions The list of conditions to evaluate. Since conditions are executed lazily, the
+ * ordering is important here.
+ * @param operand The [Evaluator.ConditionOperand] to apply to the conditions.
*/
-internal class CombinedCondition
+@OptIn(ExperimentalCoroutinesApi::class)
+class CombinedCondition
constructor(
+ private val scope: CoroutineScope,
private val conditions: Collection<Condition>,
@Evaluator.ConditionOperand private val operand: Int
-) : Condition(null, false), Condition.Callback {
+) : Condition(scope, null, false) {
+
+ private var job: Job? = null
+ private val _startStrategy by lazy { calculateStartStrategy() }
override fun start() {
- onConditionChanged(this)
- conditions.forEach { it.addCallback(this) }
- }
+ job =
+ scope.launch {
+ val groupedConditions = conditions.groupBy { it.isOverridingCondition }
- override fun onConditionChanged(condition: Condition) {
- Evaluator.evaluate(conditions, operand)?.also { value -> updateCondition(value) }
- ?: clearCondition()
+ lazilyEvaluate(
+ conditions = groupedConditions.getOrDefault(true, emptyList()),
+ filterUnknown = true
+ )
+ .distinctUntilChanged()
+ .flatMapLatest { overriddenValue ->
+ // If there are overriding conditions with values set, they take precedence.
+ if (overriddenValue == null) {
+ lazilyEvaluate(
+ conditions = groupedConditions.getOrDefault(false, emptyList()),
+ filterUnknown = false
+ )
+ } else {
+ flowOf(overriddenValue)
+ }
+ }
+ .collect { conditionMet ->
+ if (conditionMet == null) {
+ clearCondition()
+ } else {
+ updateCondition(conditionMet)
+ }
+ }
+ }
}
override fun stop() {
- conditions.forEach { it.removeCallback(this) }
+ job?.cancel()
+ job = null
+ }
+
+ /**
+ * Evaluates a list of conditions lazily with support for short-circuiting. Conditions are
+ * executed serially in the order provided. At any point if the result can be determined, we
+ * short-circuit and return the result without executing all conditions.
+ */
+ private fun lazilyEvaluate(
+ conditions: Collection<Condition>,
+ filterUnknown: Boolean,
+ ): Flow<Boolean?> = callbackFlow {
+ val jobs = MutableList<Job?>(conditions.size) { null }
+ val values = MutableList<Boolean?>(conditions.size) { null }
+ val flows = conditions.map { it.toFlow() }
+
+ fun cancelAllExcept(indexToSkip: Int) {
+ for (index in 0 until jobs.size) {
+ if (index == indexToSkip) {
+ continue
+ }
+ if (
+ indexToSkip == -1 ||
+ conditions.elementAt(index).startStrategy == START_WHEN_NEEDED
+ ) {
+ jobs[index]?.cancel()
+ jobs[index] = null
+ values[index] = null
+ }
+ }
+ }
+
+ fun collectFlow(index: Int) {
+ // Base case which is triggered once we have collected all the flows. In this case,
+ // we never short-circuited and therefore should return the fully evaluated
+ // conditions.
+ if (flows.isEmpty() || index == -1) {
+ val filteredValues =
+ if (filterUnknown) {
+ values.filterNotNull()
+ } else {
+ values
+ }
+ trySend(Evaluator.evaluate(filteredValues, operand))
+ return
+ }
+ jobs[index] =
+ scope.launch {
+ flows.elementAt(index).collect { value ->
+ values[index] = value
+ if (shouldEarlyReturn(value)) {
+ trySend(value)
+ // The overall result is contingent on this condition, so we don't need
+ // to monitor any other conditions.
+ cancelAllExcept(index)
+ } else {
+ collectFlow(jobs.indexOfFirst { it == null })
+ }
+ }
+ }
+ }
+
+ // Collect any eager conditions immediately.
+ var started = false
+ for ((index, condition) in conditions.withIndex()) {
+ if (condition.startStrategy == START_EAGERLY) {
+ collectFlow(index)
+ started = true
+ }
+ }
+
+ // If no eager conditions started, start the first condition to kick off evaluation.
+ if (!started) {
+ collectFlow(0)
+ }
+ awaitClose { cancelAllExcept(-1) }
+ }
+
+ private fun shouldEarlyReturn(conditionMet: Boolean?): Boolean {
+ return when (operand) {
+ Evaluator.OP_AND -> conditionMet == false
+ Evaluator.OP_OR -> conditionMet == true
+ else -> false
+ }
+ }
+
+ /**
+ * Calculate the start strategy for this condition. This depends on the strategies of the child
+ * conditions. If there are any eager conditions, we must also start this condition eagerly. In
+ * the absence of eager conditions, we check for lazy conditions. In the absence of either, we
+ * make the condition only start when needed.
+ */
+ private fun calculateStartStrategy(): Int {
+ var startStrategy = START_WHEN_NEEDED
+ for (condition in conditions) {
+ when (condition.startStrategy) {
+ START_EAGERLY -> return START_EAGERLY
+ START_LAZILY -> {
+ startStrategy = START_LAZILY
+ }
+ START_WHEN_NEEDED -> {
+ // this is the default, so do nothing
+ }
+ }
+ }
+ return startStrategy
+ }
+
+ override fun getStartStrategy(): Int {
+ return _startStrategy
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java
index cc48090e..6bf1ce5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java
@@ -18,11 +18,14 @@
import android.util.Log;
+import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleEventObserver;
import androidx.lifecycle.LifecycleOwner;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
@@ -30,6 +33,8 @@
import java.util.Iterator;
import java.util.List;
+import kotlinx.coroutines.CoroutineScope;
+
/**
* Base class for a condition that needs to be fulfilled in order for {@link Monitor} to inform
* its callbacks.
@@ -39,24 +44,27 @@
private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
private final boolean mOverriding;
+ private final CoroutineScope mScope;
private Boolean mIsConditionMet;
private boolean mStarted = false;
/**
* By default, conditions have an initial value of false and are not overriding.
*/
- public Condition() {
- this(false, false);
+ public Condition(CoroutineScope scope) {
+ this(scope, false, false);
}
/**
* Constructor for specifying initial state and overriding condition attribute.
+ *
* @param initialConditionMet Initial state of the condition.
- * @param overriding Whether this condition overrides others.
+ * @param overriding Whether this condition overrides others.
*/
- protected Condition(Boolean initialConditionMet, boolean overriding) {
+ protected Condition(CoroutineScope scope, Boolean initialConditionMet, boolean overriding) {
mIsConditionMet = initialConditionMet;
mOverriding = overriding;
+ mScope = scope;
}
/**
@@ -70,6 +78,29 @@
protected abstract void stop();
/**
+ * Condition should be started as soon as there is an active subscription.
+ */
+ public static final int START_EAGERLY = 0;
+ /**
+ * Condition should be started lazily only if needed. But once started, it will not be cancelled
+ * unless there are no more active subscriptions.
+ */
+ public static final int START_LAZILY = 1;
+ /**
+ * Condition should be started lazily only if needed, and can be stopped when not needed. This
+ * should be used for conditions which are expensive to keep running.
+ */
+ public static final int START_WHEN_NEEDED = 2;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({START_EAGERLY, START_LAZILY, START_WHEN_NEEDED})
+ @interface StartStrategy {
+ }
+
+ @StartStrategy
+ protected abstract int getStartStrategy();
+
+ /**
* Returns whether the current condition overrides
*/
public boolean isOverridingCondition() {
@@ -183,6 +214,7 @@
/**
* Returns whether the condition is set. This method should be consulted to understand the
* value of {@link #isConditionMet()}.
+ *
* @return {@code true} if value is present, {@code false} otherwise.
*/
public boolean isConditionSet() {
@@ -210,17 +242,18 @@
* conditions are true.
*/
public Condition and(@NonNull Collection<Condition> others) {
- final List<Condition> conditions = new ArrayList<>(others);
+ final List<Condition> conditions = new ArrayList<>();
conditions.add(this);
- return new CombinedCondition(conditions, Evaluator.OP_AND);
+ conditions.addAll(others);
+ return new CombinedCondition(mScope, conditions, Evaluator.OP_AND);
}
/**
* Creates a new condition which will only be true when both this condition and the provided
* condition is true.
*/
- public Condition and(@NonNull Condition other) {
- return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_AND);
+ public Condition and(@NonNull Condition... others) {
+ return and(Arrays.asList(others));
}
/**
@@ -228,17 +261,18 @@
* provided conditions are true.
*/
public Condition or(@NonNull Collection<Condition> others) {
- final List<Condition> conditions = new ArrayList<>(others);
+ final List<Condition> conditions = new ArrayList<>();
conditions.add(this);
- return new CombinedCondition(conditions, Evaluator.OP_OR);
+ conditions.addAll(others);
+ return new CombinedCondition(mScope, conditions, Evaluator.OP_OR);
}
/**
* Creates a new condition which will only be true when either this condition or the provided
* condition is true.
*/
- public Condition or(@NonNull Condition other) {
- return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_OR);
+ public Condition or(@NonNull Condition... others) {
+ return or(Arrays.asList(others));
}
/**
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt
index 8f8bff8..84edc35 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/ConditionExtensions.kt
@@ -1,14 +1,22 @@
package com.android.systemui.shared.condition
+import com.android.systemui.shared.condition.Condition.StartStrategy
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
+import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
/** Converts a boolean flow to a [Condition] object which can be used with a [Monitor] */
@JvmOverloads
-fun Flow<Boolean>.toCondition(scope: CoroutineScope, initialValue: Boolean? = null): Condition {
- return object : Condition(initialValue, false) {
+fun Flow<Boolean>.toCondition(
+ scope: CoroutineScope,
+ @StartStrategy strategy: Int,
+ initialValue: Boolean? = null
+): Condition {
+ return object : Condition(scope, initialValue, false) {
var job: Job? = null
override fun start() {
@@ -19,5 +27,25 @@
job?.cancel()
job = null
}
+
+ override fun getStartStrategy() = strategy
}
}
+
+/** Converts a [Condition] to a boolean flow */
+fun Condition.toFlow(): Flow<Boolean?> {
+ return callbackFlow {
+ val callback =
+ Condition.Callback { condition ->
+ if (condition.isConditionSet) {
+ trySend(condition.isConditionMet)
+ } else {
+ trySend(null)
+ }
+ }
+ addCallback(callback)
+ callback.onConditionChanged(this@toFlow)
+ awaitClose { removeCallback(callback) }
+ }
+ .distinctUntilChanged()
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Evaluator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Evaluator.kt
index 454294f..584d978 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Evaluator.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Evaluator.kt
@@ -22,7 +22,7 @@
* Helper for evaluating a collection of [Condition] objects with a given
* [Evaluator.ConditionOperand]
*/
-internal object Evaluator {
+object Evaluator {
/** Operands for combining multiple conditions together */
@Retention(AnnotationRetention.SOURCE)
@IntDef(value = [OP_AND, OP_OR])
@@ -70,15 +70,31 @@
fun evaluate(conditions: Collection<Condition>, @ConditionOperand operand: Int): Boolean? {
if (conditions.isEmpty()) return null
// If there are overriding conditions with values set, they take precedence.
- val targetConditions =
+ val values: Collection<Boolean?> =
conditions
.filter { it.isConditionSet && it.isOverridingCondition }
.ifEmpty { conditions }
+ .map { condition ->
+ if (condition.isConditionSet) {
+ condition.isConditionMet
+ } else {
+ null
+ }
+ }
+ return evaluate(values = values, operand = operand)
+ }
+
+ /**
+ * Evaluates a set of booleans with a given operand
+ *
+ * @param operand The operand to use when evaluating.
+ * @return Either true or false if the value is known, or null if value is unknown
+ */
+ internal fun evaluate(values: Collection<Boolean?>, @ConditionOperand operand: Int): Boolean? {
+ if (values.isEmpty()) return null
return when (operand) {
- OP_AND ->
- threeValuedAndOrOr(conditions = targetConditions, returnValueIfAnyMatches = false)
- OP_OR ->
- threeValuedAndOrOr(conditions = targetConditions, returnValueIfAnyMatches = true)
+ OP_AND -> threeValuedAndOrOr(values = values, returnValueIfAnyMatches = false)
+ OP_OR -> threeValuedAndOrOr(values = values, returnValueIfAnyMatches = true)
else -> null
}
}
@@ -90,16 +106,16 @@
* any value is true.
*/
private fun threeValuedAndOrOr(
- conditions: Collection<Condition>,
+ values: Collection<Boolean?>,
returnValueIfAnyMatches: Boolean
): Boolean? {
var hasUnknown = false
- for (condition in conditions) {
- if (!condition.isConditionSet) {
+ for (value in values) {
+ if (value == null) {
hasUnknown = true
continue
}
- if (condition.isConditionMet == returnValueIfAnyMatches) {
+ if (value == returnValueIfAnyMatches) {
return returnValueIfAnyMatches
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
index 016d573..4a66562 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
@@ -230,7 +230,7 @@
private ClassLoader getParentClassLoader(ClassLoader baseClassLoader) {
return new PluginManagerImpl.ClassLoaderFilter(
- baseClassLoader, "com.android.systemui.plugin");
+ baseClassLoader, "com.android.systemui.log", "com.android.systemui.plugin");
}
/** Returns class loader specific for the given plugin. */
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
index 2f9f5b2..1e668b8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
@@ -248,19 +248,23 @@
// This allows plugins to include any libraries or copied code they want by only including
// classes from the plugin library.
static class ClassLoaderFilter extends ClassLoader {
- private final String mPackage;
+ private final String[] mPackages;
private final ClassLoader mBase;
- public ClassLoaderFilter(ClassLoader base, String pkg) {
+ ClassLoaderFilter(ClassLoader base, String... pkgs) {
super(ClassLoader.getSystemClassLoader());
mBase = base;
- mPackage = pkg;
+ mPackages = pkgs;
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
- if (!name.startsWith(mPackage)) super.loadClass(name, resolve);
- return mBase.loadClass(name);
+ for (String pkg : mPackages) {
+ if (name.startsWith(pkg)) {
+ return mBase.loadClass(name);
+ }
+ }
+ return super.loadClass(name, resolve);
}
}
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 362d7a9..7cf3121 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 @@
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 dd52cfb..c613afb 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 @@
}
@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 @@
}
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 0000000..af29b05
--- /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 0000000..2764a1f
--- /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/keyguard/BouncerKeyguardMessageArea.kt b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
index 62f4f22..a82f0e3 100644
--- a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
+++ b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
@@ -26,8 +26,8 @@
import android.graphics.Color
import android.util.AttributeSet
import android.view.View
+import com.android.app.animation.Interpolators
import com.android.settingslib.Utils
-import com.android.systemui.animation.Interpolators
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.TITLE
/** Displays security messages for the keyguard bouncer. */
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 8b87e2a..4bf7be6 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -42,14 +42,14 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
import com.android.systemui.log.dagger.KeyguardLargeClockLog
import com.android.systemui.log.dagger.KeyguardSmallClockLog
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockFaceController
import com.android.systemui.plugins.ClockTickRate
import com.android.systemui.plugins.WeatherData
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
import com.android.systemui.shared.regionsampling.RegionSampler
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardBouncerMessages.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardBouncerMessages.kt
new file mode 100644
index 0000000..f4145db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardBouncerMessages.kt
@@ -0,0 +1,323 @@
+/*
+ * 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.keyguard
+
+import android.annotation.IntDef
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FACE_LOCKED_OUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FINGERPRINT_LOCKED_OUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FACE_INPUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST
+import com.android.systemui.R.string.bouncer_face_not_recognized
+import com.android.systemui.R.string.keyguard_enter_password
+import com.android.systemui.R.string.keyguard_enter_pattern
+import com.android.systemui.R.string.keyguard_enter_pin
+import com.android.systemui.R.string.kg_bio_too_many_attempts_password
+import com.android.systemui.R.string.kg_bio_too_many_attempts_pattern
+import com.android.systemui.R.string.kg_bio_too_many_attempts_pin
+import com.android.systemui.R.string.kg_bio_try_again_or_password
+import com.android.systemui.R.string.kg_bio_try_again_or_pattern
+import com.android.systemui.R.string.kg_bio_try_again_or_pin
+import com.android.systemui.R.string.kg_face_locked_out
+import com.android.systemui.R.string.kg_fp_locked_out
+import com.android.systemui.R.string.kg_fp_not_recognized
+import com.android.systemui.R.string.kg_primary_auth_locked_out_password
+import com.android.systemui.R.string.kg_primary_auth_locked_out_pattern
+import com.android.systemui.R.string.kg_primary_auth_locked_out_pin
+import com.android.systemui.R.string.kg_prompt_after_dpm_lock
+import com.android.systemui.R.string.kg_prompt_after_user_lockdown_password
+import com.android.systemui.R.string.kg_prompt_after_user_lockdown_pattern
+import com.android.systemui.R.string.kg_prompt_after_user_lockdown_pin
+import com.android.systemui.R.string.kg_prompt_auth_timeout
+import com.android.systemui.R.string.kg_prompt_password_auth_timeout
+import com.android.systemui.R.string.kg_prompt_pattern_auth_timeout
+import com.android.systemui.R.string.kg_prompt_pin_auth_timeout
+import com.android.systemui.R.string.kg_prompt_reason_restart_password
+import com.android.systemui.R.string.kg_prompt_reason_restart_pattern
+import com.android.systemui.R.string.kg_prompt_reason_restart_pin
+import com.android.systemui.R.string.kg_prompt_unattended_update
+import com.android.systemui.R.string.kg_too_many_failed_attempts_countdown
+import com.android.systemui.R.string.kg_trust_agent_disabled
+import com.android.systemui.R.string.kg_unlock_with_password_or_fp
+import com.android.systemui.R.string.kg_unlock_with_pattern_or_fp
+import com.android.systemui.R.string.kg_unlock_with_pin_or_fp
+import com.android.systemui.R.string.kg_wrong_input_try_fp_suggestion
+import com.android.systemui.R.string.kg_wrong_password_try_again
+import com.android.systemui.R.string.kg_wrong_pattern_try_again
+import com.android.systemui.R.string.kg_wrong_pin_try_again
+
+typealias BouncerMessage = Pair<Int, Int>
+
+fun emptyBouncerMessage(): BouncerMessage = Pair(0, 0)
+
+@Retention(AnnotationRetention.SOURCE)
+@IntDef(
+ PROMPT_REASON_TIMEOUT,
+ PROMPT_REASON_DEVICE_ADMIN,
+ PROMPT_REASON_USER_REQUEST,
+ PROMPT_REASON_AFTER_LOCKOUT,
+ PROMPT_REASON_PREPARE_FOR_UPDATE,
+ PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT,
+ PROMPT_REASON_TRUSTAGENT_EXPIRED,
+ PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+ PROMPT_REASON_INCORRECT_FACE_INPUT,
+ PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT,
+ PROMPT_REASON_FACE_LOCKED_OUT,
+ PROMPT_REASON_FINGERPRINT_LOCKED_OUT,
+ PROMPT_REASON_DEFAULT,
+ PROMPT_REASON_NONE,
+ PROMPT_REASON_RESTART,
+ PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT,
+)
+annotation class BouncerPromptReason
+
+/**
+ * Helper method that provides the relevant bouncer message that should be shown for different
+ * scenarios indicated by [reason]. [securityMode] & [fpAllowedInBouncer] parameters are used to
+ * provide a more specific message.
+ */
+@JvmOverloads
+fun getBouncerMessage(
+ @BouncerPromptReason reason: Int,
+ securityMode: SecurityMode,
+ fpAllowedInBouncer: Boolean = false
+): BouncerMessage {
+ return when (reason) {
+ PROMPT_REASON_RESTART -> authRequiredAfterReboot(securityMode)
+ PROMPT_REASON_TIMEOUT -> authRequiredAfterPrimaryAuthTimeout(securityMode)
+ PROMPT_REASON_DEVICE_ADMIN -> authRequiredAfterAdminLockdown(securityMode)
+ PROMPT_REASON_USER_REQUEST -> authRequiredAfterUserLockdown(securityMode)
+ PROMPT_REASON_AFTER_LOCKOUT -> biometricLockout(securityMode)
+ PROMPT_REASON_PREPARE_FOR_UPDATE -> authRequiredForUnattendedUpdate(securityMode)
+ PROMPT_REASON_FINGERPRINT_LOCKED_OUT -> fingerprintUnlockUnavailable(securityMode)
+ PROMPT_REASON_FACE_LOCKED_OUT -> faceUnlockUnavailable(securityMode)
+ PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT ->
+ if (fpAllowedInBouncer) incorrectSecurityInputWithFingerprint(securityMode)
+ else incorrectSecurityInput(securityMode)
+ PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT ->
+ if (fpAllowedInBouncer) nonStrongAuthTimeoutWithFingerprintAllowed(securityMode)
+ else nonStrongAuthTimeout(securityMode)
+ PROMPT_REASON_TRUSTAGENT_EXPIRED ->
+ if (fpAllowedInBouncer) trustAgentDisabledWithFingerprintAllowed(securityMode)
+ else trustAgentDisabled(securityMode)
+ PROMPT_REASON_INCORRECT_FACE_INPUT ->
+ if (fpAllowedInBouncer) incorrectFaceInputWithFingerprintAllowed(securityMode)
+ else incorrectFaceInput(securityMode)
+ PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT -> incorrectFingerprintInput(securityMode)
+ PROMPT_REASON_DEFAULT ->
+ if (fpAllowedInBouncer) defaultMessageWithFingerprint(securityMode)
+ else defaultMessage(securityMode)
+ PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT -> primaryAuthLockedOut(securityMode)
+ else -> emptyBouncerMessage()
+ }
+}
+
+fun defaultMessage(securityMode: SecurityMode): BouncerMessage {
+ return when (securityMode) {
+ SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
+ SecurityMode.Password -> Pair(keyguard_enter_password, 0)
+ SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
+ else -> Pair(0, 0)
+ }
+}
+
+fun defaultMessageWithFingerprint(securityMode: SecurityMode): BouncerMessage {
+ return when (securityMode) {
+ SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, 0)
+ SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, 0)
+ SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, 0)
+ else -> Pair(0, 0)
+ }
+}
+
+fun incorrectSecurityInput(securityMode: SecurityMode): BouncerMessage {
+ return when (securityMode) {
+ SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, 0)
+ SecurityMode.Password -> Pair(kg_wrong_password_try_again, 0)
+ SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, 0)
+ else -> Pair(0, 0)
+ }
+}
+
+fun incorrectSecurityInputWithFingerprint(securityMode: SecurityMode): BouncerMessage {
+ return when (securityMode) {
+ SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, kg_wrong_input_try_fp_suggestion)
+ SecurityMode.Password -> Pair(kg_wrong_password_try_again, kg_wrong_input_try_fp_suggestion)
+ SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, kg_wrong_input_try_fp_suggestion)
+ else -> Pair(0, 0)
+ }
+}
+
+fun incorrectFingerprintInput(securityMode: SecurityMode): BouncerMessage {
+ return when (securityMode) {
+ SecurityMode.Pattern -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pattern)
+ SecurityMode.Password -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_password)
+ SecurityMode.PIN -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pin)
+ else -> Pair(0, 0)
+ }
+}
+
+fun incorrectFaceInput(securityMode: SecurityMode): BouncerMessage {
+ return when (securityMode) {
+ SecurityMode.Pattern -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pattern)
+ SecurityMode.Password -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_password)
+ SecurityMode.PIN -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pin)
+ else -> Pair(0, 0)
+ }
+}
+
+fun incorrectFaceInputWithFingerprintAllowed(securityMode: SecurityMode): BouncerMessage {
+ return when (securityMode) {
+ SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, bouncer_face_not_recognized)
+ SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, bouncer_face_not_recognized)
+ SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, bouncer_face_not_recognized)
+ else -> Pair(0, 0)
+ }
+}
+
+fun biometricLockout(securityMode: SecurityMode): BouncerMessage {
+ return when (securityMode) {
+ SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern)
+ SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password)
+ SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_bio_too_many_attempts_pin)
+ else -> Pair(0, 0)
+ }
+}
+
+fun authRequiredAfterReboot(securityMode: SecurityMode): BouncerMessage {
+ return when (securityMode) {
+ SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_reason_restart_pattern)
+ SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_reason_restart_password)
+ SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin)
+ else -> Pair(0, 0)
+ }
+}
+
+fun authRequiredAfterAdminLockdown(securityMode: SecurityMode): BouncerMessage {
+ return when (securityMode) {
+ SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_dpm_lock)
+ SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_dpm_lock)
+ SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock)
+ else -> Pair(0, 0)
+ }
+}
+
+fun authRequiredAfterUserLockdown(securityMode: SecurityMode): BouncerMessage {
+ return when (securityMode) {
+ SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_user_lockdown_pattern)
+ SecurityMode.Password ->
+ Pair(keyguard_enter_password, kg_prompt_after_user_lockdown_password)
+ SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin)
+ else -> Pair(0, 0)
+ }
+}
+
+fun authRequiredForUnattendedUpdate(securityMode: SecurityMode): BouncerMessage {
+ return when (securityMode) {
+ SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_unattended_update)
+ SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_unattended_update)
+ SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_unattended_update)
+ else -> Pair(0, 0)
+ }
+}
+
+fun authRequiredAfterPrimaryAuthTimeout(securityMode: SecurityMode): BouncerMessage {
+ return when (securityMode) {
+ SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_pattern_auth_timeout)
+ SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_password_auth_timeout)
+ SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout)
+ else -> Pair(0, 0)
+ }
+}
+
+fun nonStrongAuthTimeout(securityMode: SecurityMode): BouncerMessage {
+ return when (securityMode) {
+ SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_auth_timeout)
+ SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_auth_timeout)
+ SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_auth_timeout)
+ else -> Pair(0, 0)
+ }
+}
+
+fun nonStrongAuthTimeoutWithFingerprintAllowed(securityMode: SecurityMode): BouncerMessage {
+ return when (securityMode) {
+ SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_prompt_auth_timeout)
+ SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_prompt_auth_timeout)
+ SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_auth_timeout)
+ else -> Pair(0, 0)
+ }
+}
+
+fun faceUnlockUnavailable(securityMode: SecurityMode): BouncerMessage {
+ return when (securityMode) {
+ SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_face_locked_out)
+ SecurityMode.Password -> Pair(keyguard_enter_password, kg_face_locked_out)
+ SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_face_locked_out)
+ else -> Pair(0, 0)
+ }
+}
+
+fun fingerprintUnlockUnavailable(securityMode: SecurityMode): BouncerMessage {
+ return when (securityMode) {
+ SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_fp_locked_out)
+ SecurityMode.Password -> Pair(keyguard_enter_password, kg_fp_locked_out)
+ SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_fp_locked_out)
+ else -> Pair(0, 0)
+ }
+}
+
+fun trustAgentDisabled(securityMode: SecurityMode): BouncerMessage {
+ return when (securityMode) {
+ SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_trust_agent_disabled)
+ SecurityMode.Password -> Pair(keyguard_enter_password, kg_trust_agent_disabled)
+ SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_trust_agent_disabled)
+ else -> Pair(0, 0)
+ }
+}
+
+fun trustAgentDisabledWithFingerprintAllowed(securityMode: SecurityMode): BouncerMessage {
+ return when (securityMode) {
+ SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_trust_agent_disabled)
+ SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_trust_agent_disabled)
+ SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_trust_agent_disabled)
+ else -> Pair(0, 0)
+ }
+}
+
+fun primaryAuthLockedOut(securityMode: SecurityMode): BouncerMessage {
+ return when (securityMode) {
+ SecurityMode.Pattern ->
+ Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pattern)
+ SecurityMode.Password ->
+ Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_password)
+ SecurityMode.PIN ->
+ Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pin)
+ else -> Pair(0, 0)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index a6c782d..644a9bc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -15,12 +15,13 @@
import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
+import com.android.app.animation.Interpolators;
import com.android.keyguard.dagger.KeyguardStatusViewScope;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.LogLevel;
import com.android.systemui.plugins.ClockController;
-import com.android.systemui.plugins.log.LogBuffer;
-import com.android.systemui.plugins.log.LogLevel;
+import com.android.systemui.shared.clocks.DefaultClockController;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -46,6 +47,9 @@
public static final int LARGE = 0;
public static final int SMALL = 1;
+ // compensate for translation of parents subject to device screen
+ // In this case, the translation comes from KeyguardStatusView
+ public int screenOffsetYPadding = 0;
/** Returns a region for the large clock to position itself, based on the given parent. */
public static Rect getLargeClockRegion(ViewGroup parent) {
@@ -161,8 +165,18 @@
}
if (mLargeClockFrame.isLaidOut()) {
- mClock.getLargeClock().getEvents().onTargetRegionChanged(
- getLargeClockRegion(mLargeClockFrame));
+ Rect targetRegion = getLargeClockRegion(mLargeClockFrame);
+ if (mClock instanceof DefaultClockController) {
+ mClock.getLargeClock().getEvents().onTargetRegionChanged(
+ targetRegion);
+ } else {
+ mClock.getLargeClock().getEvents().onTargetRegionChanged(
+ new Rect(
+ targetRegion.left,
+ targetRegion.top - screenOffsetYPadding,
+ targetRegion.right,
+ targetRegion.bottom - screenOffsetYPadding));
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index a34c9fa..d8bf570 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -39,10 +39,10 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.LogLevel;
import com.android.systemui.log.dagger.KeyguardClockLog;
import com.android.systemui.plugins.ClockController;
-import com.android.systemui.plugins.log.LogBuffer;
-import com.android.systemui.plugins.log.LogLevel;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.shared.regionsampling.RegionSampler;
@@ -169,6 +169,16 @@
}
/**
+ * Used for status view to pass the screen offset from parent view
+ */
+ public void setLockscreenClockY(int clockY) {
+ if (mView.screenOffsetYPadding != clockY) {
+ mView.screenOffsetYPadding = clockY;
+ mView.updateClockTargetRegions();
+ }
+ }
+
+ /**
* Attach the controller to the view it relates to.
*/
@Override
@@ -394,13 +404,6 @@
PropertyAnimator.setProperty(mStatusArea, AnimatableProperty.TRANSLATION_X,
x, props, animate);
}
-
- }
-
- void updateKeyguardStatusViewOffset() {
- // updateClockTargetRegions will call onTargetRegionChanged
- // which will require the correct translationY property of keyguardStatusView after updating
- mView.updateClockTargetRegions();
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 0394754..58807e4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -16,6 +16,7 @@
package com.android.keyguard;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_APPEAR;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_DISAPPEAR;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
@@ -32,9 +33,9 @@
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
+import com.android.app.animation.Interpolators;
import com.android.settingslib.animation.DisappearAnimationUtils;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt;
/**
@@ -184,6 +185,7 @@
}
mAppearAnimator.setDuration(ANIMATION_DURATION);
mAppearAnimator.addUpdateListener(animation -> animate(animation.getAnimatedFraction()));
+ mAppearAnimator.addListener(getAnimationListener(CUJ_LOCKSCREEN_PIN_APPEAR));
mAppearAnimator.start();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 33bea02..1d7c35d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -45,11 +45,11 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.TextViewInputDisabler;
import com.android.systemui.DejankUtils;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
/**
* Displays an alphanumeric (latin-1) key entry for the user to enter
* an unlock password
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 0a91150..b4ddc9a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -34,9 +34,9 @@
import android.view.KeyEvent;
import android.view.View;
+import com.android.app.animation.Interpolators;
import com.android.internal.widget.LockscreenCredential;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import java.util.ArrayList;
import java.util.List;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index b88d85c..5cc0547 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -32,7 +32,7 @@
import static androidx.constraintlayout.widget.ConstraintSet.TOP;
import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT;
-import static com.android.systemui.animation.InterpolatorsAndroidX.DECELERATE_QUINT;
+import static com.android.app.animation.InterpolatorsAndroidX.DECELERATE_QUINT;
import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
import static java.lang.Integer.max;
@@ -87,6 +87,7 @@
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
+import com.android.app.animation.Interpolators;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
@@ -97,7 +98,6 @@
import com.android.settingslib.drawable.CircleFramedDrawable;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.shared.system.SysUiStatsLog;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index 22ad725..419303d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -67,6 +67,42 @@
int PROMPT_REASON_TRUSTAGENT_EXPIRED = 8;
/**
+ * Prompt that is shown when there is an incorrect primary authentication input.
+ */
+ int PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT = 9;
+
+ /**
+ * Prompt that is shown when there is an incorrect face biometric input.
+ */
+ int PROMPT_REASON_INCORRECT_FACE_INPUT = 10;
+
+ /**
+ * Prompt that is shown when there is an incorrect fingerprint biometric input.
+ */
+ int PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT = 11;
+
+ /**
+ * Prompt that is shown when face authentication is in locked out state.
+ */
+ int PROMPT_REASON_FACE_LOCKED_OUT = 12;
+
+ /**
+ * Prompt that is shown when fingerprint authentication is in locked out state.
+ */
+ int PROMPT_REASON_FINGERPRINT_LOCKED_OUT = 13;
+
+ /**
+ * Default prompt that is shown on the bouncer.
+ */
+ int PROMPT_REASON_DEFAULT = 14;
+
+ /**
+ * Prompt that is shown when primary authentication is in locked out state after too many
+ * attempts
+ */
+ int PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT = 15;
+
+ /**
* Reset the view and prepare to take input. This should do things like clearing the
* password or pattern and clear error messages.
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
index c9128e5..96ac8ad 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
@@ -26,9 +26,9 @@
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnimationUtils
+import com.android.app.animation.Interpolators
import com.android.internal.R.interpolator.fast_out_extra_slow_in
import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
/** Animates constraint layout changes for the security view. */
class KeyguardSecurityViewTransition : Transition() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index 65a7166..b4f124a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -44,11 +44,11 @@
import androidx.slice.widget.RowContent;
import androidx.slice.widget.SliceContent;
+import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
import com.android.settingslib.Utils;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.util.wakelock.KeepAwakeAnimationListener;
import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 0826f8a..794eeda 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -40,11 +40,11 @@
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
+import com.android.app.animation.Interpolators;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.keyguard.KeyguardClockSwitch.ClockSize;
import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ClockController;
@@ -215,6 +215,15 @@
}
/**
+ * Pass top margin from ClockPositionAlgorithm in NotificationPanelViewController
+ * Use for clock view in LS to compensate for top margin to align to the screen
+ * Regardless of translation from AOD and unlock gestures
+ */
+ public void setLockscreenClockY(int clockY) {
+ mKeyguardClockSwitchController.setLockscreenClockY(clockY);
+ }
+
+ /**
* Set whether the view accessibility importance mode.
*/
public void setStatusAccessibilityImportance(int mode) {
@@ -230,7 +239,6 @@
* Update position of the view with an optional animation
*/
public void updatePosition(int x, int y, float scale, boolean animate) {
- float oldY = mView.getY();
setProperty(AnimatableProperty.Y, y, animate);
ClockController clock = mKeyguardClockSwitchController.getClock();
@@ -246,10 +254,6 @@
setProperty(AnimatableProperty.SCALE_X, 1f, animate);
setProperty(AnimatableProperty.SCALE_Y, 1f, animate);
}
-
- if (oldY != y) {
- mKeyguardClockSwitchController.updateKeyguardStatusViewOffset();
- }
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 9573913..c1f70fb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -522,6 +522,14 @@
FACE_AUTH_TRIGGERED_TRUST_DISABLED);
}
+ mLogger.logTrustChanged(wasTrusted, enabled, userId);
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onTrustChanged(userId);
+ }
+ }
+
if (enabled) {
String message = null;
if (KeyguardUpdateMonitor.getCurrentUser() == userId
@@ -560,14 +568,6 @@
}
}
}
-
- mLogger.logTrustChanged(wasTrusted, enabled, userId);
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.onTrustChanged(userId);
- }
- }
}
/**
@@ -2884,7 +2884,19 @@
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
}
+ /**
+ * If the current state of the device allows for triggering active unlock. This does not
+ * include active unlock availability.
+ */
+ public boolean canTriggerActiveUnlockBasedOnDeviceState() {
+ return shouldTriggerActiveUnlock(/* shouldLog */ false);
+ }
+
private boolean shouldTriggerActiveUnlock() {
+ return shouldTriggerActiveUnlock(/* shouldLog */ true);
+ }
+
+ private boolean shouldTriggerActiveUnlock(boolean shouldLog) {
// Triggers:
final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant();
final boolean awakeKeyguard = mPrimaryBouncerFullyShown || mAlternateBouncerShowing
@@ -2914,19 +2926,21 @@
&& !mKeyguardGoingAway
&& !mSecureCameraLaunched;
- // Aggregate relevant fields for debug logging.
- logListenerModelData(
- new KeyguardActiveUnlockModel(
- System.currentTimeMillis(),
- user,
- shouldTriggerActiveUnlock,
- awakeKeyguard,
- mAuthInterruptActive,
- fpLockedOut,
- primaryAuthRequired,
- mSwitchingUser,
- triggerActiveUnlockForAssistant,
- userCanDismissLockScreen));
+ if (shouldLog) {
+ // Aggregate relevant fields for debug logging.
+ logListenerModelData(
+ new KeyguardActiveUnlockModel(
+ System.currentTimeMillis(),
+ user,
+ shouldTriggerActiveUnlock,
+ awakeKeyguard,
+ mAuthInterruptActive,
+ fpLockedOut,
+ primaryAuthRequired,
+ mSwitchingUser,
+ triggerActiveUnlockForAssistant,
+ userCanDismissLockScreen));
+ }
return shouldTriggerActiveUnlock;
}
@@ -4379,9 +4393,11 @@
*/
public void startBiometricWatchdog() {
if (mFaceManager != null && !isFaceAuthInteractorEnabled()) {
+ mLogger.scheduleWatchdog("face");
mFaceManager.scheduleWatchdog();
}
if (mFpm != null) {
+ mLogger.scheduleWatchdog("fingerprint");
mFpm.scheduleWatchdog();
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index 651c979..61af722 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -21,9 +21,9 @@
import android.util.Property;
import android.view.View;
-import com.android.systemui.animation.Interpolators;
-import com.android.systemui.plugins.log.LogBuffer;
-import com.android.systemui.plugins.log.LogLevel;
+import com.android.app.animation.Interpolators;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.LogLevel;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index c6c7113..7d76f12 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -37,7 +37,7 @@
import androidx.annotation.StyleRes;
-import com.android.systemui.animation.Interpolators;
+import com.android.app.animation.Interpolators;
/**
* Provides background color and radius animations for key pad buttons.
diff --git a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
index 4aeab97..4557b34 100644
--- a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
@@ -37,9 +37,9 @@
import androidx.core.graphics.drawable.DrawableCompat;
+import com.android.app.animation.Interpolators;
import com.android.settingslib.Utils;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
/**
* This class contains implementation for methods that will be used when user has set a
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index 6740375..2d0bf9c 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -27,9 +27,9 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.dagger.KeyguardClockLog;
import com.android.systemui.plugins.PluginManager;
-import com.android.systemui.plugins.log.LogBuffer;
import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.shared.clocks.DefaultClockProvider;
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
index 2bb75aa..e7295ef 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
@@ -17,9 +17,9 @@
package com.android.keyguard.logging
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
import com.android.systemui.log.dagger.BiometricLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
import javax.inject.Inject
/** Helper class for logging for [com.android.systemui.biometrics.FaceHelpMessageDeferral] */
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
index 20f9007..c00b2c6 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
@@ -18,11 +18,11 @@
import android.hardware.biometrics.BiometricSourceType
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.BiometricLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.INFO
import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER
import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_NONE
import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_ONLY_WAKE
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index 4d71a89..8b925b1 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -18,9 +18,9 @@
import com.android.systemui.biometrics.AuthRippleController
import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.KeyguardLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.KeyguardIndicationController
import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 1661806..c2d22c3 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -27,13 +27,13 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.keyguard.TrustGrantFlags
import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.ERROR
-import com.android.systemui.plugins.log.LogLevel.INFO
-import com.android.systemui.plugins.log.LogLevel.VERBOSE
-import com.android.systemui.plugins.log.LogLevel.WARNING
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.VERBOSE
+import com.android.systemui.log.LogLevel.WARNING
import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
@@ -67,8 +67,10 @@
"ActiveUnlock",
DEBUG,
{ int1 = wakeReason },
- { "Skip requesting active unlock from wake reason that doesn't trigger face auth" +
- " reason=${PowerManager.wakeReasonToString(int1)}" }
+ {
+ "Skip requesting active unlock from wake reason that doesn't trigger face auth" +
+ " reason=${PowerManager.wakeReasonToString(int1)}"
+ }
)
}
@@ -207,17 +209,27 @@
}
fun logFaceDetected(userId: Int, isStrongBiometric: Boolean) {
- logBuffer.log(TAG, DEBUG, {
- int1 = userId
- bool1 = isStrongBiometric
- }, {"Face detected: userId: $int1, isStrongBiometric: $bool1"})
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ int1 = userId
+ bool1 = isStrongBiometric
+ },
+ { "Face detected: userId: $int1, isStrongBiometric: $bool1" }
+ )
}
fun logFingerprintDetected(userId: Int, isStrongBiometric: Boolean) {
- logBuffer.log(TAG, DEBUG, {
- int1 = userId
- bool1 = isStrongBiometric
- }, {"Fingerprint detected: userId: $int1, isStrongBiometric: $bool1"})
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ int1 = userId
+ bool1 = isStrongBiometric
+ },
+ { "Fingerprint detected: userId: $int1, isStrongBiometric: $bool1" }
+ )
}
fun logFingerprintError(msgId: Int, originalErrMsg: String) {
@@ -669,13 +681,10 @@
}
fun logHandleBatteryUpdate(isInteresting: Boolean) {
- logBuffer.log(
- TAG,
- DEBUG,
- {
- bool1 = isInteresting
- },
- { "handleBatteryUpdate: $bool1" }
- )
+ logBuffer.log(TAG, DEBUG, { bool1 = isInteresting }, { "handleBatteryUpdate: $bool1" })
+ }
+
+ fun scheduleWatchdog(@CompileTimeConstant watchdogType: String) {
+ logBuffer.log(TAG, DEBUG, "Scheduling biometric watchdog for $watchdogType")
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
index 249b3fe..daafea8 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
@@ -18,9 +18,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.TrustModel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
/** Logging helper for trust repository. */
diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
index de82ca0..c1871e0 100644
--- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
+++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
@@ -36,7 +36,7 @@
import android.view.View
import androidx.annotation.VisibleForTesting
import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.util.asIndenting
import java.io.PrintWriter
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index a3e7d71..76086df 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -34,7 +34,7 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.Utils
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.biometrics.AuthController
import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -110,6 +110,10 @@
if (showScanningAnimNow == showScanningAnim) {
return
}
+ logger.cameraProtectionShownOrHidden(keyguardUpdateMonitor.isFaceDetectionRunning,
+ authController.isShowing,
+ show,
+ showScanningAnim)
showScanningAnim = showScanningAnimNow
updateProtectionBoundingPath()
// Delay the relayout until the end of the animation when hiding,
@@ -352,6 +356,7 @@
if (biometricSourceType == BiometricSourceType.FACE) {
post {
faceAuthSucceeded = true
+ logger.biometricEvent("biometricAuthenticated")
enableShowProtection(true)
}
}
@@ -372,6 +377,7 @@
if (biometricSourceType == BiometricSourceType.FACE) {
post {
faceAuthSucceeded = false
+ logger.biometricEvent("biometricFailed")
enableShowProtection(false)
}
}
@@ -385,6 +391,7 @@
if (biometricSourceType == BiometricSourceType.FACE) {
post {
faceAuthSucceeded = false
+ logger.biometricEvent("biometricError")
enableShowProtection(false)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index adc0412..ea0f343 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -90,6 +90,8 @@
import com.android.systemui.util.concurrency.ThreadFactory;
import com.android.systemui.util.settings.SecureSettings;
+import kotlin.Pair;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -98,8 +100,6 @@
import javax.inject.Inject;
-import kotlin.Pair;
-
/**
* An overlay that draws screen decorations in software (e.g for rounded corners or display cutout)
* for antialiasing and emulation purposes.
@@ -254,11 +254,13 @@
new CameraAvailabilityListener.CameraTransitionCallback() {
@Override
public void onApplyCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) {
+ mLogger.cameraProtectionEvent("onApplyCameraProtection");
showCameraProtection(protectionPath, bounds);
}
@Override
public void onHideCameraProtection() {
+ mLogger.cameraProtectionEvent("onHideCameraProtection");
hideCameraProtection();
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index aa94ad9..99d4662 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -43,8 +43,8 @@
import androidx.annotation.VisibleForTesting;
+import com.android.app.animation.Interpolators;
import com.android.internal.dynamicanimation.animation.SpringForce;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.FalsingManager;
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
index d6f0b59..d491975 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
@@ -32,8 +32,8 @@
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
+import com.android.app.animation.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
/**
* Visually discloses that contextual data was provided to an assistant.
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 0002ae9..2aac056 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -45,9 +45,9 @@
import androidx.annotation.StyleRes;
import androidx.annotation.VisibleForTesting;
+import com.android.app.animation.Interpolators;
import com.android.systemui.DualToneHandler;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.policy.BatteryController;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index 4b5c50f..5499d2c 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 @@
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_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 @@
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 @@
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 @@
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 @@
)
}
}
-
- 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 e04dd06..f330c34 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 @@
protected final int mTextColorHint;
private AuthPanelController mPanelController;
+
private PromptInfo mPromptInfo;
private boolean mRequireConfirmation;
private int mUserId;
@@ -266,11 +267,9 @@
/** 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 @@
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 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 @@
@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 aeebb01..ce85124 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -59,14 +59,16 @@
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
+import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.R;
-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 @@
// 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 @@
@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 @@
@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 @@
mBackgroundExecutor = bgExecutor;
mInteractionJankMonitor = jankMonitor;
mBiometricPromptInteractor = biometricPromptInteractor;
+ mAuthBiometricFingerprintViewModelProvider = authBiometricFingerprintViewModelProvider;
mCredentialViewModelProvider = credentialViewModelProvider;
// Inflate biometric view only if necessary.
@@ -386,6 +395,9 @@
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 @@
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 6eb3c70..fd9cee0 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.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 @@
// 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 @@
}
onDialogDismissed(reason);
}
-
@Inject
public AuthController(Context context,
Execution execution,
@@ -741,6 +744,8 @@
@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 @@
mLogContextInteractor = logContextInteractor;
mBiometricPromptInteractor = biometricPromptInteractor;
+ mAuthBiometricFingerprintViewModelProvider = authBiometricFingerprintViewModelProvider;
mCredentialViewModelProvider = credentialViewModelProvider;
mOrientationListener = new BiometricDisplayListener(
@@ -823,9 +829,9 @@
final Rect overlayBounds = new Rect(
0, /* left */
- 0, /* top */
+ mCachedDisplayInfo.getNaturalHeight() / 2, /* top */
mCachedDisplayInfo.getNaturalWidth(), /* right */
- mCachedDisplayInfo.getNaturalHeight() /* botom */);
+ mCachedDisplayInfo.getNaturalHeight() /* bottom */);
mUdfpsOverlayParams = new UdfpsOverlayParams(
mUdfpsBounds,
@@ -1299,7 +1305,7 @@
.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 d6ad4da..f5f4640 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 @@
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 @@
/** 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/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index d0ac296..6f0f633 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -32,12 +32,12 @@
import com.android.settingslib.udfps.UdfpsOverlayParams
import com.android.systemui.CoreStartable
import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogLevel
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.CircleReveal
import com.android.systemui.statusbar.LiftReveal
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index b007134..5ede16d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -28,7 +28,7 @@
import android.view.View
import android.view.animation.PathInterpolator
import com.android.internal.graphics.ColorUtils
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.surfaceeffects.ripple.RippleShader
private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 4319f01..962140f 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.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 @@
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 @@
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/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
index ef7dcb7..1dbafc6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
@@ -19,7 +19,7 @@
import android.graphics.PointF
import android.graphics.RectF
import com.android.systemui.Dumpable
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionListener
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 7a23759..5ee38c3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -553,6 +553,10 @@
+ mOverlay.getRequestId());
return false;
}
+ if (mLockscreenShadeTransitionController.getQSDragProgress() != 0f
+ || mPrimaryBouncerInteractor.isInTransit()) {
+ return false;
+ }
final TouchProcessorResult result = mTouchProcessor.processTouch(event, mActivePointerId,
mOverlayParams);
@@ -626,9 +630,8 @@
shouldPilfer = true;
}
- // Execute the pilfer, never pilfer if a vertical swipe is in progress
- if (shouldPilfer && mLockscreenShadeTransitionController.getQSDragProgress() == 0f
- && !mPrimaryBouncerInteractor.isInTransit()) {
+ // Execute the pilfer
+ if (shouldPilfer) {
mInputManager.pilferPointers(
mOverlay.getOverlayView().getViewRootImpl().getInputToken());
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index f876aff..d953a88 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -364,7 +364,12 @@
if (accessibilityManager.isTouchExplorationEnabled && isEnrollment) {
Rect(overlayParams.sensorBounds)
} else {
- Rect(overlayParams.overlayBounds)
+ Rect(
+ 0,
+ 0,
+ overlayParams.naturalDisplayWidth,
+ overlayParams.naturalDisplayHeight
+ )
}
} else {
Rect(overlayParams.sensorBounds)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index ba8e60a..52db4ab 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -40,9 +40,9 @@
import androidx.annotation.Nullable;
import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
+import com.android.app.animation.Interpolators;
import com.android.settingslib.Utils;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.airbnb.lottie.LottieAnimationView;
import com.airbnb.lottie.LottieProperty;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
index 935de02..9f5669f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -23,11 +23,11 @@
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.animation.Interpolators
import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.Interpolators
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt
index 0d08b43..39199d1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt
@@ -16,12 +16,12 @@
package com.android.systemui.biometrics
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.LogLevel.VERBOSE
+import com.android.systemui.log.LogLevel.WARNING
import com.android.systemui.log.dagger.UdfpsLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogLevel.ERROR
-import com.android.systemui.plugins.log.LogLevel.VERBOSE
-import com.android.systemui.plugins.log.LogLevel.WARNING
import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
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 f0b9f67..c831663 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.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 @@
@SysUISingleton
fun fingerprintRepository(impl: FingerprintPropertyRepositoryImpl):
FingerprintPropertyRepository
+ @Binds
+ @SysUISingleton
+ fun rearDisplayStateRepository(impl: RearDisplayStateRepositoryImpl): RearDisplayStateRepository
@Binds
@SysUISingleton
@@ -52,6 +59,10 @@
@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 0000000..d17d961
--- /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 0000000..c935aa2
--- /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 0000000..e776ab4
--- /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/binder/CredentialViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
index e2d36dc..9292bd7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
@@ -6,8 +6,8 @@
import android.widget.TextView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.animation.Interpolators
import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
import com.android.systemui.biometrics.AuthDialog
import com.android.systemui.biometrics.AuthPanelController
import com.android.systemui.biometrics.ui.CredentialPasswordView
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 0000000..617d80c
--- /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/bluetooth/BluetoothLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
index d99625a..96af42b 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
@@ -17,9 +17,9 @@
package com.android.systemui.bluetooth
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.BluetoothLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
/** Helper class for logging bluetooth events. */
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
index d27708f..5b3a982 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
@@ -20,11 +20,11 @@
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.INFO
-import com.android.systemui.plugins.log.LogMessage
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogMessage
import com.android.systemui.log.dagger.BroadcastDispatcherLog
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index 11ef749..7bf8f4d 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -30,9 +30,9 @@
import android.widget.ImageView;
import android.widget.TextView;
+import com.android.app.animation.Interpolators;
import com.android.settingslib.Utils;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.surfaceeffects.ripple.RippleShader;
import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardImageLoader.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardImageLoader.kt
new file mode 100644
index 0000000..0542e13
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardImageLoader.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.clipboardoverlay
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.net.Uri
+import android.util.Log
+import android.util.Size
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import java.io.IOException
+import java.util.function.Consumer
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import kotlinx.coroutines.withTimeoutOrNull
+
+class ClipboardImageLoader
+@Inject
+constructor(
+ private val context: Context,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Application private val mainScope: CoroutineScope
+) {
+ private val TAG: String = "ClipboardImageLoader"
+
+ suspend fun load(uri: Uri, timeoutMs: Long = 300) =
+ withTimeoutOrNull(timeoutMs) {
+ withContext(bgDispatcher) {
+ try {
+ val size = context.resources.getDimensionPixelSize(R.dimen.overlay_x_scale)
+ context.contentResolver.loadThumbnail(uri, Size(size, size * 4), null)
+ } catch (e: IOException) {
+ Log.e(TAG, "Thumbnail loading failed!", e)
+ null
+ }
+ }
+ }
+
+ fun loadAsync(uri: Uri, callback: Consumer<Bitmap?>) {
+ mainScope.launch { callback.accept(load(uri)) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 0aeab10..757ebf4 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -32,6 +32,7 @@
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
+import static com.android.systemui.flags.Flags.CLIPBOARD_IMAGE_TIMEOUT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -90,6 +91,7 @@
private final ClipboardOverlayUtils mClipboardUtils;
private final FeatureFlags mFeatureFlags;
private final Executor mBgExecutor;
+ private final ClipboardImageLoader mClipboardImageLoader;
private final ClipboardOverlayView mView;
@@ -109,6 +111,7 @@
private Runnable mOnUiUpdate;
+ private boolean mShowingUi;
private boolean mIsMinimized;
private ClipboardModel mClipboardModel;
@@ -175,9 +178,11 @@
FeatureFlags featureFlags,
ClipboardOverlayUtils clipboardUtils,
@Background Executor bgExecutor,
+ ClipboardImageLoader clipboardImageLoader,
UiEventLogger uiEventLogger) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
+ mClipboardImageLoader = clipboardImageLoader;
mClipboardLogger = new ClipboardLogger(uiEventLogger);
@@ -260,21 +265,42 @@
boolean shouldAnimate = !model.dataMatches(mClipboardModel) || wasExiting;
mClipboardModel = model;
mClipboardLogger.setClipSource(mClipboardModel.getSource());
- if (shouldAnimate) {
- reset();
- mClipboardLogger.setClipSource(mClipboardModel.getSource());
- if (shouldShowMinimized(mWindow.getWindowInsets())) {
- mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_MINIMIZED);
- mIsMinimized = true;
- mView.setMinimized(true);
- } else {
- mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_EXPANDED);
+ if (mFeatureFlags.isEnabled(CLIPBOARD_IMAGE_TIMEOUT)) {
+ if (shouldAnimate) {
+ reset();
+ mClipboardLogger.setClipSource(mClipboardModel.getSource());
+ if (shouldShowMinimized(mWindow.getWindowInsets())) {
+ mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_MINIMIZED);
+ mIsMinimized = true;
+ mView.setMinimized(true);
+ } else {
+ mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_EXPANDED);
+ setExpandedView(this::animateIn);
+ }
+ mView.announceForAccessibility(
+ getAccessibilityAnnouncement(mClipboardModel.getType()));
+ } else if (!mIsMinimized) {
+ setExpandedView(() -> {
+ });
+ }
+ } else {
+ if (shouldAnimate) {
+ reset();
+ mClipboardLogger.setClipSource(mClipboardModel.getSource());
+ if (shouldShowMinimized(mWindow.getWindowInsets())) {
+ mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_MINIMIZED);
+ mIsMinimized = true;
+ mView.setMinimized(true);
+ } else {
+ mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_EXPANDED);
+ setExpandedView();
+ animateIn();
+ }
+ mView.announceForAccessibility(
+ getAccessibilityAnnouncement(mClipboardModel.getType()));
+ } else if (!mIsMinimized) {
setExpandedView();
}
- animateIn();
- mView.announceForAccessibility(getAccessibilityAnnouncement(mClipboardModel.getType()));
- } else if (!mIsMinimized) {
- setExpandedView();
}
if (mClipboardModel.isRemote()) {
mTimeoutHandler.cancelTimeout();
@@ -285,6 +311,58 @@
}
}
+ private void setExpandedView(Runnable onViewReady) {
+ final ClipboardModel model = mClipboardModel;
+ mView.setMinimized(false);
+ switch (model.getType()) {
+ case TEXT:
+ if (model.isRemote() || DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) {
+ if (model.getTextLinks() != null) {
+ classifyText(model);
+ }
+ }
+ if (model.isSensitive()) {
+ mView.showTextPreview(mContext.getString(R.string.clipboard_asterisks), true);
+ } else {
+ mView.showTextPreview(model.getText().toString(), false);
+ }
+ mView.setEditAccessibilityAction(true);
+ mOnPreviewTapped = this::editText;
+ onViewReady.run();
+ break;
+ case IMAGE:
+ mView.setEditAccessibilityAction(true);
+ mOnPreviewTapped = () -> editImage(model.getUri());
+ if (model.isSensitive()) {
+ mView.showImagePreview(null);
+ onViewReady.run();
+ } else {
+ mClipboardImageLoader.loadAsync(model.getUri(), (bitmap) -> mView.post(() -> {
+ if (bitmap == null) {
+ mView.showDefaultTextPreview();
+ } else {
+ mView.showImagePreview(bitmap);
+ }
+ onViewReady.run();
+ }));
+ }
+ break;
+ case URI:
+ case OTHER:
+ mView.showDefaultTextPreview();
+ onViewReady.run();
+ break;
+ }
+ if (!model.isRemote()) {
+ maybeShowRemoteCopy(model.getClipData());
+ }
+ if (model.getType() != ClipboardModel.Type.OTHER) {
+ mOnShareTapped = () -> shareContent(model.getClipData());
+ mView.showShareChip();
+ }
+ }
+
private void setExpandedView() {
final ClipboardModel model = mClipboardModel;
mView.setMinimized(false);
@@ -350,8 +428,12 @@
mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED);
mIsMinimized = false;
}
- setExpandedView();
- animateIn();
+ if (mFeatureFlags.isEnabled(CLIPBOARD_IMAGE_TIMEOUT)) {
+ setExpandedView(() -> animateIn());
+ } else {
+ setExpandedView();
+ animateIn();
+ }
}
});
mEnterAnimator.start();
@@ -412,7 +494,8 @@
mInputMonitor.getInputChannel(), Looper.getMainLooper()) {
@Override
public void onInputEvent(InputEvent event) {
- if (event instanceof MotionEvent) {
+ if ((!mFeatureFlags.isEnabled(CLIPBOARD_IMAGE_TIMEOUT) || mShowingUi)
+ && event instanceof MotionEvent) {
MotionEvent motionEvent = (MotionEvent) event;
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
if (!mView.isInTouchRegion(
@@ -452,6 +535,12 @@
mEnterAnimator = mView.getEnterAnimation();
mEnterAnimator.addListener(new AnimatorListenerAdapter() {
@Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ mShowingUi = true;
+ }
+
+ @Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (mOnUiUpdate != null) {
@@ -518,6 +607,7 @@
mOnRemoteCopyTapped = null;
mOnShareTapped = null;
mOnPreviewTapped = null;
+ mShowingUi = false;
mView.reset();
mTimeoutHandler.cancelTimeout();
mClipboardLogger.reset();
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt
similarity index 61%
copy from services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java
copy to packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt
index 7c339d2..9b0c3fa 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt
@@ -14,14 +14,12 @@
* limitations under the License.
*/
-package com.android.server.companion.datatransfer.contextsync;
+package com.android.systemui.common.ui.data.repository
-/** Callback for call metadata syncing. */
-public abstract class CallMetadataSyncCallback {
+import dagger.Binds
+import dagger.Module
- abstract void processCallControlAction(int crossDeviceCallId, int callControlAction);
-
- abstract void requestCrossDeviceSync(int userId);
-
- abstract void updateStatus(int userId, boolean shouldSyncCallMetadata);
+@Module
+interface CommonRepositoryModule {
+ @Binds fun bindRepository(impl: ConfigurationRepositoryImpl): ConfigurationRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
new file mode 100644
index 0000000..3e6ac86
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.common.ui.data.repository
+
+import android.content.Context
+import android.content.res.Configuration
+import android.view.DisplayInfo
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.wrapper.DisplayUtilsWrapper
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+
+interface ConfigurationRepository {
+ /** Called whenever ui mode, theme or configuration has changed. */
+ val onAnyConfigurationChange: Flow<Unit>
+ val scaleForResolution: Flow<Float>
+
+ fun getResolutionScale(): Float
+}
+
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class ConfigurationRepositoryImpl
+@Inject
+constructor(
+ private val configurationController: ConfigurationController,
+ private val context: Context,
+ @Application private val scope: CoroutineScope,
+ private val displayUtils: DisplayUtilsWrapper,
+) : ConfigurationRepository {
+ private val displayInfo = MutableStateFlow(DisplayInfo())
+
+ override val onAnyConfigurationChange: Flow<Unit> =
+ ConflatedCallbackFlow.conflatedCallbackFlow {
+ val callback =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onUiModeChanged() {
+ sendUpdate("ConfigurationRepository#onUiModeChanged")
+ }
+
+ override fun onThemeChanged() {
+ sendUpdate("ConfigurationRepository#onThemeChanged")
+ }
+
+ override fun onConfigChanged(newConfig: Configuration) {
+ sendUpdate("ConfigurationRepository#onConfigChanged")
+ }
+
+ fun sendUpdate(reason: String) {
+ trySendWithFailureLogging(Unit, reason)
+ }
+ }
+ configurationController.addCallback(callback)
+ awaitClose { configurationController.removeCallback(callback) }
+ }
+
+ private val configurationChange: Flow<Unit> =
+ ConflatedCallbackFlow.conflatedCallbackFlow {
+ val callback =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration) {
+ trySendWithFailureLogging(Unit, "ConfigurationRepository#onConfigChanged")
+ }
+ }
+ configurationController.addCallback(callback)
+ awaitClose { configurationController.removeCallback(callback) }
+ }
+
+ override val scaleForResolution: StateFlow<Float> =
+ configurationChange
+ .mapLatest { getResolutionScale() }
+ .distinctUntilChanged()
+ .stateIn(scope, SharingStarted.WhileSubscribed(), getResolutionScale())
+
+ override fun getResolutionScale(): Float {
+ context.display.getDisplayInfo(displayInfo.value)
+ val maxDisplayMode =
+ displayUtils.getMaximumResolutionDisplayMode(displayInfo.value.supportedModes)
+ maxDisplayMode?.let {
+ val scaleFactor =
+ displayUtils.getPhysicalPixelDisplaySizeRatio(
+ maxDisplayMode.physicalWidth,
+ maxDisplayMode.physicalHeight,
+ displayInfo.value.naturalWidth,
+ displayInfo.value.naturalHeight
+ )
+ return if (scaleFactor == Float.POSITIVE_INFINITY) 1f else scaleFactor
+ }
+ return 1f
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
index 8d0edf8..b447d66 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
@@ -32,7 +32,7 @@
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.controls.ui.ControlsUiController
object ControlsAnimations {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index 6a9aaf8..e6361f4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -50,7 +50,7 @@
import androidx.annotation.VisibleForTesting
import com.android.internal.graphics.ColorUtils
import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.util.concurrency.DelayableExecutor
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
index fa36eee..1461135 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
@@ -38,7 +38,7 @@
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.controls.ui.ControlViewHolder.Companion.MAX_LEVEL
import com.android.systemui.controls.ui.ControlViewHolder.Companion.MIN_LEVEL
import java.util.IllegalFormatException
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 8e6e0dd..17cf808 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -39,6 +39,7 @@
import com.android.systemui.biometrics.dagger.UdfpsModule;
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
+import com.android.systemui.common.ui.data.repository.CommonRepositoryModule;
import com.android.systemui.complication.dagger.ComplicationComponent;
import com.android.systemui.controls.dagger.ControlsModule;
import com.android.systemui.dagger.qualifiers.Main;
@@ -68,6 +69,7 @@
import com.android.systemui.qs.QSFragmentStartableModule;
import com.android.systemui.qs.footer.dagger.FooterActionsModule;
import com.android.systemui.recents.Recents;
+import com.android.systemui.retail.dagger.RetailModeModule;
import com.android.systemui.screenrecord.ScreenRecordModule;
import com.android.systemui.screenshot.dagger.ScreenshotModule;
import com.android.systemui.security.data.repository.SecurityRepositoryModule;
@@ -155,6 +157,7 @@
ClipboardOverlayModule.class,
ClockInfoModule.class,
ClockRegistryModule.class,
+ CommonRepositoryModule.class,
ConnectivityModule.class,
CoroutinesModule.class,
DreamModule.class,
@@ -177,6 +180,7 @@
PrivacyModule.class,
QRCodeScannerModule.class,
QSFragmentStartableModule.class,
+ RetailModeModule.class,
ScreenshotModule.class,
SensorModule.class,
SecurityRepositoryModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index d19c6ec..5369780 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -19,10 +19,10 @@
import android.view.Display
import com.android.systemui.doze.DozeLog.Reason
import com.android.systemui.doze.DozeLog.reasonToString
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.ERROR
-import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.DozeLog
import com.android.systemui.statusbar.policy.DevicePostureController
import com.google.errorprone.annotations.CompileTimeConstant
diff --git a/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt b/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt
index d853e04..1057852 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt
@@ -24,4 +24,8 @@
fun burnInOffset(amplitude: Int, xAxis: Boolean): Int {
return getBurnInOffset(amplitude, xAxis)
}
+
+ fun burnInProgressOffset(): Float {
+ return getBurnInProgressOffset()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt
index eb79290..fdb7651 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.dreams
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.DreamLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
/** Logs dream-related stuff to a {@link LogBuffer}. */
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index df46e07..c5e7e0d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -25,8 +25,8 @@
import androidx.core.animation.doOnEnd
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.animation.Interpolators
import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
import com.android.systemui.complication.ComplicationHostViewController
import com.android.systemui.complication.ComplicationLayoutParams
import com.android.systemui.complication.ComplicationLayoutParams.POSITION_BOTTOM
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 15a32d2..c22019e 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -33,9 +33,9 @@
import androidx.annotation.NonNull;
+import com.android.app.animation.Interpolators;
import com.android.dream.lowlight.LowLightTransitionCoordinator;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.complication.ComplicationHostViewController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
index 250cfec..c889ac2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
@@ -18,11 +18,14 @@
import com.android.internal.app.AssistUtils;
import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.shared.condition.Condition;
import javax.inject.Inject;
+import kotlinx.coroutines.CoroutineScope;
+
/**
* {@link AssistantAttentionCondition} provides a signal when assistant has the user's attention.
*/
@@ -58,8 +61,10 @@
@Inject
public AssistantAttentionCondition(
+ @Application CoroutineScope scope,
DreamOverlayStateController dreamOverlayStateController,
AssistUtils assistUtils) {
+ super(scope);
mDreamOverlayStateController = dreamOverlayStateController;
mAssistUtils = assistUtils;
}
@@ -75,6 +80,11 @@
mDreamOverlayStateController.removeCallback(mCallback);
}
+ @Override
+ protected int getStartStrategy() {
+ return START_EAGERLY;
+ }
+
private void enableVisualQueryDetection() {
if (mEnabled) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
index 3ef19b7..99688be 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
@@ -19,19 +19,20 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.shared.condition.Condition;
import javax.inject.Inject;
+import kotlinx.coroutines.CoroutineScope;
+
/**
* {@link DreamCondition} provides a signal when a dream begins and ends.
*/
public class DreamCondition extends Condition {
private final DreamManager mDreamManager;
-
private final KeyguardUpdateMonitor mUpdateMonitor;
-
private final KeyguardUpdateMonitorCallback mUpdateCallback =
new KeyguardUpdateMonitorCallback() {
@Override
@@ -41,7 +42,11 @@
};
@Inject
- public DreamCondition(DreamManager dreamManager, KeyguardUpdateMonitor monitor) {
+ public DreamCondition(
+ @Application CoroutineScope scope,
+ DreamManager dreamManager,
+ KeyguardUpdateMonitor monitor) {
+ super(scope);
mDreamManager = dreamManager;
mUpdateMonitor = monitor;
}
@@ -56,4 +61,9 @@
protected void stop() {
mUpdateMonitor.removeCallback(mUpdateCallback);
}
+
+ @Override
+ protected int getStartStrategy() {
+ return START_EAGERLY;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
index a2f65ba..4b03fd3 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
@@ -24,7 +24,7 @@
import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_CRITICAL
import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_NORMAL
import com.android.systemui.dump.nano.SystemUIProtoDump
-import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.log.LogBuffer
import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager
import com.google.protobuf.nano.MessageNano
import java.io.BufferedOutputStream
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index 7d1ffca..2d57633 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -16,12 +16,12 @@
package com.android.systemui.dump
-import android.util.ArrayMap
import com.android.systemui.Dumpable
import com.android.systemui.ProtoDumpable
import com.android.systemui.dump.nano.SystemUIProtoDump
-import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.log.LogBuffer
import java.io.PrintWriter
+import java.util.TreeMap
import javax.inject.Inject
import javax.inject.Singleton
@@ -36,8 +36,9 @@
*/
@Singleton
open class DumpManager @Inject constructor() {
- private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = ArrayMap()
- private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = ArrayMap()
+ // NOTE: Using TreeMap ensures that iteration is in a predictable & alphabetical order.
+ private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = TreeMap()
+ private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = TreeMap()
/** See [registerCriticalDumpable]. */
fun registerCriticalDumpable(module: Dumpable) {
@@ -132,7 +133,8 @@
}
/**
- * Dumps the first dumpable or buffer whose registered name ends with [target]
+ * Dumps the alphabetically first, shortest-named dumpable or buffer whose registered name ends
+ * with [target].
*/
@Synchronized
fun dumpTarget(
@@ -141,19 +143,14 @@
args: Array<String>,
tailLength: Int,
) {
- for (dumpable in dumpables.values) {
- if (dumpable.name.endsWith(target)) {
- dumpDumpable(dumpable, pw, args)
- return
+ sequence {
+ findBestTargetMatch(dumpables, target)?.let {
+ yield(it.name to { dumpDumpable(it, pw, args) })
}
- }
-
- for (buffer in buffers.values) {
- if (buffer.name.endsWith(target)) {
- dumpBuffer(buffer, pw, tailLength)
- return
+ findBestTargetMatch(buffers, target)?.let {
+ yield(it.name to { dumpBuffer(it, pw, tailLength) })
}
- }
+ }.sortedBy { it.first }.minByOrNull { it.first.length }?.second?.invoke()
}
@Synchronized
@@ -162,11 +159,8 @@
protoDump: SystemUIProtoDump,
args: Array<String>
) {
- for (dumpable in dumpables.values) {
- if (dumpable.dumpable is ProtoDumpable && dumpable.name.endsWith(target)) {
- dumpProtoDumpable(dumpable.dumpable, protoDump, args)
- return
- }
+ findBestProtoTargetMatch(dumpables, target)?.let {
+ dumpProtoDumpable(it, protoDump, args)
}
}
@@ -303,6 +297,22 @@
val existingDumpable = dumpables[name]?.dumpable ?: buffers[name]?.dumpable
return existingDumpable == null || newDumpable == existingDumpable
}
+
+ private fun <V : Any> findBestTargetMatch(map: Map<String, V>, target: String): V? = map
+ .asSequence()
+ .filter { it.key.endsWith(target) }
+ .minByOrNull { it.key.length }
+ ?.value
+
+ private fun findBestProtoTargetMatch(
+ map: Map<String, RegisteredDumpable<Dumpable>>,
+ target: String
+ ): ProtoDumpable? = map
+ .asSequence()
+ .filter { it.key.endsWith(target) }
+ .filter { it.value.dumpable is ProtoDumpable }
+ .minByOrNull { it.key.length }
+ ?.value?.dumpable as? ProtoDumpable
}
private data class RegisteredDumpable<T>(
diff --git a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
index 8299b13..0eab1af 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
@@ -19,7 +19,7 @@
import android.content.Context
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.log.LogBuffer
import com.android.systemui.util.io.Files
import com.android.systemui.util.time.SystemClock
import java.io.IOException
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 75eab82..873755f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -61,6 +61,10 @@
// 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", teamfood = true)
+
/**
* This flag is server-controlled and should stay as [unreleasedFlag] since we never want to
* enable it on release builds.
@@ -85,7 +89,7 @@
// TODO(b/277338665): Tracking Bug
@JvmField
val NOTIFICATION_SHELF_REFACTOR =
- unreleasedFlag(271161129, "notification_shelf_refactor")
+ unreleasedFlag(271161129, "notification_shelf_refactor", teamfood = true)
@JvmField
val ANIMATED_NOTIFICATION_SHADE_INSETS =
@@ -203,11 +207,6 @@
)
/** 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")
@@ -233,6 +232,11 @@
val REVAMPED_BOUNCER_MESSAGES =
unreleasedFlag(234, "revamped_bouncer_messages")
+ /** Whether to delay showing bouncer UI when face auth or active unlock are enrolled. */
+ // TODO(b/279794160): Tracking bug.
+ @JvmField
+ val DELAY_BOUNCER = unreleasedFlag(235, "delay_bouncer")
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -271,7 +275,7 @@
)
@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 +392,7 @@
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 +402,10 @@
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")
@@ -597,6 +601,8 @@
// 1700 - clipboard
@JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
+ // TODO(b/278714186) Tracking Bug
+ @JvmField val CLIPBOARD_IMAGE_TIMEOUT = unreleasedFlag(1702, "clipboard_image_timeout")
// 1800 - shade container
// TODO(b/265944639): Tracking Bug
@@ -620,7 +626,7 @@
// TODO(b/269132640): Tracking Bug
@JvmField
val APP_PANELS_REMOVE_APPS_ALLOWED =
- unreleasedFlag(2003, "app_panels_remove_apps_allowed", teamfood = true)
+ releasedFlag(2003, "app_panels_remove_apps_allowed")
// 2200 - biometrics (udfps, sfps, BiometricPrompt, etc.)
// TODO(b/259264861): Tracking Bug
@@ -696,6 +702,5 @@
// TODO(b/278761837): Tracking Bug
@JvmField
- val USE_NEW_ACTIVITY_STARTER = unreleasedFlag(2801, name = "use_new_activity_starter",
- teamfood = true)
+ val USE_NEW_ACTIVITY_STARTER = releasedFlag(2801, name = "use_new_activity_starter")
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index d3b6fc2..2807107 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -97,6 +97,7 @@
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
+import com.android.app.animation.Interpolators;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
@@ -116,7 +117,6 @@
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.animation.Expandable;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Background;
@@ -128,6 +128,7 @@
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 @@
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 @@
MetricsLogger metricsLogger,
SysuiColorExtractor colorExtractor,
IStatusBarService statusBarService,
+ LightBarController lightBarController,
NotificationShadeWindowController notificationShadeWindowController,
IWindowManager iWindowManager,
@Background Executor backgroundExecutor,
@@ -381,6 +384,7 @@
mUiEventLogger = uiEventLogger;
mSysuiColorExtractor = colorExtractor;
mStatusBarService = statusBarService;
+ mLightBarController = lightBarController;
mNotificationShadeWindowController = notificationShadeWindowController;
mIWindowManager = iWindowManager;
mBackgroundExecutor = backgroundExecutor;
@@ -694,6 +698,7 @@
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 @@
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 @@
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 @@
mPowerOptionsAdapter = powerAdapter;
mColorExtractor = sysuiColorExtractor;
mStatusBarService = statusBarService;
+ mLightBarController = lightBarController;
mNotificationShadeWindowController = notificationShadeWindowController;
mOnRefreshCallback = onRefreshCallback;
mKeyguardShowing = keyguardShowing;
@@ -2474,6 +2482,7 @@
@Override
protected void start() {
mGlobalActionsLayout.updateList();
+ mLightBarController.setGlobalActionsVisible(true);
if (mBackgroundDrawable instanceof ScrimDrawable) {
mColorExtractor.addOnColorsChangedListener(this);
@@ -2504,6 +2513,7 @@
@Override
protected void stop() {
+ mLightBarController.setGlobalActionsVisible(false);
mColorExtractor.removeOnColorsChangedListener(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
index 2ef5e19..6bc763c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyboard.backlight.ui.view
+import android.annotation.AttrRes
import android.annotation.ColorInt
import android.app.Dialog
import android.content.Context
@@ -31,6 +32,7 @@
import android.widget.LinearLayout
import android.widget.LinearLayout.LayoutParams
import android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
+import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.util.children
@@ -38,7 +40,7 @@
context: Context,
initialCurrentLevel: Int,
initialMaxLevel: Int,
-) : Dialog(context) {
+) : Dialog(context, R.style.Theme_SystemUI_Dialog) {
private data class RootProperties(
val cornerRadius: Float,
@@ -69,9 +71,14 @@
private lateinit var rootProperties: RootProperties
private lateinit var iconProperties: BacklightIconProperties
private lateinit var stepProperties: StepViewProperties
- @ColorInt var filledRectangleColor: Int = 0
- @ColorInt var emptyRectangleColor: Int = 0
- @ColorInt var backgroundColor: Int = 0
+ @ColorInt
+ var filledRectangleColor = getColorFromStyle(com.android.internal.R.attr.materialColorPrimary)
+ @ColorInt
+ var emptyRectangleColor =
+ getColorFromStyle(com.android.internal.R.attr.materialColorOutlineVariant)
+ @ColorInt
+ var backgroundColor = getColorFromStyle(com.android.internal.R.attr.materialColorSurfaceBright)
+ @ColorInt var iconColor = getColorFromStyle(com.android.internal.R.attr.materialColorOnPrimary)
init {
currentLevel = initialCurrentLevel
@@ -90,9 +97,6 @@
private fun updateResources() {
context.resources.apply {
- filledRectangleColor = getColor(R.color.backlight_indicator_step_filled, context.theme)
- emptyRectangleColor = getColor(R.color.backlight_indicator_step_empty, context.theme)
- backgroundColor = getColor(R.color.backlight_indicator_background, context.theme)
rootProperties =
RootProperties(
cornerRadius =
@@ -126,6 +130,11 @@
}
}
+ @ColorInt
+ fun getColorFromStyle(@AttrRes colorId: Int): Int {
+ return Utils.getColorAttrDefaultColor(context, colorId)
+ }
+
fun updateState(current: Int, max: Int, forceRefresh: Boolean = false) {
if (maxLevel != max || forceRefresh) {
maxLevel = max
@@ -159,6 +168,7 @@
private fun buildRootView(): LinearLayout {
val linearLayout =
LinearLayout(context).apply {
+ id = R.id.keyboard_backlight_dialog_container
orientation = LinearLayout.HORIZONTAL
layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
setPadding(
@@ -214,6 +224,7 @@
private fun createBacklightIconView(): ImageView {
return ImageView(context).apply {
setImageResource(R.drawable.ic_keyboard_backlight)
+ setColorFilter(iconColor)
layoutParams =
FrameLayout.LayoutParams(iconProperties.width, iconProperties.height).apply {
gravity = Gravity.CENTER
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 2925d8d..9844ca0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -36,7 +36,7 @@
import com.android.internal.R
import com.android.keyguard.KeyguardClockSwitchController
import com.android.keyguard.KeyguardViewController
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 1a126d7..2854de0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -98,6 +98,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.app.animation.Interpolators;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
import com.android.internal.policy.IKeyguardDismissCallback;
@@ -121,7 +122,6 @@
import com.android.systemui.EventLogTags;
import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.LaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
index b92499e..b7ba201 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 @@
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 @@
} 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 @@
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/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 5f2178df..5b71a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -32,6 +32,8 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -42,6 +44,7 @@
import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus
import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.log.SessionTracker
@@ -63,6 +66,7 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
@@ -135,6 +139,7 @@
@FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
@FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val featureFlags: FeatureFlags,
dumpManager: DumpManager,
) : DeviceEntryFaceAuthRepository, Dumpable {
private var authCancellationSignal: CancellationSignal? = null
@@ -212,15 +217,21 @@
.collect(Collectors.toSet())
dumpManager.registerCriticalDumpable("DeviceEntryFaceAuthRepositoryImpl", this)
- observeFaceAuthGatingChecks()
- observeFaceDetectGatingChecks()
- observeFaceAuthResettingConditions()
- listenForSchedulingWatchdog()
+ if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
+ observeFaceAuthGatingChecks()
+ observeFaceDetectGatingChecks()
+ observeFaceAuthResettingConditions()
+ listenForSchedulingWatchdog()
+ }
}
private fun listenForSchedulingWatchdog() {
keyguardTransitionInteractor.anyStateToGoneTransition
- .onEach { faceManager?.scheduleWatchdog() }
+ .filter { it.transitionState == TransitionState.FINISHED }
+ .onEach {
+ faceAuthLogger.watchdogScheduled()
+ faceManager?.scheduleWatchdog()
+ }
.launchIn(applicationScope)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
index ef8b401..ee0eb2d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
@@ -46,8 +46,6 @@
impl: SystemUIKeyguardFaceAuthInteractor
): KeyguardFaceAuthInteractor
- @Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository
-
companion object {
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index f27f899..e7b9af6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -45,4 +45,6 @@
@Binds
fun keyguardBouncerRepository(impl: KeyguardBouncerRepositoryImpl): KeyguardBouncerRepository
+
+ @Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
index d90f328..74c5520 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
@@ -28,7 +28,9 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
@@ -40,6 +42,9 @@
interface TrustRepository {
/** Flow representing whether the current user is trusted. */
val isCurrentUserTrusted: Flow<Boolean>
+
+ /** Flow representing whether active unlock is available for the current user. */
+ val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean>
}
@SysUISingleton
@@ -89,11 +94,13 @@
}
.shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)
- override val isCurrentUserTrusted: Flow<Boolean>
- get() =
- combine(trust, userRepository.selectedUserInfo, ::Pair)
- .map { latestTrustModelForUser[it.second.id]?.isTrusted ?: false }
- .distinctUntilChanged()
- .onEach { logger.isCurrentUserTrusted(it) }
- .onStart { emit(false) }
+ override val isCurrentUserTrusted: Flow<Boolean> =
+ combine(trust, userRepository.selectedUserInfo, ::Pair)
+ .map { latestTrustModelForUser[it.second.id]?.isTrusted ?: false }
+ .distinctUntilChanged()
+ .onEach { logger.isCurrentUserTrusted(it) }
+ .onStart { emit(false) }
+
+ // TODO: Implement based on TrustManager callback b/267322286
+ override val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean> = MutableStateFlow(true)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
new file mode 100644
index 0000000..252982f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
@@ -0,0 +1,125 @@
+/*
+ * 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.domain.interactor
+
+import android.content.Context
+import androidx.annotation.DimenRes
+import com.android.systemui.R
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+
+/** Encapsulates business-logic related to Ambient Display burn-in offsets. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class BurnInInteractor
+@Inject
+constructor(
+ private val context: Context,
+ private val burnInHelperWrapper: BurnInHelperWrapper,
+ @Application private val scope: CoroutineScope,
+ private val configurationRepository: ConfigurationRepository,
+ private val systemClock: SystemClock,
+) {
+ private val _dozeTimeTick = MutableStateFlow<Long>(0)
+ val dozeTimeTick: StateFlow<Long> = _dozeTimeTick.asStateFlow()
+
+ val udfpsBurnInXOffset: StateFlow<Int> =
+ burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_x, isXAxis = true)
+ val udfpsBurnInYOffset: StateFlow<Int> =
+ burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_y, isXAxis = false)
+ val udfpsBurnInProgress: StateFlow<Float> =
+ dozeTimeTick
+ .mapLatest { burnInHelperWrapper.burnInProgressOffset() }
+ .stateIn(scope, SharingStarted.Lazily, burnInHelperWrapper.burnInProgressOffset())
+
+ fun dozeTimeTick() {
+ _dozeTimeTick.value = systemClock.uptimeMillis()
+ }
+
+ /**
+ * Use for max burn-in offsets that are NOT specified in pixels. This flow will recalculate the
+ * max burn-in offset on any configuration changes. If the max burn-in offset is specified in
+ * pixels, use [burnInOffsetDefinedInPixels].
+ */
+ private fun burnInOffset(
+ @DimenRes maxBurnInOffsetResourceId: Int,
+ isXAxis: Boolean,
+ ): StateFlow<Int> {
+ return configurationRepository.onAnyConfigurationChange
+ .flatMapLatest {
+ val maxBurnInOffsetPixels =
+ context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
+ dozeTimeTick.mapLatest { calculateOffset(maxBurnInOffsetPixels, isXAxis) }
+ }
+ .stateIn(
+ scope,
+ SharingStarted.Lazily,
+ calculateOffset(
+ context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId),
+ isXAxis,
+ )
+ )
+ }
+
+ /**
+ * Use for max burn-in offBurn-in offsets that ARE specified in pixels. This flow will apply the
+ * a scale for any resolution changes. If the max burn-in offset is specified in dp, use
+ * [burnInOffset].
+ */
+ private fun burnInOffsetDefinedInPixels(
+ @DimenRes maxBurnInOffsetResourceId: Int,
+ isXAxis: Boolean,
+ ): StateFlow<Int> {
+ return configurationRepository.scaleForResolution
+ .flatMapLatest { scale ->
+ val maxBurnInOffsetPixels =
+ context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
+ dozeTimeTick.mapLatest { calculateOffset(maxBurnInOffsetPixels, isXAxis, scale) }
+ }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ calculateOffset(
+ context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId),
+ isXAxis,
+ configurationRepository.getResolutionScale(),
+ )
+ )
+ }
+
+ private fun calculateOffset(
+ maxBurnInOffsetPixels: Int,
+ isXAxis: Boolean,
+ scale: Float = 1f
+ ): Int {
+ return (burnInHelperWrapper.burnInOffset(maxBurnInOffsetPixels, isXAxis) * scale).toInt()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index e6568f2..cde67f9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -17,7 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index c2d139c..7e9cbc1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -17,7 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 86f65dde..aca4019 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -17,7 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 3beac0b..fc7bfb4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -17,7 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index b5bcd45..39c630b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -17,7 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 87f3164..0505d37 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -17,7 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index c0d5abc..47846d1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -17,7 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index a681c43..bc55bd4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -17,10 +17,10 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
+import com.android.app.animation.Interpolators
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index e650b9f..7c5641f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -19,7 +19,7 @@
import com.android.keyguard.logging.KeyguardLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.plugins.log.LogLevel.VERBOSE
+import com.android.systemui.log.LogLevel.VERBOSE
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index 970d004..110bcd7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -32,14 +32,16 @@
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.shared.system.SysUiStatsLog
-import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -56,28 +58,21 @@
class PrimaryBouncerInteractor
@Inject
constructor(
- private val repository: KeyguardBouncerRepository,
- private val primaryBouncerView: BouncerView,
- @Main private val mainHandler: Handler,
- private val keyguardStateController: KeyguardStateController,
- private val keyguardSecurityModel: KeyguardSecurityModel,
- private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor,
- private val falsingCollector: FalsingCollector,
- private val dismissCallbackRegistry: DismissCallbackRegistry,
- private val context: Context,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- keyguardBypassController: KeyguardBypassController,
+ private val repository: KeyguardBouncerRepository,
+ private val primaryBouncerView: BouncerView,
+ @Main private val mainHandler: Handler,
+ private val keyguardStateController: KeyguardStateController,
+ private val keyguardSecurityModel: KeyguardSecurityModel,
+ private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor,
+ private val falsingCollector: FalsingCollector,
+ private val dismissCallbackRegistry: DismissCallbackRegistry,
+ private val context: Context,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val trustRepository: TrustRepository,
+ private val featureFlags: FeatureFlags,
) {
- /** Whether we want to wait for face auth. */
- private val primaryBouncerFaceDelay =
- keyguardStateController.isFaceAuthEnabled &&
- !keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- KeyguardUpdateMonitor.getCurrentUser()
- ) &&
- !needsFullscreenBouncer() &&
- keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE) &&
- !keyguardBypassController.bypassEnabled
-
+ private val passiveAuthBouncerDelay = context.resources.getInteger(
+ R.integer.primary_bouncer_passive_auth_delay).toLong()
/** Runnable to show the primary bouncer. */
val showRunnable = Runnable {
repository.setPrimaryShow(true)
@@ -160,8 +155,9 @@
}
repository.setPrimaryShowingSoon(true)
- if (primaryBouncerFaceDelay) {
- mainHandler.postDelayed(showRunnable, 1200L)
+ if (usePrimaryBouncerPassiveAuthDelay()) {
+ Log.d(TAG, "delay bouncer, passive auth may succeed")
+ mainHandler.postDelayed(showRunnable, passiveAuthBouncerDelay)
} else {
DejankUtils.postAfterTraversal(showRunnable)
}
@@ -377,6 +373,17 @@
return repository.primaryBouncerShow.value
}
+ /** Whether we want to wait to show the bouncer in case passive auth succeeds. */
+ private fun usePrimaryBouncerPassiveAuthDelay(): Boolean {
+ val canRunFaceAuth = keyguardStateController.isFaceAuthEnabled &&
+ keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE)
+ val canRunActiveUnlock = trustRepository.isCurrentUserActiveUnlockAvailable.value &&
+ keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState()
+ return featureFlags.isEnabled(Flags.DELAY_BOUNCER) &&
+ !needsFullscreenBouncer() &&
+ (canRunFaceAuth || canRunActiveUnlock)
+ }
+
companion object {
private const val TAG = "PrimaryBouncerInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 38b9d50..9d7477c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -16,7 +16,7 @@
package com.android.systemui.keyguard.ui
import android.view.animation.Interpolator
-import com.android.systemui.animation.Interpolators.LINEAR
+import com.android.app.animation.Interpolators.LINEAR
import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index d96609c..c8d37a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -32,11 +32,11 @@
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.animation.Interpolators
import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.Expandable
-import com.android.systemui.animation.Interpolators
import com.android.systemui.animation.view.LaunchableLinearLayout
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.binder.IconViewBinder
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index 6bbc6f6..c1aefc7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -112,8 +112,6 @@
viewModel.isShowing.collect { isShowing ->
view.visibility = if (isShowing) View.VISIBLE else View.INVISIBLE
if (isShowing) {
- // Reset Security Container entirely.
- view.visibility = View.VISIBLE
securityContainerController.reinflateViewFlipper {
// Reset Security Container entirely.
securityContainerController.onBouncerVisibilityChanged(
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 a98a7d8..ad11360 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
@@ -17,10 +17,12 @@
package com.android.systemui.keyguard.ui.preview
+import android.annotation.ColorInt
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 +35,7 @@
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 +64,7 @@
private val clockRegistry: ClockRegistry,
private val broadcastDispatcher: BroadcastDispatcher,
private val lockscreenSmartspaceController: LockscreenSmartspaceController,
+ private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
@Assisted bundle: Bundle,
) {
@@ -82,6 +86,7 @@
private var clockView: View? = null
private var smartSpaceView: View? = null
+ private var colorOverride: Int? = null
private val disposables = mutableSetOf<DisposableHandle>()
private var isDestroyed = false
@@ -112,7 +117,9 @@
setUpBottomArea(rootView)
- setupSmartspace(rootView)
+ setUpSmartspace(rootView)
+
+ setUpUdfps(rootView)
setUpClock(rootView)
@@ -166,52 +173,64 @@
}
}
+ /** Sets the clock's color to the overridden seed color. */
+ fun onColorOverridden(@ColorInt color: Int?) {
+ runBlocking(mainDispatcher) {
+ colorOverride = color
+ clockController.clock?.run { events.onSeedColorChanged(color) }
+ }
+ }
+
/**
* 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 +253,33 @@
)
}
+ 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 {
@@ -252,8 +298,10 @@
val receiver =
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
- clockController.clock?.smallClock?.events?.onTimeTick()
- clockController.clock?.largeClock?.events?.onTimeTick()
+ clockController.clock?.run {
+ smallClock.events.onTimeTick()
+ largeClock.events.onTimeTick()
+ }
}
}
broadcastDispatcher.registerReceiver(
@@ -266,48 +314,35 @@
disposables.add(DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) })
onClockChanged(parentView)
-
- updateSmartspaceWithSetupClock()
}
private fun onClockChanged(parentView: ViewGroup) {
- clockController.clock = clockRegistry.createCurrentClock()
+ val clock = clockRegistry.createCurrentClock()
+ clockController.clock = clock
+ colorOverride?.let { clock.events.onSeedColorChanged(it) }
if (!shouldHideClock) {
- val largeClock = clockController.clock?.largeClock
-
- largeClock
- ?.events
- ?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView))
+ clock.largeClock.events.onTargetRegionChanged(
+ KeyguardClockSwitch.getLargeClockRegion(parentView)
+ )
clockView?.let { parentView.removeView(it) }
- clockView = largeClock?.view?.apply {
- if (shouldHighlightSelectedAffordance) {
- alpha = DIM_ALPHA
+ clockView =
+ clock.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
-
+ 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/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
index 3869b23..79712f9c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -115,16 +115,21 @@
when (message.what) {
KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
- message.data
- .getString(
- KeyguardPreviewConstants.KEY_SLOT_ID,
- )
- ?.let { slotId -> renderer.onSlotSelected(slotId = slotId) }
+ message.data.getString(KeyguardPreviewConstants.KEY_SLOT_ID)?.let { slotId ->
+ renderer.onSlotSelected(slotId = slotId)
+ }
}
KeyguardPreviewConstants.MESSAGE_ID_HIDE_SMART_SPACE -> {
- message.data
- .getBoolean(KeyguardPreviewConstants.KEY_HIDE_SMART_SPACE)
- .let { hide -> renderer.hideSmartspace(hide) }
+ renderer.hideSmartspace(
+ message.data.getBoolean(KeyguardPreviewConstants.KEY_HIDE_SMART_SPACE)
+ )
+ }
+ KeyguardPreviewConstants.MESSAGE_ID_COLOR_OVERRIDE -> {
+ renderer.onColorOverridden(
+ message.data
+ .getString(KeyguardPreviewConstants.KEY_COLOR_OVERRIDE)
+ ?.toIntOrNull()
+ )
}
else -> requestDestruction(this)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index 8d6545a4..2c9a9b3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -16,8 +16,8 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
-import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
index f16827d..c135786 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
@@ -16,7 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
index bc9dc4f..c6187dd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
@@ -16,7 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
index a60665a..d3ea89c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
@@ -16,7 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
index ddce516..6845c55 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
@@ -16,7 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index df93d23..68810f9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -16,7 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index efd3ad6..fefa1b2 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -6,9 +6,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.log.LogLevel.DEBUG
import com.android.systemui.log.dagger.FaceAuthLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
import javax.inject.Inject
private const val TAG = "DeviceEntryFaceAuthRepositoryLog"
@@ -261,4 +260,8 @@
{ "Attempting face auth again because of HW error: retry attempt $int1" }
)
}
+
+ fun watchdogScheduled() {
+ logBuffer.log(TAG, DEBUG, "FaceManager Biometric watchdog scheduled.")
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
index d6e29e0..e4465ac 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
@@ -19,9 +19,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogcatEchoTracker
-
import javax.inject.Inject
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
index edc278d..f727784 100644
--- a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
@@ -21,10 +21,11 @@
import android.graphics.RectF
import androidx.core.graphics.toRectF
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.ERROR
import com.android.systemui.log.dagger.ScreenDecorationsLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.ERROR
+import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
private const val TAG = "ScreenDecorationsLog"
@@ -131,4 +132,36 @@
fun onSensorLocationChanged() {
logBuffer.log(TAG, DEBUG, "AuthControllerCallback in ScreenDecorations triggered")
}
+
+ fun cameraProtectionShownOrHidden(
+ faceDetectionRunning: Boolean,
+ biometricPromptShown: Boolean,
+ requestedState: Boolean,
+ currentlyShowing: Boolean
+ ) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ bool1 = faceDetectionRunning
+ bool2 = biometricPromptShown
+ bool3 = requestedState
+ bool4 = currentlyShowing
+ },
+ {
+ "isFaceDetectionRunning: $bool1, " +
+ "isBiometricPromptShowing: $bool2, " +
+ "requestedState: $bool3, " +
+ "currentState: $bool4"
+ }
+ )
+ }
+
+ fun biometricEvent(@CompileTimeConstant info: String) {
+ logBuffer.log(TAG, DEBUG, info)
+ }
+
+ fun cameraProtectionEvent(@CompileTimeConstant cameraProtectionEvent: String) {
+ logBuffer.log(TAG, DEBUG, cameraProtectionEvent)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricLog.java
index 4b774d3..233f7a12 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricLog.java
@@ -23,7 +23,7 @@
import javax.inject.Qualifier;
/**
- * A {@link com.android.systemui.plugins.log.LogBuffer} for BiometricMessages processing such as
+ * A {@link com.android.systemui.log.LogBuffer} for BiometricMessages processing such as
* {@link com.android.systemui.biometrics.FaceHelpMessageDeferral}
*/
@Qualifier
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java
index 5cca1ab..7d1f1c2 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java
index 1d016d8..9ca0293 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java
index c9f78bc..7c5f402 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt
index 9f563fe4..8732ef5 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt
@@ -18,19 +18,19 @@
import javax.inject.Qualifier
-/** A [com.android.systemui.plugins.log.LogBuffer] for keyguard clock logs. */
+/** A [com.android.systemui.log.LogBuffer] for keyguard clock logs. */
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class KeyguardClockLog
-/** A [com.android.systemui.plugins.log.LogBuffer] for small keyguard clock logs. */
+/** A [com.android.systemui.log.LogBuffer] for small keyguard clock logs. */
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class KeyguardSmallClockLog
-/** A [com.android.systemui.plugins.log.LogBuffer] for large keyguard clock logs. */
+/** A [com.android.systemui.log.LogBuffer] for large keyguard clock logs. */
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java
index 76d20be..08d969b 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LSShadeTransitionLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 658f6a0..9be18ac 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -22,13 +22,13 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.LogBufferFactory;
+import com.android.systemui.log.LogcatEchoTracker;
+import com.android.systemui.log.LogcatEchoTrackerDebug;
+import com.android.systemui.log.LogcatEchoTrackerProd;
import com.android.systemui.log.table.TableLogBuffer;
import com.android.systemui.log.table.TableLogBufferFactory;
-import com.android.systemui.plugins.log.LogBuffer;
-import com.android.systemui.plugins.log.LogcatEchoTracker;
-import com.android.systemui.plugins.log.LogcatEchoTrackerDebug;
-import com.android.systemui.plugins.log.LogcatEchoTrackerProd;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.util.Compile;
import com.android.systemui.util.wakelock.WakeLockLog;
@@ -209,7 +209,7 @@
@SysUISingleton
@CollapsedSbFragmentLog
public static LogBuffer provideCollapsedSbFragmentLogBuffer(LogBufferFactory factory) {
- return factory.create("CollapsedSbFragmentLog", 20);
+ return factory.create("CollapsedSbFragmentLog", 40);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java
index af43347..1c00c93 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java
index f4dac6e..86a916e 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java
index 73690ab..c67d8be 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaMuteAwaitLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
index 0c2cd92..98e6556 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java
index 5b7f4bb..dde0ee0 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java
index 6d91f0c..b1c6dcf 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NearbyMediaDevicesLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java
index 26af496..20fc6ff 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java
index 61daf9c..fcc184a 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationHeadsUpLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java
index a59afa0..760fbf3 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLockscreenLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLockscreenLog.java
index a2d381e..f1646a8 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLockscreenLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLockscreenLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java
index 6f8ea7f..a0b6864 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java
index 835d349..8c8753a 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java
index 6e2bd7b..7259eeb 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationSectionLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java
index 77b1bf5..e96e532 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSConfigLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSConfigLog.java
index 295bf88..973f650 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSConfigLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSConfigLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java
index 9fd166b..557a254 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java
index dd168ba..dd5010c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java
index d24bfcb..bd0d298 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeWindowLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeWindowLog.java
index 1d2b68c..4aa2b1d 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeWindowLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeWindowLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java
index af0f7c5..f26b316 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeUpLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeUpLog.java
index d58b538..4d797c1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeUpLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeUpLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java
index ba8b27c..8671dbf 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/UnseenNotificationLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/UnseenNotificationLog.java
index 5c2321b..dbb6e8c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/UnseenNotificationLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/UnseenNotificationLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
index 42fdd68..592044e 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
@@ -27,19 +27,27 @@
*/
data class TableChange(
var timestamp: Long = 0,
- var columnPrefix: String = "",
- var columnName: String = "",
- var isInitial: Boolean = false,
- var type: DataType = DataType.EMPTY,
- var bool: Boolean = false,
- var int: Int? = null,
- var str: String? = null,
+ private var columnPrefix: String = "",
+ private var columnName: String = "",
+ private var isInitial: Boolean = false,
+ private var type: DataType = DataType.EMPTY,
+ private var bool: Boolean = false,
+ private var int: Int? = null,
+ private var str: String? = null,
) {
+ init {
+ // Truncate any strings that were passed into the constructor. [reset] and [set] will take
+ // care of the rest of the truncation.
+ this.columnPrefix = columnPrefix.take(MAX_STRING_LENGTH)
+ this.columnName = columnName.take(MAX_STRING_LENGTH)
+ this.str = str?.take(MAX_STRING_LENGTH)
+ }
+
/** Resets to default values so that the object can be recycled. */
fun reset(timestamp: Long, columnPrefix: String, columnName: String, isInitial: Boolean) {
this.timestamp = timestamp
- this.columnPrefix = columnPrefix
- this.columnName = columnName
+ this.columnPrefix = columnPrefix.take(MAX_STRING_LENGTH)
+ this.columnName = columnName.take(MAX_STRING_LENGTH)
this.isInitial = isInitial
this.type = DataType.EMPTY
this.bool = false
@@ -50,7 +58,7 @@
/** Sets this to store a string change. */
fun set(value: String?) {
type = DataType.STRING
- str = value
+ str = value?.take(MAX_STRING_LENGTH)
}
/** Sets this to store a boolean change. */
@@ -89,6 +97,8 @@
}
}
+ fun getColumnName() = columnName
+
fun getVal(): String {
val value =
when (type) {
@@ -109,5 +119,8 @@
companion object {
@VisibleForTesting const val IS_INITIAL_PREFIX = "**"
+ // Don't allow any strings larger than this length so that we have a hard upper limit on the
+ // size of the data stored by the buffer.
+ @VisibleForTesting const val MAX_STRING_LENGTH = 500
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
index 9d883cc..1d785ae 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -20,8 +20,8 @@
import com.android.systemui.Dumpable
import com.android.systemui.common.buffer.RingBuffer
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogcatEchoTracker
import com.android.systemui.util.time.SystemClock
import java.io.PrintWriter
import java.text.SimpleDateFormat
@@ -35,7 +35,7 @@
* A logger that logs changes in table format.
*
* Some parts of System UI maintain a lot of pieces of state at once.
- * [com.android.systemui.plugins.log.LogBuffer] allows us to easily log change events:
+ * [com.android.systemui.log.LogBuffer] allows us to easily log change events:
* - 10-10 10:10:10.456: state2 updated to newVal2
* - 10-10 10:11:00.000: stateN updated to StateN(val1=true, val2=1)
* - 10-10 10:11:02.123: stateN updated to StateN(val1=true, val2=2)
@@ -291,7 +291,7 @@
private fun echoToDesiredEndpoints(change: TableChange) {
if (
logcatEchoTracker.isBufferLoggable(bufferName = name, LogLevel.DEBUG) ||
- logcatEchoTracker.isTagLoggable(change.columnName, LogLevel.DEBUG)
+ logcatEchoTracker.isTagLoggable(change.getColumnName(), LogLevel.DEBUG)
) {
if (change.hasData()) {
localLogcat.d(name, change.logcatRepresentation())
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
index 42e742d..19e1124 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
@@ -21,7 +21,7 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.LogcatEchoTracker
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index aec7b5f..c41f82b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -111,7 +111,7 @@
if (featureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) {
component.emptyStateProvider
} else {
- super.createBlockerEmptyStateProvider()
+ object : EmptyStateProvider {}
}
override fun createListController(userHandle: UserHandle): ResolverListController =
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index 28adfbb..c9c2ea2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -79,6 +79,9 @@
// Indicates if user must review already-granted consent that the MediaProjection app is
// attempting to re-use.
private boolean mReviewGrantedConsentRequired = false;
+ // Indicates if the user has consented to record, but is continuing in another activity to
+ // select a particular task to capture.
+ private boolean mUserSelectingTask = false;
@Inject
public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
@@ -296,6 +299,7 @@
// Start activity from the current foreground user to avoid creating a separate
// SystemUI process without access to recent tasks because it won't have
// WM Shell running inside.
+ mUserSelectingTask = true;
startActivityAsUser(intent, UserHandle.of(ActivityManager.getCurrentUser()));
}
} catch (RemoteException e) {
@@ -316,7 +320,10 @@
@Override
public void finish() {
// Default to cancelling recording when user needs to review consent.
- finish(RECORD_CANCEL, /* projection= */ null);
+ // Don't send cancel if the user has moved on to the next activity.
+ if (!mUserSelectingTask) {
+ finish(RECORD_CANCEL, /* projection= */ null);
+ }
}
private void finish(@ReviewGrantedConsentResult int consentResult,
@@ -328,7 +335,7 @@
private void onDialogDismissedOrCancelled(DialogInterface dialogInterface) {
if (!isFinishing()) {
- finish(RECORD_CANCEL, /* projection= */ null);
+ finish();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
index 37d956b..e38abc2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
@@ -21,9 +21,9 @@
import android.text.format.DateUtils
import androidx.annotation.UiThread
import androidx.lifecycle.Observer
+import com.android.app.animation.Interpolators
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
import com.android.systemui.media.controls.ui.SquigglyProgress
/**
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 9997730..fa42114 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.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.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 @@
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 @@
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 @@
mediaFlags: MediaFlags,
logger: MediaUiEventLogger,
smartspaceManager: SmartspaceManager,
- statusBarService: IStatusBarService,
keyguardUpdateMonitor: KeyguardUpdateMonitor,
) : this(
context,
@@ -283,7 +277,6 @@
logger,
smartspaceManager,
keyguardUpdateMonitor,
- statusBarService,
)
private val appChangeReceiver =
@@ -385,21 +378,21 @@
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 @@
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 @@
}
}
- 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 @@
key: String,
sbn: StatusBarNotification,
oldKey: String?,
- isNewlyActiveEntry: Boolean = false,
+ logEvent: Boolean = false
) {
val token =
sbn.notification.extras.getParcelable(
@@ -774,34 +765,6 @@
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 @@
// 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 @@
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/media/controls/pipeline/MediaTimeoutLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
index f731dc0..e2e269d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
@@ -18,9 +18,9 @@
import android.media.session.PlaybackState
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.MediaTimeoutListenerLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
private const val TAG = "MediaTimeout"
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt
index 095cf09..9e53d77 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt
@@ -18,9 +18,9 @@
import android.content.ComponentName
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.MediaBrowserLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
/** A logger for events in [ResumeMediaBrowser]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
index 3669493..b46ebb2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
@@ -34,10 +34,10 @@
import android.util.MathUtils
import android.view.View
import androidx.annotation.Keep
+import com.android.app.animation.Interpolators
import com.android.internal.graphics.ColorUtils
import com.android.internal.graphics.ColorUtils.blendARGB
import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
import org.xmlpull.v1.XmlPullParser
private const val BACKGROUND_ANIM_DURATION = 370L
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
index dd5c2bf..937a618 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
@@ -35,9 +35,9 @@
import android.util.AttributeSet
import android.util.MathUtils.lerp
import androidx.annotation.Keep
+import com.android.app.animation.Interpolators
import com.android.internal.graphics.ColorUtils
import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
import org.xmlpull.v1.XmlPullParser
private const val RIPPLE_ANIM_DURATION = 800L
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
index 9af11b9..0ed2434 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
@@ -17,9 +17,9 @@
package com.android.systemui.media.controls.ui
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.MediaCarouselControllerLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
/** A debug logger for [MediaCarouselController]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 40027a1..f9d3094 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -72,6 +72,7 @@
import androidx.annotation.UiThread;
import androidx.constraintlayout.widget.ConstraintSet;
+import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
import com.android.internal.jank.InteractionJankMonitor;
@@ -82,7 +83,6 @@
import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.GhostedViewLaunchAnimatorController;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.bluetooth.BroadcastDialogController;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.qualifiers.Background;
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index 49e1665..fe8ebaf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -33,9 +33,9 @@
import android.view.ViewGroup
import android.view.ViewGroupOverlay
import androidx.annotation.VisibleForTesting
+import com.android.app.animation.Interpolators
import com.android.keyguard.KeyguardViewController
import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dreams.DreamOverlayStateController
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
index fdac33a..c781b76 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
@@ -17,9 +17,9 @@
package com.android.systemui.media.controls.ui
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.MediaViewLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
private const val TAG = "MediaView"
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
index e9b2cf2..583c626 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
@@ -31,8 +31,8 @@
import android.util.MathUtils.lerpInv
import android.util.MathUtils.lerpInvSat
import androidx.annotation.VisibleForTesting
+import com.android.app.animation.Interpolators
import com.android.internal.graphics.ColorUtils
-import com.android.systemui.animation.Interpolators
import kotlin.math.abs
import kotlin.math.cos
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 9ae4577..46efac5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -17,6 +17,7 @@
package com.android.systemui.media.dagger;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.LogBufferFactory;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
@@ -30,16 +31,15 @@
import com.android.systemui.media.taptotransfer.MediaTttFlags;
import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogBuffer;
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogBuffer;
-import com.android.systemui.plugins.log.LogBuffer;
-
-import java.util.Optional;
-
-import javax.inject.Named;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
+import java.util.Optional;
+
+import javax.inject.Named;
+
/** Dagger module for the media package. */
@Module(subcomponents = {
MediaComplicationComponent.class,
diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
index 5ace3ea..bbcf259 100644
--- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
@@ -2,8 +2,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.dagger.MediaMuteAwaitLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import javax.inject.Inject
/** Log messages for [MediaMuteAwaitConnectionManager]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
index 78408fc..66399d5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
@@ -2,8 +2,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.dagger.NearbyMediaDevicesLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import javax.inject.Inject
/** Log messages for [NearbyMediaDevicesManager]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt
index 0e839c6..eeda1027 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt
@@ -16,8 +16,8 @@
package com.android.systemui.media.taptotransfer.common
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
/** A helper for logging media tap-to-transfer events. */
object MediaTttLoggerUtils {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 78082c3..77ff036 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -36,7 +36,7 @@
import android.view.View.ACCESSIBILITY_LIVE_REGION_NONE
import com.android.internal.widget.CachingIconView
import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.ui.binder.TintedIconViewBinder
import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogBuffer.java b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogBuffer.java
index 67e464c..31ccb9a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogBuffer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogBuffer.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt
index b0c6257..1502df7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt
@@ -18,8 +18,8 @@
import android.app.StatusBarManager
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
import com.android.systemui.media.taptotransfer.common.MediaTttLoggerUtils
-import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.temporarydisplay.TemporaryViewLogger
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogBuffer.java b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogBuffer.java
index a262e97..edee4a8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogBuffer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogBuffer.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
index 964a95b..03bcfc8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
@@ -18,9 +18,9 @@
import android.app.StatusBarManager
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.media.taptotransfer.common.MediaTttLoggerUtils
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
/** A logger for all events related to the media tap-to-transfer sender experience. */
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index 219629b..1d8fe72 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -19,7 +19,6 @@
import android.content.ComponentName
import android.os.UserHandle
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
import javax.inject.Inject
@@ -59,13 +58,7 @@
* Removes all recent tasks that are different from the profile of the host app to avoid any
* cross-profile sharing
*/
- private fun List<RecentTask>.filterDevicePolicyRestrictedTasks(): List<RecentTask> =
- if (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) {
- // TODO(b/263950746): filter tasks based on the enterprise policies
- this
- } else {
- filter { UserHandle.of(it.userId) == hostUserHandle }
- }
+ private fun List<RecentTask>.filterDevicePolicyRestrictedTasks(): List<RecentTask> = this
private fun List<RecentTask>.filterAppSelector(): List<RecentTask> = filter {
// Only take tasks that is not the app selector
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 94f01b8..146b5f5 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -58,11 +58,11 @@
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.Utils;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
import com.android.systemui.navigationbar.buttons.ContextualButton;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
index 0218016..10084bd 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
@@ -16,7 +16,7 @@
package com.android.systemui.navigationbar.buttons;
-import static com.android.systemui.animation.Interpolators.LINEAR;
+import static com.android.app.animation.Interpolators.LINEAR;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -24,9 +24,6 @@
import android.view.View;
import android.view.View.AccessibilityDelegate;
-import com.android.systemui.Dependency;
-import com.android.systemui.assist.AssistManager;
-
import java.util.ArrayList;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index 590efbb..ff22398 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -48,10 +48,10 @@
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
+import com.android.app.animation.Interpolators;
import com.android.internal.util.LatencyTracker;
import com.android.settingslib.Utils;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.plugins.NavigationEdgeBackPlugin;
import com.android.systemui.settings.DisplayTracker;
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 0000000..c209a00
--- /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 d4052f5..7e9b346 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.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 @@
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 @@
)
}
+ /** 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 @@
* 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 @@
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 @@
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 @@
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 @@
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 @@
PackageManager.DONT_KILL_APP,
)
- logDebug { "setNoteTaskShortcutEnabled - completed: $isEnabled" }
+ debugLog { "setNoteTaskShortcutEnabled - completed: $isEnabled" }
}
/**
@@ -352,11 +371,6 @@
}
}
-/** [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 2c62ffd..4d5173a 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 @@
@[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 2da5b76..444407c 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.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 @@
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 @@
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 @@
.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 @@
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 0f38d32..8ca13b9 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 @@
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 @@
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 @@
}
}
}
-
-/** [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/logging/PrivacyLogger.kt b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
index 03503fd..f9e1adf 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
@@ -18,9 +18,9 @@
import android.permission.PermissionGroupUsage
import com.android.systemui.log.dagger.PrivacyLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogMessage
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogMessage
import com.android.systemui.privacy.PrivacyDialog
import com.android.systemui.privacy.PrivacyItem
import java.text.SimpleDateFormat
diff --git a/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java b/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java
index 80fbf911..b6a5ad6 100644
--- a/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java
@@ -16,11 +16,14 @@
package com.android.systemui.process.condition;
+import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.shared.condition.Condition;
import javax.inject.Inject;
+import kotlinx.coroutines.CoroutineScope;
+
/**
* {@link SystemProcessCondition} checks to make sure the current process is being ran by the
* System User.
@@ -29,8 +32,9 @@
private final ProcessWrapper mProcessWrapper;
@Inject
- public SystemProcessCondition(ProcessWrapper processWrapper) {
- super();
+ public SystemProcessCondition(@Application CoroutineScope scope,
+ ProcessWrapper processWrapper) {
+ super(scope);
mProcessWrapper = processWrapper;
}
@@ -42,4 +46,9 @@
@Override
protected void stop() {
}
+
+ @Override
+ protected int getStartStrategy() {
+ return START_EAGERLY;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index a7aac5a..463c79c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -26,7 +26,7 @@
import androidx.annotation.Nullable;
-import com.android.systemui.animation.Interpolators;
+import com.android.app.animation.Interpolators;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.qs.QSTile;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index b7f9f6b..1afc885 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 @@
*/
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 @@
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 @@
/**
* 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 @@
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 @@
mClippingEnabled = enabled;
updatePath = true;
}
+ if (mIsFullWidth != fullWidth) {
+ mIsFullWidth = fullWidth;
+ updatePath = true;
+ }
if (updatePath) {
updateClippingPath();
@@ -281,15 +297,21 @@
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/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
index 0d29a1a..85f557b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
@@ -27,6 +27,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.retail.domain.interactor.RetailModeInteractor;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.ViewController;
@@ -45,18 +46,22 @@
private final View mEditButton;
private final FalsingManager mFalsingManager;
private final ActivityStarter mActivityStarter;
+ private final RetailModeInteractor mRetailModeInteractor;
@Inject
QSFooterViewController(QSFooterView view,
UserTracker userTracker,
FalsingManager falsingManager,
ActivityStarter activityStarter,
- QSPanelController qsPanelController) {
+ QSPanelController qsPanelController,
+ RetailModeInteractor retailModeInteractor
+ ) {
super(view);
mUserTracker = userTracker;
mQsPanelController = qsPanelController;
mFalsingManager = falsingManager;
mActivityStarter = activityStarter;
+ mRetailModeInteractor = retailModeInteractor;
mBuildText = mView.findViewById(R.id.build);
mPageIndicator = mView.findViewById(R.id.footer_page_indicator);
@@ -96,6 +101,8 @@
@Override
public void setVisibility(int visibility) {
mView.setVisibility(visibility);
+ mEditButton
+ .setVisibility(mRetailModeInteractor.isInRetailMode() ? View.GONE : View.VISIBLE);
mEditButton.setClickable(visibility == View.VISIBLE);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index d806afa..b8c2fad 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 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;
@@ -43,10 +44,10 @@
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
+import com.android.app.animation.Interpolators;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.compose.ComposeFacade;
import com.android.systemui.dump.DumpManager;
@@ -395,9 +396,11 @@
}
@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/QSFragmentDisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
index 025fb22..cd52ec2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
@@ -1,8 +1,8 @@
package com.android.systemui.qs
import com.android.systemui.log.dagger.QSFragmentDisableLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
index 0bce1f7..b70b94b 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 @@
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/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index 5b461a6..c00a81c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -21,14 +21,14 @@
import android.content.res.Configuration.Orientation
import android.service.quicksettings.Tile
import android.view.View
+import com.android.systemui.log.ConstantStringsLogger
+import com.android.systemui.log.ConstantStringsLoggerImpl
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.LogLevel.VERBOSE
import com.android.systemui.log.dagger.QSConfigLog
import com.android.systemui.log.dagger.QSLog
-import com.android.systemui.plugins.log.ConstantStringsLogger
-import com.android.systemui.plugins.log.ConstantStringsLoggerImpl
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.ERROR
-import com.android.systemui.plugins.log.LogLevel.VERBOSE
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.statusbar.StatusBarState
import com.google.errorprone.annotations.CompileTimeConstant
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
index e85440c..a066242 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
@@ -18,8 +18,8 @@
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecSettingsRepository
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index 595b29a9..3b2362f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -20,6 +20,7 @@
import android.content.res.Resources
import android.database.ContentObserver
import android.provider.Settings
+import com.android.systemui.R
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -27,12 +28,16 @@
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.retail.data.repository.RetailModeRepository
import com.android.systemui.util.settings.SecureSettings
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@@ -84,6 +89,9 @@
* [Settings.Secure.QS_TILES].
*
* All operations against [Settings] will be performed in a background thread.
+ *
+ * If the device is in retail mode, the tiles are fixed to the value of
+ * [R.string.quick_settings_tiles_retail_mode].
*/
@SysUISingleton
class TileSpecSettingsRepository
@@ -92,9 +100,31 @@
private val secureSettings: SecureSettings,
@Main private val resources: Resources,
private val logger: QSPipelineLogger,
+ private val retailModeRepository: RetailModeRepository,
@Background private val backgroundDispatcher: CoroutineDispatcher,
) : TileSpecRepository {
+
+ private val retailModeTiles by lazy {
+ resources
+ .getString(R.string.quick_settings_tiles_retail_mode)
+ .split(DELIMITER)
+ .map(TileSpec::create)
+ .filter { it !is TileSpec.Invalid }
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
override fun tilesSpecs(userId: Int): Flow<List<TileSpec>> {
+ return retailModeRepository.retailMode.flatMapLatest { inRetailMode ->
+ if (inRetailMode) {
+ logger.logUsingRetailTiles()
+ flowOf(retailModeTiles)
+ } else {
+ settingsTiles(userId)
+ }
+ }
+ }
+
+ private fun settingsTiles(userId: Int): Flow<List<TileSpec>> {
return conflatedCallbackFlow {
val observer =
object : ContentObserver(null) {
@@ -157,6 +187,10 @@
}
private suspend fun storeTiles(@UserIdInt forUser: Int, tiles: List<TileSpec>) {
+ if (retailModeRepository.inRetailMode) {
+ // No storing tiles when in retail mode
+ return
+ }
val toStore =
tiles
.filter { it !is TileSpec.Invalid }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
index 767ce91..ff7d206 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
@@ -17,8 +17,8 @@
package com.android.systemui.qs.pipeline.shared.logging
import android.annotation.UserIdInt
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.qs.pipeline.dagger.QSTileListLog
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
@@ -120,6 +120,10 @@
)
}
+ fun logUsingRetailTiles() {
+ tileListLogBuffer.log(TILE_LIST_TAG, LogLevel.DEBUG, {}, { "Using retail tiles" })
+ }
+
/** Reasons for destroying an existing tile. */
enum class TileDestroyedReason(val readable: String) {
TILE_REMOVED("Tile removed from current set"),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 7f7f8ad6..2d9f7dd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -68,6 +68,7 @@
private final SensorPrivacyManager mPrivacyManager;
private final BatteryController mBatteryController;
private final SettingObserver mSetting;
+ private final boolean mAllowRotationResolver;
@Inject
public RotationLockTile(
@@ -105,6 +106,8 @@
}
};
mBatteryController.observe(getLifecycle(), this);
+ mAllowRotationResolver = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_allowRotationResolver);
}
@Override
@@ -145,7 +148,7 @@
final boolean powerSave = mBatteryController.isPowerSave();
final boolean cameraLocked = mPrivacyManager.isSensorPrivacyEnabled(CAMERA);
- final boolean cameraRotation =
+ final boolean cameraRotation = mAllowRotationResolver &&
!powerSave && !cameraLocked && hasSufficientPermission(mContext)
&& mController.isCameraRotationEnabled();
state.value = !rotationLocked;
diff --git a/packages/SystemUI/src/com/android/systemui/retail/dagger/RetailModeModule.kt b/packages/SystemUI/src/com/android/systemui/retail/dagger/RetailModeModule.kt
new file mode 100644
index 0000000..e863949
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/retail/dagger/RetailModeModule.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.retail.dagger
+
+import com.android.systemui.retail.data.repository.RetailModeRepository
+import com.android.systemui.retail.data.repository.RetailModeSettingsRepository
+import com.android.systemui.retail.domain.interactor.RetailModeInteractor
+import com.android.systemui.retail.domain.interactor.RetailModeInteractorImpl
+import dagger.Binds
+import dagger.Module
+
+@Module
+abstract class RetailModeModule {
+
+ @Binds
+ abstract fun bindsRetailModeRepository(impl: RetailModeSettingsRepository): RetailModeRepository
+
+ @Binds
+ abstract fun bindsRetailModeInteractor(impl: RetailModeInteractorImpl): RetailModeInteractor
+}
diff --git a/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt b/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt
new file mode 100644
index 0000000..3c0aa38
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.retail.data.repository
+
+import android.database.ContentObserver
+import android.provider.Settings
+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.Background
+import com.android.systemui.util.settings.GlobalSettings
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+/** Repository to track if the device is in Retail mode */
+interface RetailModeRepository {
+ /** Flow of whether the device is currently in retail mode. */
+ val retailMode: StateFlow<Boolean>
+
+ /** Last value of whether the device is in retail mode. */
+ val inRetailMode: Boolean
+ get() = retailMode.value
+}
+
+/**
+ * Tracks [Settings.Global.DEVICE_DEMO_MODE].
+ *
+ * @see UserManager.isDeviceInDemoMode
+ */
+@SysUISingleton
+class RetailModeSettingsRepository
+@Inject
+constructor(
+ globalSettings: GlobalSettings,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+ @Application scope: CoroutineScope,
+) : RetailModeRepository {
+ override val retailMode =
+ conflatedCallbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ globalSettings.registerContentObserver(RETAIL_MODE_SETTING, observer)
+
+ awaitClose { globalSettings.unregisterContentObserver(observer) }
+ }
+ .onStart { emit(Unit) }
+ .map { globalSettings.getInt(RETAIL_MODE_SETTING, 0) != 0 }
+ .flowOn(backgroundDispatcher)
+ .stateIn(scope, SharingStarted.Eagerly, false)
+
+ companion object {
+ private const val RETAIL_MODE_SETTING = Settings.Global.DEVICE_DEMO_MODE
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
new file mode 100644
index 0000000..eea452c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.retail.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.retail.data.repository.RetailModeRepository
+import javax.inject.Inject
+
+/** Interactor to determine if the device is currently in retail mode */
+interface RetailModeInteractor {
+ /** Whether the device is currently in retail mode */
+ val isInRetailMode: Boolean
+}
+
+@SysUISingleton
+class RetailModeInteractorImpl
+@Inject
+constructor(
+ private val repository: RetailModeRepository,
+) : RetailModeInteractor {
+ override val isInRetailMode: Boolean
+ get() = repository.inRetailMode
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 69008cc..84f358c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -33,6 +33,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
@@ -60,11 +61,10 @@
public static final int REQUEST_CODE = 2;
private static final int USER_ID_NOT_SPECIFIED = -1;
- private static final int NOTIFICATION_RECORDING_ID = 4274;
- private static final int NOTIFICATION_PROCESSING_ID = 4275;
- private static final int NOTIFICATION_VIEW_ID = 4273;
+ private static final int NOTIF_BASE_ID = 4273;
private static final String TAG = "RecordingService";
private static final String CHANNEL_ID = "screen_record";
+ private static final String GROUP_KEY = "screen_record_saved";
private static final String EXTRA_RESULT_CODE = "extra_resultCode";
private static final String EXTRA_PATH = "extra_path";
private static final String EXTRA_AUDIO_SOURCE = "extra_useAudio";
@@ -89,6 +89,7 @@
private final UiEventLogger mUiEventLogger;
private final NotificationManager mNotificationManager;
private final UserContextProvider mUserContextTracker;
+ private int mNotificationId = NOTIF_BASE_ID;
@Inject
public RecordingService(RecordingController controller, @LongRunning Executor executor,
@@ -134,14 +135,23 @@
}
String action = intent.getAction();
Log.d(TAG, "onStartCommand " + action);
+ NotificationChannel channel = new NotificationChannel(
+ CHANNEL_ID,
+ getString(R.string.screenrecord_title),
+ NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setDescription(getString(R.string.screenrecord_channel_description));
+ channel.enableVibration(true);
+ mNotificationManager.createNotificationChannel(channel);
int currentUserId = mUserContextTracker.getUserContext().getUserId();
UserHandle currentUser = new UserHandle(currentUserId);
switch (action) {
case ACTION_START:
+ // Get a unique ID for this recording's notifications
+ mNotificationId = NOTIF_BASE_ID + (int) SystemClock.uptimeMillis();
mAudioSource = ScreenRecordingAudioSource
.values()[intent.getIntExtra(EXTRA_AUDIO_SOURCE, 0)];
- Log.d(TAG, "recording with audio source" + mAudioSource);
+ Log.d(TAG, "recording with audio source " + mAudioSource);
mShowTaps = intent.getBooleanExtra(EXTRA_SHOW_TAPS, false);
MediaProjectionCaptureTarget captureTarget =
intent.getParcelableExtra(EXTRA_CAPTURE_TARGET,
@@ -169,7 +179,7 @@
} else {
updateState(false);
createErrorNotification();
- stopForeground(true);
+ stopForeground(STOP_FOREGROUND_DETACH);
stopSelf();
return Service.START_NOT_STICKY;
}
@@ -200,7 +210,7 @@
startActivity(Intent.createChooser(shareIntent, shareLabel)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
// Remove notification
- mNotificationManager.cancelAsUser(null, NOTIFICATION_VIEW_ID, currentUser);
+ mNotificationManager.cancelAsUser(null, mNotificationId, currentUser);
return false;
}, false, false);
@@ -260,14 +270,6 @@
@VisibleForTesting
protected void createErrorNotification() {
Resources res = getResources();
- NotificationChannel channel = new NotificationChannel(
- CHANNEL_ID,
- getString(R.string.screenrecord_title),
- NotificationManager.IMPORTANCE_DEFAULT);
- channel.setDescription(getString(R.string.screenrecord_channel_description));
- channel.enableVibration(true);
- mNotificationManager.createNotificationChannel(channel);
-
Bundle extras = new Bundle();
extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
res.getString(R.string.screenrecord_title));
@@ -277,7 +279,7 @@
.setSmallIcon(R.drawable.ic_screenrecord)
.setContentTitle(notificationTitle)
.addExtras(extras);
- startForeground(NOTIFICATION_RECORDING_ID, builder.build());
+ startForeground(mNotificationId, builder.build());
}
@VisibleForTesting
@@ -288,14 +290,6 @@
@VisibleForTesting
protected void createRecordingNotification() {
Resources res = getResources();
- NotificationChannel channel = new NotificationChannel(
- CHANNEL_ID,
- getString(R.string.screenrecord_title),
- NotificationManager.IMPORTANCE_DEFAULT);
- channel.setDescription(getString(R.string.screenrecord_channel_description));
- channel.enableVibration(true);
- mNotificationManager.createNotificationChannel(channel);
-
Bundle extras = new Bundle();
extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
res.getString(R.string.screenrecord_title));
@@ -323,7 +317,7 @@
.setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE)
.addAction(stopAction)
.addExtras(extras);
- startForeground(NOTIFICATION_RECORDING_ID, builder.build());
+ startForeground(mNotificationId, builder.build());
}
@VisibleForTesting
@@ -337,11 +331,12 @@
extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
res.getString(R.string.screenrecord_title));
- Notification.Builder builder = new Notification.Builder(getApplicationContext(), CHANNEL_ID)
+ Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
.setContentTitle(notificationTitle)
.setContentText(
getResources().getString(R.string.screenrecord_background_processing_label))
.setSmallIcon(R.drawable.ic_screenrecord)
+ .setGroup(GROUP_KEY)
.addExtras(extras);
return builder.build();
}
@@ -378,6 +373,7 @@
PendingIntent.FLAG_IMMUTABLE))
.addAction(shareAction)
.setAutoCancel(true)
+ .setGroup(GROUP_KEY)
.addExtras(extras);
// Add thumbnail if available
@@ -391,6 +387,24 @@
return builder.build();
}
+ /**
+ * Adds a group notification so that save notifications from multiple recordings are
+ * grouped together, and the foreground service recording notification is not
+ */
+ private void postGroupNotification(UserHandle currentUser) {
+ Bundle extras = new Bundle();
+ extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
+ getResources().getString(R.string.screenrecord_title));
+ Notification groupNotif = new Notification.Builder(this, CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_screenrecord)
+ .setContentTitle(getResources().getString(R.string.screenrecord_save_title))
+ .setGroup(GROUP_KEY)
+ .setGroupSummary(true)
+ .setExtras(extras)
+ .build();
+ mNotificationManager.notifyAsUser(TAG, NOTIF_BASE_ID, groupNotif, currentUser);
+ }
+
private void stopService() {
stopService(USER_ID_NOT_SPECIFIED);
}
@@ -423,27 +437,26 @@
Log.e(TAG, "stopRecording called, but recorder was null");
}
updateState(false);
+ stopForeground(STOP_FOREGROUND_DETACH);
stopSelf();
}
private void saveRecording(int userId) {
UserHandle currentUser = new UserHandle(userId);
- mNotificationManager.notifyAsUser(null, NOTIFICATION_PROCESSING_ID,
+ mNotificationManager.notifyAsUser(null, mNotificationId,
createProcessingNotification(), currentUser);
mLongExecutor.execute(() -> {
try {
Log.d(TAG, "saving recording");
Notification notification = createSaveNotification(getRecorder().save());
- if (!mController.isRecording()) {
- mNotificationManager.notifyAsUser(null, NOTIFICATION_VIEW_ID, notification,
- currentUser);
- }
+ postGroupNotification(currentUser);
+ mNotificationManager.notifyAsUser(null, mNotificationId, notification,
+ currentUser);
} catch (IOException e) {
Log.e(TAG, "Error saving screen recording: " + e.getMessage());
showErrorToast(R.string.screenrecord_delete_error);
- } finally {
- mNotificationManager.cancelAsUser(null, NOTIFICATION_PROCESSING_ID, currentUser);
+ mNotificationManager.cancelAsUser(null, mNotificationId, currentUser);
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 5117915..926ede9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -22,10 +22,10 @@
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
+import static com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE;
+import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
-import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE;
-import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE;
import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
import static com.android.systemui.classifier.Classifier.GENERIC;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
@@ -88,6 +88,7 @@
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
+import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
@@ -111,7 +112,6 @@
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.LaunchAnimator;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.classifier.Classifier;
@@ -1513,6 +1513,8 @@
mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
mKeyguardStatusViewController.isClockTopAligned());
mClockPositionAlgorithm.run(mClockPositionResult);
+ mKeyguardStatusViewController.setLockscreenClockY(
+ mClockPositionAlgorithm.getExpandedPreferredClockY());
mKeyguardBottomAreaInteractor.setClockPosition(
mClockPositionResult.clockX, mClockPositionResult.clockY);
boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
@@ -4474,7 +4476,7 @@
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/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index fb7c5c2..31b361f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -18,7 +18,6 @@
import android.view.View
import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.WindowInsets
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintSet
@@ -62,6 +61,7 @@
private var isQSCustomizing = false
private var isQSCustomizerAnimating = false
+ private var shadeHeaderHeight = 0
private var largeScreenShadeHeaderHeight = 0
private var largeScreenShadeHeaderActive = false
private var notificationsBottomMargin = 0
@@ -146,6 +146,8 @@
R.dimen.notification_panel_margin_bottom)
largeScreenShadeHeaderHeight =
resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height)
+ shadeHeaderHeight =
+ resources.getDimensionPixelSize(R.dimen.qs_header_height)
panelMarginHorizontal = resources.getDimensionPixelSize(
R.dimen.notification_panel_margin_horizontal)
topMargin = if (largeScreenShadeHeaderActive) {
@@ -245,7 +247,7 @@
if (largeScreenShadeHeaderActive) {
constraintSet.constrainHeight(R.id.split_shade_status_bar, largeScreenShadeHeaderHeight)
} else {
- constraintSet.constrainHeight(R.id.split_shade_status_bar, WRAP_CONTENT)
+ constraintSet.constrainHeight(R.id.split_shade_status_bar, shadeHeaderHeight)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 4012736..abdd1a9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -50,6 +50,7 @@
import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
+import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
@@ -59,7 +60,6 @@
import com.android.keyguard.FaceAuthApiRequestReason;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
@@ -85,11 +85,13 @@
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 @@
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 @@
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 @@
NotificationRemoteInputManager remoteInputManager,
ShadeExpansionStateManager shadeExpansionStateManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ LightBarController lightBarController,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
LockscreenShadeTransitionController lockscreenShadeTransitionController,
NotificationShadeDepthController notificationShadeDepthController,
@@ -324,7 +329,8 @@
InteractionJankMonitor interactionJankMonitor,
ShadeLogger shadeLog,
KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
- ShadeRepository shadeRepository
+ ShadeRepository shadeRepository,
+ CastController castController
) {
mPanelViewControllerLazy = panelViewControllerLazy;
mPanelView = panelView;
@@ -343,6 +349,7 @@
mRemoteInputManager = remoteInputManager;
mShadeExpansionStateManager = shadeExpansionStateManager;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mLightBarController = lightBarController;
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mDepthController = notificationShadeDepthController;
@@ -364,6 +371,7 @@
mMetricsLogger = metricsLogger;
mShadeLog = shadeLog;
mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
+ mCastController = castController;
mFeatureFlags = featureFlags;
mInteractionJankMonitor = interactionJankMonitor;
mShadeRepository = shadeRepository;
@@ -879,7 +887,9 @@
}
void setOverScrollAmount(int overExpansion) {
- mQs.setOverScrollAmount(overExpansion);
+ if (mQs != null) {
+ mQs.setOverScrollAmount(overExpansion);
+ }
}
private void setOverScrolling(boolean overscrolling) {
@@ -1014,6 +1024,9 @@
mShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction);
mShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
mShadeHeaderController.setQsVisible(mVisible);
+
+ // Update the light bar
+ mLightBarController.setQsExpanded(mFullyExpanded);
}
float getLockscreenShadeDragProgress() {
@@ -1188,7 +1201,9 @@
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 @@
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/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 86ae4ec..f080d3d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -34,10 +34,10 @@
import android.widget.TextView
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.motion.widget.MotionLayout
+import com.android.app.animation.Interpolators
import com.android.settingslib.Utils
import com.android.systemui.Dumpable
import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 1839e13..25073c1b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -18,8 +18,8 @@
import android.view.MotionEvent
import com.android.systemui.log.dagger.ShadeLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.shade.ShadeViewController.Companion.FLING_COLLAPSE
import com.android.systemui.shade.ShadeViewController.Companion.FLING_EXPAND
import com.android.systemui.shade.ShadeViewController.Companion.FLING_HIDE
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
index 9851625..d8d4279 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
@@ -18,12 +18,12 @@
import android.view.WindowManager
import com.android.systemui.log.dagger.ShadeWindowLog
-import com.android.systemui.plugins.log.ConstantStringsLogger
-import com.android.systemui.plugins.log.ConstantStringsLoggerImpl
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogMessage
+import com.android.systemui.log.ConstantStringsLogger
+import com.android.systemui.log.ConstantStringsLoggerImpl
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogMessage
import javax.inject.Inject
private const val TAG = "systemui.shadewindow"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
index 90c52bd..e008ec0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
@@ -18,8 +18,8 @@
import android.app.PendingIntent
import com.android.systemui.log.dagger.NotifInteractionLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
index 07b6869..b6970ae 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.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 @@
) : 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 @@
}
/**
+ * 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 @@
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/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
index 63179da..c1ebf12 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
@@ -18,8 +18,8 @@
import android.view.View;
+import com.android.app.animation.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
index 54b341f..1a32d70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
@@ -38,8 +38,8 @@
import android.view.animation.Interpolator;
import android.widget.ImageView;
+import com.android.app.animation.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.wm.shell.animation.FlingAnimationUtils;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index ea5a1c0..0ea2570 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -41,8 +41,8 @@
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED;
import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
+import static com.android.systemui.log.LogLevel.ERROR;
import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
-import static com.android.systemui.plugins.log.LogLevel.ERROR;
import android.app.AlarmManager;
import android.app.admin.DevicePolicyManager;
@@ -95,8 +95,8 @@
import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.log.LogLevel;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.log.LogLevel;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 9421524..823bb35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -16,7 +16,7 @@
import android.util.MathUtils.lerp
import android.view.View
import android.view.animation.PathInterpolator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.statusbar.LightRevealEffect.Companion.getPercentPastThreshold
import com.android.systemui.util.getColorWithAlpha
import com.android.systemui.util.leak.RotationUtils
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 7f016f3..b61f243 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -18,7 +18,7 @@
import com.android.systemui.ExpandHelper
import com.android.systemui.Gefingerpoken
import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.biometrics.UdfpsKeyguardViewController
import com.android.systemui.classifier.Classifier
import com.android.systemui.classifier.FalsingCollector
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 72ae16e..fb88a96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -44,9 +44,9 @@
import android.view.View;
import android.widget.ImageView;
+import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dumpable;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 8dc7842..0e20df6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -33,7 +33,7 @@
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.systemui.Dumpable
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
@@ -189,12 +189,7 @@
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 @@
// 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 @@
return
}
updateScheduled = true
+ val (blur, _) = computeBlurAndZoomOut()
+ blurUtils.prepareBlur(root.viewRootImpl, blur)
choreographer.postFrameCallback(updateBlurCallback)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 49c7950..9d7f3be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -35,10 +35,10 @@
import androidx.annotation.NonNull;
+import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index 821a172..fc6eaa8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -31,7 +31,7 @@
import com.android.systemui.Dumpable
import com.android.systemui.Gefingerpoken
import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt
index 575f354..f1e51e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt
@@ -4,7 +4,7 @@
import android.content.res.Configuration
import android.util.MathUtils
import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.policy.ConfigurationController
import dagger.assisted.Assisted
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
index 572c0e0..3d574ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
@@ -8,7 +8,7 @@
import android.view.animation.PathInterpolator
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.qs.QS
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 7755003..91c08a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -52,10 +52,10 @@
import androidx.core.graphics.ColorUtils;
+import com.android.app.animation.Interpolators;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.notification.NotificationIconDozeHelper;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.util.drawable.DrawableSize;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 79d01b4a..d6a14604 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -42,6 +42,7 @@
import androidx.annotation.NonNull;
+import com.android.app.animation.Interpolators;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
@@ -49,7 +50,6 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
index 2fa27ee..67ab060 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
@@ -25,8 +25,8 @@
import android.view.ViewGroup;
import android.view.animation.Interpolator;
+import com.android.app.animation.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.notification.TransformState;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index 6e74542..2465c21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -73,9 +73,9 @@
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.LogLevel;
import com.android.systemui.log.dagger.StatusBarNetworkControllerLog;
-import com.android.systemui.plugins.log.LogBuffer;
-import com.android.systemui.plugins.log.LogLevel;
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
@@ -87,6 +87,8 @@
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.CarrierConfigTracker;
+import kotlin.Unit;
+
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -101,8 +103,6 @@
import javax.inject.Inject;
-import kotlin.Unit;
-
/** Platform implementation of the network controller. **/
@SysUISingleton
public class NetworkControllerImpl extends BroadcastReceiver
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index bfc4e9c..eddb683 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -26,7 +26,7 @@
import android.widget.FrameLayout
import com.android.internal.annotations.GuardedBy
import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt
index 9ce6b02..a67c26c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt
@@ -18,8 +18,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.dagger.SwipeUpLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import javax.inject.Inject
/** Log messages for [SwipeUpGestureHandler]. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
index 0446165..b09b9f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
@@ -21,8 +21,8 @@
import android.view.View;
import android.widget.ImageView;
+import com.android.app.animation.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.notification.row.HybridNotificationView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
index c22dbf6..785e65d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
@@ -3,7 +3,7 @@
import android.util.MathUtils
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.animation.LaunchAnimator
import kotlin.math.min
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
index c22cd1b..5a14200 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
@@ -23,13 +23,13 @@
import android.view.ViewGroup;
import android.widget.TextView;
+import com.android.app.animation.Interpolators;
import com.android.internal.widget.IMessagingLayout;
import com.android.internal.widget.MessagingGroup;
import com.android.internal.widget.MessagingImageMessage;
import com.android.internal.widget.MessagingLinearLayout;
import com.android.internal.widget.MessagingMessage;
import com.android.internal.widget.MessagingPropertyAnimator;
-import com.android.systemui.animation.Interpolators;
import java.util.ArrayList;
import java.util.HashMap;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
index 3058fbb..a3a72d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.notification
import com.android.systemui.log.dagger.NotifInteractionLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
index 3fc7b13..a045698 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
@@ -24,8 +24,8 @@
import android.view.View;
import android.widget.ImageView;
+import com.android.app.animation.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import java.util.function.Consumer;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index fe0b28d..9ba2199 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -21,8 +21,8 @@
import androidx.annotation.VisibleForTesting
import androidx.core.animation.ObjectAnimator
import com.android.systemui.Dumpable
-import com.android.systemui.animation.Interpolators
-import com.android.systemui.animation.InterpolatorsAndroidX
+import com.android.app.animation.Interpolators
+import com.android.app.animation.InterpolatorsAndroidX
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
index dd3c2a9..f7679ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
@@ -13,9 +13,9 @@
package com.android.systemui.statusbar.notification
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
import com.android.systemui.log.dagger.NotificationLockscreenLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
import com.android.systemui.statusbar.StatusBarState
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
index 5d07cac..57d20246 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -24,7 +24,7 @@
import android.view.View;
import android.view.animation.Interpolator;
-import com.android.systemui.animation.Interpolators;
+import com.android.app.animation.Interpolators;
import com.android.systemui.statusbar.notification.stack.AnimationFilter;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ViewState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
index 9582dfad..487a5f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
@@ -17,9 +17,9 @@
package com.android.systemui.statusbar.notification
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
import com.android.systemui.log.dagger.NotificationRemoteInputLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
import javax.inject.Inject
/** Logger class for [RemoteInputController]. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index 9f9fba4..90eb630 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -23,11 +23,11 @@
import android.widget.ProgressBar;
import android.widget.TextView;
+import com.android.app.animation.Interpolators;
import com.android.internal.widget.MessagingImageMessage;
import com.android.internal.widget.MessagingPropertyAnimator;
import com.android.internal.widget.ViewClippingUtil;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.ViewTransformationHelper;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
index dc16274..16f1a45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
@@ -22,7 +22,7 @@
import android.view.View
import android.view.ViewGroup
import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
/**
* Class to help with fading of view groups without fading one subview
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
index 68d1319..39d0833 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.notification.collection.coalescer
import com.android.systemui.log.dagger.NotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import javax.inject.Inject
class GroupCoalescerLogger @Inject constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
index 2919def..79c63e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt
@@ -1,8 +1,8 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import com.android.systemui.log.dagger.NotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.statusbar.notification.row.NotificationGuts
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
index 32c3c66..e17ce5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
@@ -3,8 +3,8 @@
import android.util.Log
import com.android.systemui.log.dagger.NotificationHeadsUpLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import javax.inject.Inject
private const val TAG = "HeadsUpCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
index 6503a64..1f8ec34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.notification.collection.coordinator
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.UnseenNotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
import javax.inject.Inject
private const val TAG = "KeyguardCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
index 9558f47..6271d38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import com.android.systemui.log.dagger.NotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
index d804454..1f4861a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import com.android.systemui.log.dagger.NotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import javax.inject.Inject
private const val TAG = "ShadeEventCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index 4adc90a..f13ff68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -17,10 +17,10 @@
package com.android.systemui.statusbar.notification.collection.listbuilder
import com.android.systemui.log.dagger.NotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.INFO
-import com.android.systemui.plugins.log.LogLevel.WARNING
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.WARNING
import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index 911a2d0..20de785 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -21,12 +21,12 @@
import android.service.notification.NotificationListenerService.RankingMap
import android.service.notification.StatusBarNotification
import com.android.systemui.log.dagger.NotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.ERROR
-import com.android.systemui.plugins.log.LogLevel.INFO
-import com.android.systemui.plugins.log.LogLevel.WARNING
-import com.android.systemui.plugins.log.LogLevel.WTF
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.LogLevel.WTF
import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason
import com.android.systemui.statusbar.notification.collection.NotifCollection.FutureDismissal
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
index 9c71e5c..07fd349 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.notification.collection.render
import com.android.systemui.log.dagger.NotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
import com.android.systemui.util.Compile
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
index 1e22c2c..a880b71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.notification.collection.render
import com.android.systemui.log.dagger.NotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import java.lang.RuntimeException
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
index d4f11fc..0b31265 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
@@ -1,8 +1,8 @@
package com.android.systemui.statusbar.notification.interruption
import com.android.systemui.log.dagger.NotificationHeadsUpLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index 115e0502..5bac2a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -19,10 +19,10 @@
import android.util.Log
import com.android.systemui.log.dagger.NotificationInterruptLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.INFO
-import com.android.systemui.plugins.log.LogLevel.WARNING
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.WARNING
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import com.android.systemui.util.Compile
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
index 10197a3..fe03b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.notification.logging
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.NotificationRenderLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.INFO
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.stack.NotificationSection
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 66d4c3a9..8af488e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -31,12 +31,12 @@
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import com.android.app.animation.Interpolators;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
import com.android.settingslib.Utils;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -733,12 +733,16 @@
super.dump(pw, args);
if (DUMP_VERBOSE) {
DumpUtilsKt.withIncreasedIndent(pw, () -> {
- pw.println("mBackgroundNormal: " + mBackgroundNormal);
- if (mBackgroundNormal != null) {
- DumpUtilsKt.withIncreasedIndent(pw, () -> {
- mBackgroundNormal.dump(pw, args);
- });
- }
+ dumpBackgroundView(pw, args);
+ });
+ }
+ }
+
+ protected void dumpBackgroundView(IndentingPrintWriter pw, String[] args) {
+ pw.println("Background View: " + mBackgroundNormal);
+ if (DUMP_VERBOSE && mBackgroundNormal != null) {
+ DumpUtilsKt.withIncreasedIndent(pw, () -> {
+ mBackgroundNormal.dump(pw, args);
});
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 5978133..ba8a5f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -64,6 +64,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -72,7 +73,6 @@
import com.android.internal.widget.CachingIconView;
import com.android.internal.widget.CallLayout;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -3593,6 +3593,7 @@
// Skip super call; dump viewState ourselves
pw.println("Notification: " + mEntry.getKey());
DumpUtilsKt.withIncreasedIndent(pw, () -> {
+ pw.println(this);
pw.print("visibility: " + getVisibility());
pw.print(", alpha: " + getAlpha());
pw.print(", translation: " + getTranslation());
@@ -3612,6 +3613,7 @@
pw.println("no viewState!!!");
}
pw.println(getRoundableState().debugString());
+ dumpBackgroundView(pw, args);
int transientViewCount = mChildrenContainer == null
? 0 : mChildrenContainer.getTransientViewCount();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index b56bae1..7a2bee9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -45,10 +45,10 @@
import androidx.annotation.VisibleForTesting;
+import com.android.app.animation.Interpolators;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.InstanceIdSequence;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 5edff5f..f0e15c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -32,14 +32,15 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.Roundable;
import com.android.systemui.statusbar.notification.RoundableState;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.util.Compile;
import com.android.systemui.util.DumpUtilsKt;
import java.io.PrintWriter;
@@ -52,7 +53,8 @@
public abstract class ExpandableView extends FrameLayout implements Dumpable, Roundable {
private static final String TAG = "ExpandableView";
/** whether the dump() for this class should include verbose details */
- protected static final boolean DUMP_VERBOSE = false;
+ protected static final boolean DUMP_VERBOSE =
+ Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
private RoundableState mRoundableState = null;
protected OnHeightChangedListener mOnHeightChangedListener;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
index 46fef3f..45be0b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.notification.row
import com.android.systemui.log.dagger.NotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index da8d2d5..647505c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.row;
+import static com.android.systemui.util.ColorUtilKt.hexColorString;
+
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
@@ -27,6 +29,9 @@
import android.util.AttributeSet;
import android.view.View;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.internal.util.ArrayUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
@@ -44,6 +49,7 @@
private int mClipTopAmount;
private int mClipBottomAmount;
private int mTintColor;
+ @Nullable private Integer mRippleColor;
private final float[] mCornerRadii = new float[8];
private boolean mBottomIsRounded;
private boolean mBottomAmountClips = true;
@@ -127,6 +133,7 @@
unscheduleDrawable(mBackground);
}
mBackground = background;
+ mRippleColor = null;
mBackground.mutate();
if (mBackground != null) {
mBackground.setCallback(this);
@@ -215,6 +222,9 @@
if (mBackground instanceof RippleDrawable) {
RippleDrawable ripple = (RippleDrawable) mBackground;
ripple.setColor(ColorStateList.valueOf(color));
+ mRippleColor = color;
+ } else {
+ mRippleColor = null;
}
}
@@ -290,7 +300,7 @@
}
@Override
- public void dump(PrintWriter pw, String[] args) {
+ public void dump(PrintWriter pw, @NonNull String[] args) {
pw.println("mDontModifyCorners: " + mDontModifyCorners);
pw.println("mClipTopAmount: " + mClipTopAmount);
pw.println("mClipBottomAmount: " + mClipBottomAmount);
@@ -299,5 +309,8 @@
pw.println("mBottomAmountClips: " + mBottomAmountClips);
pw.println("mActualWidth: " + mActualWidth);
pw.println("mActualHeight: " + mActualHeight);
+ pw.println("mTintColor: " + hexColorString(mTintColor));
+ pw.println("mRippleColor: " + hexColorString(mRippleColor));
+ pw.println("mBackground: " + mBackground);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 4522e41..b4bfded 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -966,7 +966,7 @@
@Override
public ApplicationInfo getApplicationInfo() {
- ApplicationInfo applicationInfo = super.getApplicationInfo();
+ ApplicationInfo applicationInfo = new ApplicationInfo(super.getApplicationInfo());
applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_RTL;
return applicationInfo;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index f21db0b..9bc0333 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -25,7 +25,7 @@
import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE;
import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT;
-import static com.android.systemui.animation.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
import static java.lang.annotation.RetentionPolicy.SOURCE;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
index 596bdc0..047db20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
@@ -33,9 +33,9 @@
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index 8a50f2f..99a7755 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -20,7 +20,7 @@
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
-import static com.android.systemui.animation.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
import static java.lang.annotation.RetentionPolicy.SOURCE;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index bafc474..5a129fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -39,9 +39,9 @@
import android.widget.FrameLayout;
import android.widget.FrameLayout.LayoutParams;
+import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.AlphaOptimizedImageView;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
index ce11be3..c3dd92a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
@@ -17,9 +17,9 @@
package com.android.systemui.statusbar.notification.row
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
index 5f4c926..d5d7f75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
@@ -45,11 +45,11 @@
import android.widget.LinearLayout;
import android.widget.TextView;
+import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
index 8a5d29a..684a276 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.notification.row
import com.android.systemui.log.dagger.NotificationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index 5aaf63f..b24cec1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -23,8 +23,8 @@
import android.view.View;
import android.view.animation.Interpolator;
+import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.animation.Interpolators;
import java.util.function.Consumer;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 2c59c2e..ef5e86f06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -34,10 +34,10 @@
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
import com.android.internal.widget.CachingIconView;
import com.android.internal.widget.NotificationExpandButton;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.ViewTransformationHelper;
import com.android.systemui.statusbar.notification.CustomInterpolatorTransformation;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
index 7f3381c..d73bbeb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
@@ -22,8 +22,8 @@
import android.animation.ValueAnimator;
import android.view.View;
+import com.android.app.animation.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
index 0b435fe..9a33a94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
@@ -26,7 +26,7 @@
import android.view.View;
import android.view.animation.Interpolator;
-import com.android.systemui.animation.Interpolators;
+import com.android.app.animation.Interpolators;
import com.android.systemui.statusbar.notification.ShadeViewRefactor;
import com.android.systemui.statusbar.notification.row.ExpandableView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
index b61c55e..f953187 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt
@@ -18,8 +18,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.dagger.NotificationSectionLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import javax.inject.Inject
private const val TAG = "NotifSections"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index edff877..cf051fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -75,6 +75,7 @@
import android.widget.OverScroller;
import android.widget.ScrollView;
+import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
import com.android.internal.jank.InteractionJankMonitor;
@@ -86,7 +87,6 @@
import com.android.systemui.Dumpable;
import com.android.systemui.ExpandHelper;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ActivityStarter;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
index 64dd6dc..5b0ec1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
@@ -1,9 +1,9 @@
package com.android.systemui.statusbar.notification.stack
import com.android.systemui.log.dagger.NotificationHeadsUpLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index ee72943..f07dd00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -22,9 +22,9 @@
import android.util.Property;
import android.view.View;
+import com.android.app.animation.Interpolators;
import com.android.keyguard.KeyguardSliceView;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarIconView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
index f5de678..cca84b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
@@ -1,8 +1,8 @@
package com.android.systemui.statusbar.notification.stack
import com.android.systemui.log.dagger.NotificationHeadsUpLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
index d07da38..f4605be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
@@ -26,9 +26,9 @@
import android.view.View;
import android.view.animation.Interpolator;
+import com.android.app.animation.Interpolators;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.NotificationFadeAware.FadeOptimizedNotification;
import com.android.systemui.statusbar.notification.PropertyAnimator;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
index 9dce332..4590712 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
@@ -33,9 +33,9 @@
import android.util.Log;
import android.view.View;
+import com.android.app.animation.Interpolators;
import com.android.settingslib.Utils;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 89c3946..618120d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -37,6 +37,7 @@
import com.android.systemui.doze.DozeLog;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor;
import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -56,10 +57,12 @@
import javax.inject.Inject;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
/**
* Implementation of DozeHost for SystemUI.
*/
-@SysUISingleton
+@ExperimentalCoroutinesApi @SysUISingleton
public final class DozeServiceHost implements DozeHost {
private static final String TAG = "DozeServiceHost";
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
@@ -89,6 +92,7 @@
private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
private final AuthController mAuthController;
private final NotificationIconAreaController mNotificationIconAreaController;
+ private final BurnInInteractor mBurnInInteractor;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private ShadeViewController mNotificationPanel;
private View mAmbientIndicationContainer;
@@ -110,7 +114,8 @@
NotificationShadeWindowController notificationShadeWindowController,
NotificationWakeUpCoordinator notificationWakeUpCoordinator,
AuthController authController,
- NotificationIconAreaController notificationIconAreaController) {
+ NotificationIconAreaController notificationIconAreaController,
+ BurnInInteractor burnInInteractor) {
super();
mDozeLog = dozeLog;
mPowerManager = powerManager;
@@ -129,6 +134,7 @@
mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
mAuthController = authController;
mNotificationIconAreaController = notificationIconAreaController;
+ mBurnInInteractor = burnInInteractor;
mHeadsUpManagerPhone.addListener(mOnHeadsUpChangedListener);
}
@@ -304,6 +310,7 @@
if (mAmbientIndicationContainer instanceof DozeReceiver) {
((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick();
}
+ mBurnInInteractor.dozeTimeTick();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 90a6d0f..561bd91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -23,10 +23,10 @@
import android.content.res.Resources;
import android.util.MathUtils;
+import com.android.app.animation.Interpolators;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.keyguard.KeyguardStatusView;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView;
@@ -239,7 +239,11 @@
}
}
- private int getExpandedPreferredClockY() {
+ /**
+ * give the static topMargin, used for lockscreen clocks to get the initial translationY
+ * to do counter translation
+ */
+ public int getExpandedPreferredClockY() {
if (mIsSplitShade) {
return mSplitShadeTargetTopMargin;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
index 9d30cb4..61c1cc8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
@@ -30,9 +30,9 @@
import androidx.annotation.StyleRes;
+import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.keyguard.KeyguardIndication;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 13566ef..720eeba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -43,9 +43,9 @@
import androidx.annotation.VisibleForTesting;
+import com.android.app.animation.Interpolators;
import com.android.settingslib.Utils;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index e835c5ce..4d716c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -37,15 +37,15 @@
import androidx.core.animation.AnimatorListenerAdapter;
import androidx.core.animation.ValueAnimator;
+import com.android.app.animation.InterpolatorsAndroidX;
import com.android.keyguard.CarrierTextController;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.R;
-import com.android.systemui.animation.InterpolatorsAndroidX;
import com.android.systemui.battery.BatteryMeterViewController;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.plugins.log.LogLevel;
+import com.android.systemui.log.LogLevel;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeViewStateProvider;
import com.android.systemui.statusbar.CommandQueue;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
index 4839fe6..5c357d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
@@ -20,8 +20,8 @@
import android.view.View
import com.android.internal.logging.nano.MetricsProto.MetricsEvent
import com.android.systemui.log.dagger.LSShadeTransitionLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import javax.inject.Inject
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 534edb9..a058bf8 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 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.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 @@
@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 @@
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 @@
* {@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 @@
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 @@
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 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 @@
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 @@
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 @@
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 @@
/** 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/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
index 6bf5443..7bc4fc3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
@@ -24,21 +24,21 @@
import android.util.MathUtils;
import android.util.TimeUtils;
+import com.android.app.animation.Interpolators;
import com.android.systemui.Dumpable;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-
import dagger.assisted.Assisted;
import dagger.assisted.AssistedFactory;
import dagger.assisted.AssistedInject;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+
/**
* Class to control all aspects about light bar changes.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index cc4f901..46a2457 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -31,9 +31,9 @@
import android.util.SparseArray;
import android.view.ViewTreeObserver.OnPreDrawListener;
+import com.android.app.animation.Interpolators;
import com.android.internal.graphics.ColorUtils;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.KeyguardAffordanceView;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 55dc188..560ea8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -15,11 +15,11 @@
import androidx.annotation.VisibleForTesting;
import androidx.collection.ArrayMap;
+import com.android.app.animation.Interpolators;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.ContrastColorUtil;
import com.android.settingslib.Utils;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
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 006a029d..bef422c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -36,10 +36,10 @@
import androidx.annotation.VisibleForTesting;
import androidx.collection.ArrayMap;
+import com.android.app.animation.Interpolators;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.settingslib.Utils;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.stack.AnimationFilter;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
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 51c56a0..fdb772b 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 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 @@
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 @@
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 @@
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 @@
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/SettingsButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
index 5e5317d7..07a6d0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
@@ -29,7 +29,7 @@
import android.view.animation.AnimationUtils;
import android.widget.Button;
-import com.android.systemui.animation.Interpolators;
+import com.android.app.animation.Interpolators;
import com.android.systemui.statusbar.AlphaOptimizedImageView;
public class SettingsButton extends AlphaOptimizedImageView {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
index 1f0b96a..12f023b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
@@ -18,11 +18,11 @@
import android.app.PendingIntent
import com.android.systemui.log.dagger.NotifInteractionLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogLevel.ERROR
-import com.android.systemui.plugins.log.LogLevel.INFO
-import com.android.systemui.plugins.log.LogLevel.WARNING
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.WARNING
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 8fa803e..cdf6652 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -15,7 +15,7 @@
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
import com.android.systemui.DejankUtils
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.WakefulnessLifecycle
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 453dd1b..5af8932 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -14,11 +14,7 @@
package com.android.systemui.statusbar.phone.fragment;
-import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
-import static android.app.StatusBarManager.DISABLE_CLOCK;
-import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS;
-import static android.app.StatusBarManager.DISABLE_ONGOING_CALL_CHIP;
-import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO;
+
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE;
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.SHOWING_PERSISTENT_DOT;
@@ -44,10 +40,10 @@
import androidx.annotation.VisibleForTesting;
import androidx.core.animation.Animator;
+import com.android.app.animation.Interpolators;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
@@ -112,8 +108,13 @@
private View mClockView;
private View mOngoingCallChip;
private View mNotificationIconAreaInner;
- private int mDisabled1;
- private int mDisabled2;
+ // Visibilities come in from external system callers via disable flags, but we also sometimes
+ // modify the visibilities internally. We need to store both so that we don't accidentally
+ // propagate our internally modified flags for too long.
+ private StatusBarVisibilityModel mLastSystemVisibility =
+ StatusBarVisibilityModel.createDefaultModel();
+ private StatusBarVisibilityModel mLastModifiedVisibility =
+ StatusBarVisibilityModel.createDefaultModel();
private DarkIconManager mDarkIconManager;
private final StatusBarFragmentComponent.Factory mStatusBarFragmentComponentFactory;
private final CommandQueue mCommandQueue;
@@ -141,7 +142,7 @@
private final OngoingCallListener mOngoingCallListener = new OngoingCallListener() {
@Override
public void onOngoingCallStateChanged(boolean animate) {
- disable(getContext().getDisplayId(), mDisabled1, mDisabled2, animate);
+ updateStatusBarVisibilities(animate);
}
};
private OperatorNameViewController mOperatorNameViewController;
@@ -388,8 +389,7 @@
}
notificationIconArea.addView(mNotificationIconAreaInner);
- // #disable should have already been called, so use the disable values to set visibility.
- updateNotificationIconAreaAndCallChip(mDisabled1, false);
+ updateNotificationIconAreaAndCallChip(/* animate= */ false);
}
/**
@@ -408,49 +408,50 @@
if (displayId != getContext().getDisplayId()) {
return;
}
+ mCollapsedStatusBarFragmentLogger
+ .logDisableFlagChange(new DisableState(state1, state2));
+ mLastSystemVisibility =
+ StatusBarVisibilityModel.createModelFromFlags(state1, state2);
+ updateStatusBarVisibilities(animate);
+ }
- int state1BeforeAdjustment = state1;
- state1 = adjustDisableFlags(state1);
+ private void updateStatusBarVisibilities(boolean animate) {
+ StatusBarVisibilityModel previousModel = mLastModifiedVisibility;
+ StatusBarVisibilityModel newModel = calculateInternalModel(mLastSystemVisibility);
+ mCollapsedStatusBarFragmentLogger.logVisibilityModel(newModel);
+ mLastModifiedVisibility = newModel;
- mCollapsedStatusBarFragmentLogger.logDisableFlagChange(
- /* new= */ new DisableState(state1BeforeAdjustment, state2),
- /* newAfterLocalModification= */ new DisableState(state1, state2));
-
- final int old1 = mDisabled1;
- final int diff1 = state1 ^ old1;
- final int old2 = mDisabled2;
- final int diff2 = state2 ^ old2;
- mDisabled1 = state1;
- mDisabled2 = state2;
- if ((diff1 & DISABLE_SYSTEM_INFO) != 0 || ((diff2 & DISABLE2_SYSTEM_ICONS) != 0)) {
- if ((state1 & DISABLE_SYSTEM_INFO) != 0 || ((state2 & DISABLE2_SYSTEM_ICONS) != 0)) {
- hideEndSideContent(animate);
- hideOperatorName(animate);
- } else {
+ if (newModel.getShowSystemInfo() != previousModel.getShowSystemInfo()) {
+ if (newModel.getShowSystemInfo()) {
showEndSideContent(animate);
showOperatorName(animate);
+ } else {
+ hideEndSideContent(animate);
+ hideOperatorName(animate);
}
}
// The ongoing call chip and notification icon visibilities are intertwined, so update both
// if either change.
- if (((diff1 & DISABLE_ONGOING_CALL_CHIP) != 0)
- || ((diff1 & DISABLE_NOTIFICATION_ICONS) != 0)) {
- updateNotificationIconAreaAndCallChip(state1, animate);
+ if (newModel.getShowNotificationIcons() != previousModel.getShowNotificationIcons()
+ || newModel.getShowOngoingCallChip() != previousModel.getShowOngoingCallChip()) {
+ updateNotificationIconAreaAndCallChip(animate);
}
// The clock may have already been hidden, but we might want to shift its
// visibility to GONE from INVISIBLE or vice versa
- if ((diff1 & DISABLE_CLOCK) != 0 || mClockView.getVisibility() != clockHiddenMode()) {
- if ((state1 & DISABLE_CLOCK) != 0) {
- hideClock(animate);
- } else {
+ if (newModel.getShowClock() != previousModel.getShowClock()
+ || mClockView.getVisibility() != clockHiddenMode()) {
+ if (newModel.getShowClock()) {
showClock(animate);
+ } else {
+ hideClock(animate);
}
}
}
- protected int adjustDisableFlags(int state) {
+ private StatusBarVisibilityModel calculateInternalModel(
+ StatusBarVisibilityModel externalModel) {
boolean headsUpVisible =
mStatusBarFragmentComponent.getHeadsUpAppearanceController().shouldBeVisible();
@@ -459,34 +460,31 @@
&& shouldHideNotificationIcons()
&& !(mStatusBarStateController.getState() == StatusBarState.KEYGUARD
&& headsUpVisible)) {
- state |= DISABLE_NOTIFICATION_ICONS;
- state |= DISABLE_SYSTEM_INFO;
- state |= DISABLE_CLOCK;
+ // Hide everything
+ return new StatusBarVisibilityModel(
+ /* showClock= */ false,
+ /* showNotificationIcons= */ false,
+ /* showOngoingCallChip= */ false,
+ /* showSystemInfo= */ false);
}
- if (mOngoingCallController.hasOngoingCall()) {
- state &= ~DISABLE_ONGOING_CALL_CHIP;
- } else {
- state |= DISABLE_ONGOING_CALL_CHIP;
- }
-
- if (headsUpVisible) {
- // Disable everything on the left side of the status bar, since the app name for the
- // heads up notification appears there instead.
- state |= DISABLE_CLOCK;
- state |= DISABLE_ONGOING_CALL_CHIP;
- }
-
- return state;
+ boolean showClock = externalModel.getShowClock() && !headsUpVisible;
+ boolean showOngoingCallChip = mOngoingCallController.hasOngoingCall() && !headsUpVisible;
+ return new StatusBarVisibilityModel(
+ showClock,
+ externalModel.getShowNotificationIcons(),
+ showOngoingCallChip,
+ externalModel.getShowSystemInfo());
}
/**
* Updates the visibility of the notification icon area and ongoing call chip based on disabled1
* state.
*/
- private void updateNotificationIconAreaAndCallChip(int state1, boolean animate) {
- boolean disableNotifications = (state1 & DISABLE_NOTIFICATION_ICONS) != 0;
- boolean hasOngoingCall = (state1 & DISABLE_ONGOING_CALL_CHIP) == 0;
+ private void updateNotificationIconAreaAndCallChip(boolean animate) {
+ StatusBarVisibilityModel visibilityModel = mLastModifiedVisibility;
+ boolean disableNotifications = !visibilityModel.getShowNotificationIcons();
+ boolean hasOngoingCall = visibilityModel.getShowOngoingCallChip();
// Hide notifications if the disable flag is set or we have an ongoing call.
if (disableNotifications || hasOngoingCall) {
@@ -683,7 +681,7 @@
@Override
public void onDozingChanged(boolean isDozing) {
- disable(getContext().getDisplayId(), mDisabled1, mDisabled2, false /* animate */);
+ updateStatusBarVisibilities(/* animate= */ false);
}
@Nullable
@@ -698,10 +696,6 @@
return mSystemEventAnimator.onSystemEventAnimationFinish(hasPersistentDot);
}
- private boolean isSystemIconAreaDisabled() {
- return (mDisabled1 & DISABLE_SYSTEM_INFO) != 0 || (mDisabled2 & DISABLE2_SYSTEM_ICONS) != 0;
- }
-
private void updateStatusBarLocation(int left, int right) {
int leftMargin = left - mStatusBar.getLeft();
int rightMargin = mStatusBar.getRight() - right;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt
index d64bc58..8c19fb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.phone.fragment
import com.android.systemui.log.dagger.CollapsedSbFragmentLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
import javax.inject.Inject
@@ -37,7 +37,6 @@
*/
fun logDisableFlagChange(
new: DisableFlagsLogger.DisableState,
- newAfterLocalModification: DisableFlagsLogger.DisableState
) {
buffer.log(
TAG,
@@ -45,19 +44,34 @@
{
int1 = new.disable1
int2 = new.disable2
- long1 = newAfterLocalModification.disable1.toLong()
- long2 = newAfterLocalModification.disable2.toLong()
},
{
disableFlagsLogger.getDisableFlagsString(
old = null,
new = DisableFlagsLogger.DisableState(int1, int2),
- newAfterLocalModification =
- DisableFlagsLogger.DisableState(long1.toInt(), long2.toInt())
)
}
)
}
+
+ fun logVisibilityModel(model: StatusBarVisibilityModel) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ bool1 = model.showClock
+ bool2 = model.showNotificationIcons
+ bool3 = model.showOngoingCallChip
+ bool4 = model.showSystemInfo
+ },
+ { "New visibilities calculated internally. " +
+ "showClock=$bool1 " +
+ "showNotificationIcons=$bool2 " +
+ "showOngoingCallChip=$bool3 " +
+ "showSystemInfo=$bool4"
+ }
+ )
+ }
}
private const val TAG = "CollapsedSbFragment"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModel.kt
new file mode 100644
index 0000000..cf54cb7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModel.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.fragment
+
+import android.app.StatusBarManager.DISABLE2_NONE
+import android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS
+import android.app.StatusBarManager.DISABLE_CLOCK
+import android.app.StatusBarManager.DISABLE_NONE
+import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS
+import android.app.StatusBarManager.DISABLE_ONGOING_CALL_CHIP
+import android.app.StatusBarManager.DISABLE_SYSTEM_INFO
+
+/** A model for which parts of the status bar should be visible or not visible. */
+data class StatusBarVisibilityModel(
+ val showClock: Boolean,
+ val showNotificationIcons: Boolean,
+ val showOngoingCallChip: Boolean,
+ val showSystemInfo: Boolean,
+) {
+ companion object {
+ /** Creates the default model. */
+ @JvmStatic
+ fun createDefaultModel(): StatusBarVisibilityModel {
+ return createModelFromFlags(DISABLE_NONE, DISABLE2_NONE)
+ }
+
+ /**
+ * Given a set of disabled flags, converts them into the correct visibility statuses.
+ *
+ * See [CommandQueue.Callbacks.disable].
+ */
+ @JvmStatic
+ fun createModelFromFlags(disabled1: Int, disabled2: Int): StatusBarVisibilityModel {
+ return StatusBarVisibilityModel(
+ showClock = (disabled1 and DISABLE_CLOCK) == 0,
+ showNotificationIcons = (disabled1 and DISABLE_NOTIFICATION_ICONS) == 0,
+ // TODO(b/279899176): [CollapsedStatusBarFragment] always overwrites this with the
+ // value of [OngoingCallController]. Do we need to process the flag here?
+ showOngoingCallChip = (disabled1 and DISABLE_ONGOING_CALL_CHIP) == 0,
+ showSystemInfo =
+ (disabled1 and DISABLE_SYSTEM_INFO) == 0 &&
+ (disabled2 and DISABLE2_SYSTEM_ICONS) == 0
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index b3d2461..19c77e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -22,7 +22,7 @@
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
-import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.log.LogBuffer
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index 68cbbce..b3a1c40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -22,8 +22,8 @@
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.statusbar.pipeline.dagger.MobileInputLog
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
index f2f9143..7e0c145 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
@@ -20,8 +20,8 @@
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.statusbar.pipeline.dagger.MobileViewLog
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
import java.io.PrintWriter
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
index f67bc8f..507549b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
@@ -19,8 +19,8 @@
import android.view.View
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.statusbar.pipeline.dagger.VerboseMobileViewLog
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger.Companion.getIdForLogging
import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt
index 82492ba..051f43f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt
@@ -19,8 +19,8 @@
import android.net.Network
import android.net.NetworkCapabilities
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.statusbar.pipeline.dagger.SharedConnectivityInputLog
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
index a96e8ff..328d901 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
@@ -18,8 +18,8 @@
import android.net.Network
import android.net.NetworkCapabilities
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
/** Helper object for logs that are shared between wifi and mobile. */
object LoggerHelper {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt
index 2a02687..058eda4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.pipeline.shared.data.model
import android.net.NetworkCapabilities
-import com.android.systemui.plugins.log.LogMessage
+import com.android.systemui.log.LogMessage
/**
* A model for all of the current default connections(s).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
index bb0b166..4a9ceac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
@@ -19,8 +19,8 @@
import android.net.Network
import android.net.NetworkCapabilities
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.statusbar.pipeline.dagger.WifiInputLog
import com.android.systemui.statusbar.pipeline.shared.LoggerHelper
import javax.inject.Inject
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 98cde2a..abedd3220 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 @@
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 7290a1a..f7b601b 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 @@
}
}
+ @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/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt
index f61f3b7..6ba2a81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt
@@ -21,9 +21,9 @@
import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED
import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED
import com.android.internal.R
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.VERBOSE
import com.android.systemui.log.dagger.DeviceStateAutoRotationLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.VERBOSE
import javax.inject.Inject
class DeviceStateRotationLockSettingControllerLogger
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index df1e80b..06ed1fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -17,9 +17,9 @@
package com.android.systemui.statusbar.policy
import com.android.systemui.log.dagger.NotificationHeadsUpLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel.INFO
-import com.android.systemui.plugins.log.LogLevel.VERBOSE
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.VERBOSE
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
index 4dd63be..e1ec94f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserDetailItemView.java
@@ -24,9 +24,9 @@
import androidx.core.graphics.ColorUtils;
+import com.android.app.animation.Interpolators;
import com.android.keyguard.KeyguardConstants;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.qs.tiles.UserDetailItemView;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index 928e011..66b5256 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -31,6 +31,7 @@
import android.view.View;
import android.view.ViewGroup;
+import com.android.app.animation.Interpolators;
import com.android.keyguard.KeyguardConstants;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -38,7 +39,6 @@
import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
import com.android.settingslib.drawable.CircleFramedDrawable;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
index 850a4b4..363b06a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
@@ -21,11 +21,11 @@
import android.util.Log;
import android.view.View;
+import com.android.app.animation.Interpolators;
import com.android.keyguard.AlphaOptimizedLinearLayout;
import com.android.keyguard.KeyguardConstants;
import com.android.settingslib.animation.AppearAnimationUtils;
import com.android.settingslib.animation.DisappearAnimationUtils;
-import com.android.systemui.animation.Interpolators;
/**
* The container for the user switcher on Keyguard.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 403a7e8..e311bad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -73,6 +73,7 @@
import androidx.core.animation.ObjectAnimator;
import androidx.core.animation.ValueAnimator;
+import com.android.app.animation.InterpolatorsAndroidX;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
import com.android.internal.logging.UiEvent;
@@ -80,7 +81,6 @@
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.animation.InterpolatorsAndroidX;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
index b563d86..21d0338 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
@@ -311,7 +311,7 @@
setBounds(0, 0, newIconSize, newIconSize)
}
// Add the action icon to the Smart Action button.
- setCompoundDrawables(iconDrawable, null, null, null)
+ setCompoundDrawablesRelative(iconDrawable, null, null, null)
val onClickListener = View.OnClickListener {
onSmartActionClick(entry, smartActions, actionIndex, action)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 9e88ceb..fb6ba85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -588,15 +588,15 @@
}
/**
- * Returns the combined width of the left drawable (the action icon) and the padding between the
- * drawable and the button text.
+ * Returns the combined width of the start drawable (the action icon) and the padding between
+ * the drawable and the button text.
*/
- private int getLeftCompoundDrawableWidthWithPadding(Button button) {
- Drawable[] drawables = button.getCompoundDrawables();
- Drawable leftDrawable = drawables[0];
- if (leftDrawable == null) return 0;
+ private int getStartCompoundDrawableWidthWithPadding(Button button) {
+ Drawable[] drawables = button.getCompoundDrawablesRelative();
+ Drawable startDrawable = drawables[0];
+ if (startDrawable == null) return 0;
- return leftDrawable.getBounds().width() + button.getCompoundDrawablePadding();
+ return startDrawable.getBounds().width() + button.getCompoundDrawablePadding();
}
private int squeezeButtonToTextWidth(Button button, int heightMeasureSpec, int textWidth) {
@@ -605,8 +605,8 @@
// Re-measure the squeezed smart reply button.
clearLayoutLineCount(button);
final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(
- button.getPaddingLeft() + button.getPaddingRight() + textWidth
- + getLeftCompoundDrawableWidthWithPadding(button), MeasureSpec.AT_MOST);
+ button.getPaddingStart() + button.getPaddingEnd() + textWidth
+ + getStartCompoundDrawableWidthWithPadding(button), MeasureSpec.AT_MOST);
button.measure(widthMeasureSpec, heightMeasureSpec);
if (button.getLayout() == null) {
Log.wtf(TAG, "Button layout is null after measure.");
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
index 412b315..27aaa68 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.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.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 @@
fun startListener() {
handler.post {
if (hasStarted) return@post
- logDebug { "Listener has started." }
+ debugLog { "Listener has started." }
hasStarted = true
isInUsiSession =
@@ -109,7 +109,7 @@
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 @@
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 @@
if (!hasStarted) return
if (!inputDeviceAddressMap.contains(deviceId)) return
- logDebug { "Stylus InputDevice removed: $deviceId" }
+ debugLog { "Stylus InputDevice removed: $deviceId" }
unregisterBatteryListener(deviceId)
@@ -180,7 +180,7 @@
val isCharging = String(value) == "true"
- logDebug {
+ debugLog {
"Charging state metadata changed for device $inputDeviceId " +
"${device.address}: $isCharging"
}
@@ -199,7 +199,7 @@
handler.post {
if (!hasStarted) return@post
- logDebug {
+ debugLog {
"Battery state changed for $deviceId. " +
"batteryState present: ${batteryState.isPresent}, " +
"capacity: ${batteryState.capacity}"
@@ -247,7 +247,7 @@
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 @@
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 @@
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 @@
btAddress: String,
btConnected: Boolean
) {
- logDebug {
+ debugLog {
"Bluetooth stylus ${if (btConnected) "connected" else "disconnected"}:" +
" $deviceId $btAddress"
}
@@ -386,9 +386,3 @@
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 21b0efa..6eddd9e 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.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.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 @@
inputDeviceId = deviceId
batteryCapacity = batteryState.capacity
- logDebug {
+ debugLog {
"Updating notification battery state to $batteryCapacity " +
"for InputDevice $deviceId."
}
@@ -130,14 +130,14 @@
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 @@
.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 @@
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 @@
@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/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
index 667e22a..066ac04 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -17,8 +17,8 @@
package com.android.systemui.temporarydisplay
import android.view.View
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
/** A logger for temporary view changes -- see [TemporaryViewDisplayController]. */
open class TemporaryViewLogger<T : TemporaryViewInfo>(
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt
index 1612388..46954b5 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt
@@ -18,7 +18,7 @@
import android.view.View
import android.view.ViewGroup
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
import com.android.systemui.animation.ViewHierarchyAnimator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.util.children
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index e819f94..4fbbc89 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -34,10 +34,10 @@
import androidx.annotation.DimenRes
import androidx.annotation.IdRes
import androidx.annotation.VisibleForTesting
+import com.android.app.animation.Interpolators
import com.android.internal.widget.CachingIconView
import com.android.systemui.Gefingerpoken
import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Text.Companion.loadText
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
index f239428..d55751b 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
@@ -17,8 +17,8 @@
package com.android.systemui.temporarydisplay.chipbar
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.temporarydisplay.TemporaryViewLogger
import com.android.systemui.temporarydisplay.dagger.ChipbarLog
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
index b1be404..cae1308 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
@@ -18,9 +18,9 @@
import android.content.Context
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.media.taptotransfer.MediaTttFlags
-import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.settings.DisplayTracker
import com.android.systemui.statusbar.gesture.SwipeUpGestureLogger
import com.android.systemui.temporarydisplay.chipbar.SwipeChipbarAwayGestureHandler
diff --git a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
index f0fa779..58f2246 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
+++ b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
@@ -51,6 +51,7 @@
Pair.create("surface_variant", MDC.surfaceVariant()),
Pair.create("on_surface_variant", MDC.onSurfaceVariant()),
Pair.create("outline", MDC.outline()),
+ Pair.create("outline_variant", MDC.outlineVariant()),
Pair.create("error", MDC.error()),
Pair.create("on_error", MDC.onError()),
Pair.create("error_container", MDC.errorContainer()),
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 43d15bc..4b73d61 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -659,6 +659,10 @@
== mColorScheme.getNeutral1().getS500()
&& res.getColor(android.R.color.system_neutral2_500, theme)
== mColorScheme.getNeutral2().getS500()
+ && res.getColor(android.R.color.system_outline_variant_dark, theme)
+ == MaterialDynamicColors.outlineVariant().getArgb(mDynamicSchemeDark)
+ && res.getColor(android.R.color.system_outline_variant_light, theme)
+ == MaterialDynamicColors.outlineVariant().getArgb(mDynamicSchemeLight)
&& res.getColor(android.R.color.system_primary_container_dark, theme)
== MaterialDynamicColors.primaryContainer().getArgb(mDynamicSchemeDark)
&& res.getColor(android.R.color.system_primary_container_light, theme)
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
index fda5114..dfe748a 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
@@ -17,10 +17,10 @@
package com.android.systemui.toast
import com.android.systemui.log.dagger.ToastLog
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.plugins.log.LogMessage
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogMessage
import javax.inject.Inject
private const val TAG = "ToastLog"
diff --git a/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto b/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto
index eb23b9d..d940a6b 100644
--- a/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto
+++ b/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto
@@ -16,8 +16,6 @@
syntax = "proto2";
-import "frameworks/base/libs/WindowManager/Shell/proto/wm_shell_trace.proto";
-
package com.android.systemui.tracing;
option java_multiple_files = true;
@@ -25,7 +23,6 @@
message SystemUiTraceProto {
optional EdgeBackGestureHandlerProto edge_back_gesture_handler = 1;
- optional com.android.wm.shell.WmShellTraceProto wm_shell = 2;
}
message EdgeBackGestureHandlerProto {
diff --git a/packages/SystemUI/src/com/android/systemui/util/ColorUtil.kt b/packages/SystemUI/src/com/android/systemui/util/ColorUtil.kt
index 27a53bf..41b3145 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ColorUtil.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/ColorUtil.kt
@@ -19,6 +19,7 @@
import android.content.res.TypedArray
import android.graphics.Color
import android.view.ContextThemeWrapper
+import androidx.annotation.ColorInt
/** Returns an ARGB color version of [color] at the given [alpha]. */
fun getColorWithAlpha(color: Int, alpha: Float): Int =
@@ -35,8 +36,11 @@
* otherwise, returns the color from the private attribute {@param privAttrId}.
*/
fun getPrivateAttrColorIfUnset(
- ctw: ContextThemeWrapper, attrArray: TypedArray,
- attrIndex: Int, defColor: Int, privAttrId: Int
+ ctw: ContextThemeWrapper,
+ attrArray: TypedArray,
+ attrIndex: Int,
+ defColor: Int,
+ privAttrId: Int
): Int {
// If the index is specified, use that value
var a = attrArray
@@ -51,3 +55,8 @@
a.recycle()
return color
}
+
+/** Returns the color as a HTML hex color (or null) */
+fun hexColorString(@ColorInt color: Int?): String = color
+ ?.let { String.format("#%08x", it) }
+ ?: "null"
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt
index 5d80292..db4ab7e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt
@@ -19,7 +19,7 @@
import android.animation.ValueAnimator
import android.graphics.PointF
import android.util.MathUtils
-import com.android.systemui.animation.Interpolators
+import com.android.app.animation.Interpolators
/**
* The fraction after which we start fading in when going from a gone widget to a visible one
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLog.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLog.java
index 59cb052..9cebc33 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLog.java
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLog.java
@@ -18,7 +18,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt
index 951903d..0926800 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt
@@ -17,8 +17,8 @@
package com.android.systemui.util.wakelock
import android.os.PowerManager
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import javax.inject.Inject
class WakeLockLogger @Inject constructor(@WakeLockLog private val buffer: LogBuffer) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/wrapper/DisplayUtilsWrapper.kt b/packages/SystemUI/src/com/android/systemui/util/wrapper/DisplayUtilsWrapper.kt
new file mode 100644
index 0000000..cfca7f9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/wrapper/DisplayUtilsWrapper.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.util.wrapper
+
+import android.util.DisplayUtils
+import android.view.Display
+import javax.inject.Inject
+
+/** Injectable wrapper around `DisplayUtils` functions */
+class DisplayUtilsWrapper @Inject constructor() {
+ fun getPhysicalPixelDisplaySizeRatio(
+ physicalWidth: Int,
+ physicalHeight: Int,
+ currentWidth: Int,
+ currentHeight: Int
+ ): Float {
+ return DisplayUtils.getPhysicalPixelDisplaySizeRatio(
+ physicalWidth,
+ physicalHeight,
+ currentWidth,
+ currentHeight
+ )
+ }
+
+ fun getMaximumResolutionDisplayMode(modes: Array<Display.Mode>?): Display.Mode? {
+ return DisplayUtils.getMaximumResolutionDisplayMode(modes)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 77210b7..91078dc 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -109,6 +109,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
@@ -119,7 +120,6 @@
import com.android.systemui.Dumpable;
import com.android.systemui.Prefs;
import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index b3e7cb0..5144d19 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.graphics.Rect;
import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
+import android.view.Display;
import android.view.KeyEvent;
import androidx.annotation.NonNull;
@@ -347,19 +348,20 @@
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
public void writeToProto(SystemUiTraceProto proto) {
- if (proto.wmShell == null) {
- proto.wmShell = new WmShellTraceProto();
- }
// Dump to WMShell proto here
// TODO: Figure out how we want to synchronize while dumping to proto
}
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index e2b568c..080be6d 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/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 8f4b320..19d5278 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -35,7 +35,7 @@
import com.android.systemui.plugins.ClockFaceConfig
import com.android.systemui.plugins.ClockFaceEvents
import com.android.systemui.plugins.ClockTickRate
-import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.log.LogBuffer
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 95db0c0..fb73845 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -45,6 +45,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.log.LogBuffer;
import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.plugins.ClockController;
import com.android.systemui.plugins.ClockEvents;
@@ -52,7 +53,6 @@
import com.android.systemui.plugins.ClockFaceController;
import com.android.systemui.plugins.ClockFaceEvents;
import com.android.systemui.plugins.ClockTickRate;
-import com.android.systemui.plugins.log.LogBuffer;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.shared.clocks.ClockRegistry;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index f4df26d..8a05a37 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 @@
}
@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/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 2962c14..ddd9a08 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -151,7 +151,9 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
+import org.mockito.InOrder;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
import org.mockito.internal.util.reflection.FieldSetter;
@@ -2737,6 +2739,36 @@
verifyFingerprintAuthenticateCall();
}
+ @Test
+ public void onTrustChangedCallbacksCalledBeforeOnTrustGrantedForCurrentUserCallback() {
+ // GIVEN device is interactive
+ deviceIsInteractive();
+
+ // GIVEN callback is registered
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN onTrustChanged enabled=true
+ mKeyguardUpdateMonitor.onTrustChanged(
+ true /* enabled */,
+ true /* newlyUnlocked */,
+ getCurrentUser() /* userId */,
+ TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
+ null /* trustGrantedMessages */);
+
+ // THEN onTrustChanged is called FIRST
+ final InOrder inOrder = Mockito.inOrder(callback);
+ inOrder.verify(callback).onTrustChanged(eq(getCurrentUser()));
+
+ // AND THEN onTrustGrantedForCurrentUser callback called
+ inOrder.verify(callback).onTrustGrantedForCurrentUser(
+ eq(true) /* dismissKeyguard */,
+ eq(true) /* newlyUnlocked */,
+ eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD)),
+ eq(null) /* message */
+ );
+ }
+
private void verifyFingerprintAuthenticateNeverCalled() {
verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any());
verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
deleted file mode 100644
index 2c680be..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.animation
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import java.lang.reflect.Modifier
-import junit.framework.Assert.assertEquals
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@SmallTest
-@RunWith(JUnit4::class)
-class InterpolatorsAndroidXTest : SysuiTestCase() {
-
- @Test
- fun testInterpolatorsAndInterpolatorsAndroidXPublicMethodsAreEqual() {
- assertEquals(
- Interpolators::class.java.getPublicMethods(),
- InterpolatorsAndroidX::class.java.getPublicMethods()
- )
- }
-
- @Test
- fun testInterpolatorsAndInterpolatorsAndroidXPublicFieldsAreEqual() {
- assertEquals(
- Interpolators::class.java.getPublicFields(),
- InterpolatorsAndroidX::class.java.getPublicFields()
- )
- }
-
- private fun <T> Class<T>.getPublicMethods() =
- declaredMethods
- .filter { Modifier.isPublic(it.modifiers) }
- .map { it.toString().replace(name, "") }
- .toSet()
-
- private fun <T> Class<T>.getPublicFields() =
- fields.filter { Modifier.isPublic(it.modifiers) }.map { it.name }.toSet()
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
index d7aa6e0..14ad3ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
@@ -39,10 +39,6 @@
import kotlin.math.ceil
-private val PAINT = TextPaint().apply {
- textSize = 32f
-}
-
@RunWith(AndroidTestingRunner::class)
@SmallTest
class TextAnimatorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt
index 063757a..f6fcd16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt
@@ -49,7 +49,7 @@
private fun Font.toTypeface() =
Typeface.CustomFallbackBuilder(FontFamily.Builder(this).build()).build()
-private val PAINT = TextPaint().apply {
+internal val PAINT = TextPaint().apply {
typeface = Font.Builder(VF_FONT).setFontVariationSettings("'wght' 400").build().toTypeface()
textSize = 32f
}
@@ -79,7 +79,7 @@
@Before
fun setup() {
- typefaceCache = TypefaceVariantCacheImpl()
+ typefaceCache = TypefaceVariantCacheImpl(PAINT.typeface)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
index 6ab54a3..da9ceb4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -19,6 +19,7 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import com.android.app.animation.Interpolators
@SmallTest
@RunWith(AndroidTestingRunner::class)
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 a20875b..a361bbc 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.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
@@ -63,7 +68,6 @@
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
-@Ignore("b/279650412")
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
@@ -87,13 +91,26 @@
@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
@@ -263,6 +280,7 @@
assertThat(authContainer!!.parent).isNull()
}
+ @Ignore("b/279650412")
@Test
fun testActionUseDeviceCredential_sendsOnDeviceCredentialPressed() {
val container = initializeFingerprintContainer(
@@ -469,9 +487,10 @@
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 4f24b3a..a326cc7 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.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 @@
@Mock
private BiometricPromptCredentialInteractor mBiometricPromptCredentialInteractor;
@Mock
+ private AuthBiometricFingerprintViewModel mAuthBiometricFingerprintViewModel;
+ @Mock
private CredentialViewModel mCredentialViewModel;
@Mock
private UdfpsUtils mUdfpsUtils;
@@ -995,8 +998,9 @@
() -> 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 e6334cf..40d9009 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.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.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 @@
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 @@
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 @@
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 @@
isReverseDefaultRotation: Boolean = false,
initInfo: DisplayInfo.() -> Unit = {},
windowInsets: WindowInsets = insetsForSmallNavbar(),
+ inRearDisplayMode: Boolean = false,
block: () -> Unit
) {
this.deviceConfig = deviceConfig
@@ -228,6 +248,13 @@
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 @@
activityTaskManager,
overviewProxyService,
displayManager,
+ displayStateInteractor,
executor,
handler,
alternateBouncerInteractor,
TestCoroutineScope(),
- dumpManager,
+ dumpManager
)
+ rearDisplayStateRepository.setIsInRearDisplayMode(inRearDisplayMode)
overlayController =
ArgumentCaptor.forClass(ISidefpsController::class.java)
@@ -584,10 +613,62 @@
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 @@
* in other rotations have been omitted.
*/
@Test
- fun verifiesIndicatorPlacementForXAlignedSensor_0() {
+ fun verifiesIndicatorPlacementForXAlignedSensor_0() =
testWithDisplay(
deviceConfig = DeviceConfig.X_ALIGNED,
isReverseDefaultRotation = false,
@@ -641,7 +722,6 @@
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 @@
* 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 @@
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/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 2747e83..8a62ea0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -25,7 +25,6 @@
import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
-import androidx.test.ext.junit.runners.AndroidJUnit4
import android.testing.TestableLooper.RunWithLooper
import android.view.LayoutInflater
import android.view.MotionEvent
@@ -35,6 +34,7 @@
import android.view.View
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.settingslib.udfps.UdfpsOverlayParams
@@ -69,8 +69,8 @@
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
private const val REQUEST_ID = 2L
@@ -340,4 +340,22 @@
assertThat(lp.height).isEqualTo(overlayParams.sensorBounds.height())
}
}
+
+ @Test
+ fun fullScreenOverlayWithNewTouchDetectionEnabled() = withRotation(ROTATION_0) {
+ withReason(REASON_AUTH_KEYGUARD) {
+ whenever(featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true)
+
+ controllerOverlay.show(udfpsController, overlayParams)
+ verify(windowManager).addView(
+ eq(controllerOverlay.overlayView),
+ layoutParamsCaptor.capture()
+ )
+
+ // Layout params should use natural display width and height
+ val lp = layoutParamsCaptor.value
+ assertThat(lp.width).isEqualTo(overlayParams.naturalDisplayWidth)
+ assertThat(lp.height).isEqualTo(overlayParams.naturalDisplayHeight)
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index da71188..5536d83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -1388,7 +1388,7 @@
}
@Test
- public void onTouch_withNewTouchDetection_doNotPilferWhenPullingUpBouncer()
+ public void onTouch_withNewTouchDetection_doNotProcessTouchWhenPullingUpBouncer()
throws RemoteException {
final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
0L);
@@ -1427,8 +1427,10 @@
mBiometricExecutor.runAllReady();
moveEvent.recycle();
- // THEN the touch is NOT pilfered
- verify(mInputManager, never()).pilferPointers(any());
+ // THEN the touch is NOT processed
+ verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(),
+ anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(),
+ anyBoolean());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index 8bf32cf..8f6017c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -21,22 +21,23 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
+import com.android.systemui.RoboPilotTest
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
+import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.RoboPilotTest
import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.time.SystemClock
@@ -94,7 +95,8 @@
mock(DismissCallbackRegistry::class.java),
context,
mKeyguardUpdateMonitor,
- mock(KeyguardBypassController::class.java),
+ mock(TrustRepository::class.java),
+ FakeFeatureFlags(),
)
mAlternateBouncerInteractor =
AlternateBouncerInteractor(
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 0000000..dfe8d36
--- /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 0000000..2217c5c
--- /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 0000000..0c210e5
--- /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/clipboardoverlay/ClipboardImageLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt
new file mode 100644
index 0000000..21516d49
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.clipboardoverlay
+
+import android.content.ContentResolver
+import android.content.Context
+import android.net.Uri
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.whenever
+import java.io.IOException
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class ClipboardImageLoaderTest : SysuiTestCase() {
+ @Mock private lateinit var mockContext: Context
+
+ @Mock private lateinit var mockContentResolver: ContentResolver
+
+ private lateinit var clipboardImageLoader: ClipboardImageLoader
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ @Throws(IOException::class)
+ fun test_imageLoadSuccess() = runTest {
+ val testDispatcher = StandardTestDispatcher(this.testScheduler)
+ clipboardImageLoader =
+ ClipboardImageLoader(mockContext, testDispatcher, CoroutineScope(testDispatcher))
+ val testUri = Uri.parse("testUri")
+ whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
+ whenever(mockContext.resources).thenReturn(context.resources)
+
+ clipboardImageLoader.load(testUri)
+
+ verify(mockContentResolver).loadThumbnail(eq(testUri), any(), any())
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ @Throws(IOException::class)
+ fun test_imageLoadFailure() = runTest {
+ val testDispatcher = StandardTestDispatcher(this.testScheduler)
+ clipboardImageLoader =
+ ClipboardImageLoader(mockContext, testDispatcher, CoroutineScope(testDispatcher))
+ val testUri = Uri.parse("testUri")
+ whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
+ whenever(mockContext.resources).thenReturn(context.resources)
+
+ val res = clipboardImageLoader.load(testUri)
+
+ verify(mockContentResolver).loadThumbnail(eq(testUri), any(), any())
+ assertNull(res)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index fe5fa1f..39fb7b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -25,6 +25,7 @@
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_EXPANDED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_MINIMIZED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
+import static com.android.systemui.flags.Flags.CLIPBOARD_IMAGE_TIMEOUT;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -90,6 +91,8 @@
@Mock
private ClipboardOverlayUtils mClipboardUtils;
@Mock
+ private ClipboardImageLoader mClipboardImageLoader;
+ @Mock
private UiEventLogger mUiEventLogger;
private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@@ -120,6 +123,7 @@
mSampleClipData = new ClipData("Test", new String[]{"text/plain"},
new ClipData.Item("Test Item"));
+ mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, true); // turned off for legacy tests
mOverlayController = new ClipboardOverlayController(
mContext,
@@ -131,6 +135,7 @@
mFeatureFlags,
mClipboardUtils,
mExecutor,
+ mClipboardImageLoader,
mUiEventLogger);
verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
mCallbacks = mOverlayCallbacksCaptor.getValue();
@@ -142,6 +147,69 @@
}
@Test
+ public void test_setClipData_invalidImageData_legacy() {
+ ClipData clipData = new ClipData("", new String[]{"image/png"},
+ new ClipData.Item(Uri.parse("")));
+ mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
+
+ mOverlayController.setClipData(clipData, "");
+
+ verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+ verify(mClipboardOverlayView, times(1)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_nonImageUri_legacy() {
+ ClipData clipData = new ClipData("", new String[]{"resource/png"},
+ new ClipData.Item(Uri.parse("")));
+ mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
+
+ mOverlayController.setClipData(clipData, "");
+
+ verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+ verify(mClipboardOverlayView, times(1)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_textData_legacy() {
+ mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
+ mOverlayController.setClipData(mSampleClipData, "abc");
+
+ verify(mClipboardOverlayView, times(1)).showTextPreview("Test Item", false);
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHOWN_EXPANDED, 0, "abc");
+ verify(mClipboardOverlayView, times(1)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_sensitiveTextData_legacy() {
+ mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
+ ClipDescription description = mSampleClipData.getDescription();
+ PersistableBundle b = new PersistableBundle();
+ b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true);
+ description.setExtras(b);
+ ClipData data = new ClipData(description, mSampleClipData.getItemAt(0));
+ mOverlayController.setClipData(data, "");
+
+ verify(mClipboardOverlayView, times(1)).showTextPreview("••••••", true);
+ verify(mClipboardOverlayView, times(1)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_repeatedCalls_legacy() {
+ when(mAnimator.isRunning()).thenReturn(true);
+ mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
+
+ mOverlayController.setClipData(mSampleClipData, "");
+ mOverlayController.setClipData(mSampleClipData, "");
+
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
public void test_setClipData_invalidImageData() {
ClipData clipData = new ClipData("", new String[]{"image/png"},
new ClipData.Item(Uri.parse("")));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt
new file mode 100644
index 0000000..a308c8e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt
@@ -0,0 +1,196 @@
+/*
+ * 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.common.ui.data.repository
+
+import android.content.res.Configuration
+import android.view.Display
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.wrapper.DisplayUtilsWrapper
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ConfigurationRepositoryImplTest : SysuiTestCase() {
+ private var displaySizeRatio = 0f
+ @Mock private lateinit var configurationController: ConfigurationController
+ @Mock private lateinit var displayUtils: DisplayUtilsWrapper
+
+ private lateinit var testScope: TestScope
+ private lateinit var underTest: ConfigurationRepositoryImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ setPhysicalPixelDisplaySizeRatio(displaySizeRatio)
+
+ testScope = TestScope()
+ underTest =
+ ConfigurationRepositoryImpl(
+ configurationController,
+ context,
+ testScope.backgroundScope,
+ displayUtils,
+ )
+ }
+
+ @Test
+ fun onAnyConfigurationChange_updatesOnUiModeChanged() =
+ testScope.runTest {
+ val lastAnyConfigurationChange by collectLastValue(underTest.onAnyConfigurationChange)
+ assertThat(lastAnyConfigurationChange).isNull()
+
+ val configurationCallback = withArgCaptor {
+ verify(configurationController).addCallback(capture())
+ }
+
+ configurationCallback.onUiModeChanged()
+ runCurrent()
+ assertThat(lastAnyConfigurationChange).isNotNull()
+ }
+
+ @Test
+ fun onAnyConfigurationChange_updatesOnThemeChanged() =
+ testScope.runTest {
+ val lastAnyConfigurationChange by collectLastValue(underTest.onAnyConfigurationChange)
+ assertThat(lastAnyConfigurationChange).isNull()
+
+ val configurationCallback = withArgCaptor {
+ verify(configurationController).addCallback(capture())
+ }
+
+ configurationCallback.onThemeChanged()
+ runCurrent()
+ assertThat(lastAnyConfigurationChange).isNotNull()
+ }
+
+ @Test
+ fun onAnyConfigurationChange_updatesOnConfigChanged() =
+ testScope.runTest {
+ val lastAnyConfigurationChange by collectLastValue(underTest.onAnyConfigurationChange)
+ assertThat(lastAnyConfigurationChange).isNull()
+
+ val configurationCallback = withArgCaptor {
+ verify(configurationController).addCallback(capture())
+ }
+
+ configurationCallback.onConfigChanged(mock(Configuration::class.java))
+ runCurrent()
+ assertThat(lastAnyConfigurationChange).isNotNull()
+ }
+
+ @Test
+ fun onResolutionScale_updatesOnConfigurationChange() =
+ testScope.runTest {
+ val scaleForResolution by collectLastValue(underTest.scaleForResolution)
+ assertThat(scaleForResolution).isEqualTo(displaySizeRatio)
+
+ val configurationCallback = withArgCaptor {
+ verify(configurationController).addCallback(capture())
+ }
+
+ setPhysicalPixelDisplaySizeRatio(2f)
+ configurationCallback.onConfigChanged(mock(Configuration::class.java))
+ assertThat(scaleForResolution).isEqualTo(displaySizeRatio)
+
+ setPhysicalPixelDisplaySizeRatio(.21f)
+ configurationCallback.onConfigChanged(mock(Configuration::class.java))
+ assertThat(scaleForResolution).isEqualTo(displaySizeRatio)
+ }
+
+ @Test
+ fun onResolutionScale_nullMaxResolution() =
+ testScope.runTest {
+ val scaleForResolution by collectLastValue(underTest.scaleForResolution)
+ runCurrent()
+
+ givenNullMaxResolutionDisplayMode()
+ val configurationCallback = withArgCaptor {
+ verify(configurationController).addCallback(capture())
+ }
+ configurationCallback.onConfigChanged(mock(Configuration::class.java))
+ assertThat(scaleForResolution).isEqualTo(1f)
+ }
+
+ @Test
+ fun getResolutionScale_nullMaxResolutionDisplayMode() {
+ givenNullMaxResolutionDisplayMode()
+ assertThat(underTest.getResolutionScale()).isEqualTo(1f)
+ }
+
+ @Test
+ fun getResolutionScale_infiniteDisplayRatios() {
+ setPhysicalPixelDisplaySizeRatio(Float.POSITIVE_INFINITY)
+ assertThat(underTest.getResolutionScale()).isEqualTo(1f)
+ }
+
+ @Test
+ fun getResolutionScale_differentDisplayRatios() {
+ setPhysicalPixelDisplaySizeRatio(.5f)
+ assertThat(underTest.getResolutionScale()).isEqualTo(displaySizeRatio)
+
+ setPhysicalPixelDisplaySizeRatio(.283f)
+ assertThat(underTest.getResolutionScale()).isEqualTo(displaySizeRatio)
+
+ setPhysicalPixelDisplaySizeRatio(3.58f)
+ assertThat(underTest.getResolutionScale()).isEqualTo(displaySizeRatio)
+
+ setPhysicalPixelDisplaySizeRatio(0f)
+ assertThat(underTest.getResolutionScale()).isEqualTo(displaySizeRatio)
+
+ setPhysicalPixelDisplaySizeRatio(1f)
+ assertThat(underTest.getResolutionScale()).isEqualTo(displaySizeRatio)
+ }
+
+ private fun givenNullMaxResolutionDisplayMode() {
+ whenever(displayUtils.getMaximumResolutionDisplayMode(any())).thenReturn(null)
+ }
+
+ private fun setPhysicalPixelDisplaySizeRatio(ratio: Float) {
+ displaySizeRatio = ratio
+ whenever(displayUtils.getMaximumResolutionDisplayMode(any()))
+ .thenReturn(Display.Mode(0, 0, 0, 90f))
+ whenever(
+ displayUtils.getPhysicalPixelDisplaySizeRatio(
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt()
+ )
+ )
+ .thenReturn(ratio)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
new file mode 100644
index 0000000..b2a1668
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.common.ui.data.repository
+
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.receiveAsFlow
+
+class FakeConfigurationRepository : ConfigurationRepository {
+ private val onAnyConfigurationChangeChannel = Channel<Unit>()
+ override val onAnyConfigurationChange: Flow<Unit> =
+ onAnyConfigurationChangeChannel.receiveAsFlow()
+
+ private val _scaleForResolution = MutableStateFlow(1f)
+ override val scaleForResolution: Flow<Float> = _scaleForResolution.asStateFlow()
+
+ suspend fun onAnyConfigurationChange() {
+ onAnyConfigurationChangeChannel.send(Unit)
+ }
+
+ fun setScaleForResolution(scale: Float) {
+ _scaleForResolution.value = scale
+ }
+
+ override fun getResolutionScale(): Float {
+ return _scaleForResolution.value
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
index ef1061f..07cb5d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
@@ -42,6 +42,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import kotlinx.coroutines.CoroutineScope;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class AssistantAttentionConditionTest extends SysuiTestCase {
@@ -51,6 +53,8 @@
AssistUtils mAssistUtils;
@Mock
DreamOverlayStateController mDreamOverlayStateController;
+ @Mock
+ CoroutineScope mScope;
private AssistantAttentionCondition mAssistantAttentionCondition;
@@ -59,7 +63,7 @@
MockitoAnnotations.initMocks(this);
mAssistantAttentionCondition =
- new AssistantAttentionCondition(mDreamOverlayStateController, mAssistUtils);
+ new AssistantAttentionCondition(mScope, mDreamOverlayStateController, mAssistUtils);
// Adding a callback also starts the condition.
mAssistantAttentionCondition.addCallback(mCallback);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
index e1c54976..68c7965 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
@@ -25,7 +25,6 @@
import static org.mockito.Mockito.when;
import android.app.DreamManager;
-import android.content.Context;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
@@ -42,13 +41,12 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import kotlinx.coroutines.CoroutineScope;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class DreamConditionTest extends SysuiTestCase {
@Mock
- Context mContext;
-
- @Mock
Condition.Callback mCallback;
@Mock
@@ -57,6 +55,9 @@
@Mock
KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock
+ CoroutineScope mScope;
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
@@ -68,7 +69,8 @@
@Test
public void testInitialDreamingState() {
when(mDreamManager.isDreaming()).thenReturn(true);
- final DreamCondition condition = new DreamCondition(mDreamManager, mKeyguardUpdateMonitor);
+ final DreamCondition condition = new DreamCondition(mScope, mDreamManager,
+ mKeyguardUpdateMonitor);
condition.addCallback(mCallback);
verify(mCallback).onConditionChanged(eq(condition));
@@ -81,7 +83,8 @@
@Test
public void testInitialNonDreamingState() {
when(mDreamManager.isDreaming()).thenReturn(false);
- final DreamCondition condition = new DreamCondition(mDreamManager, mKeyguardUpdateMonitor);
+ final DreamCondition condition = new DreamCondition(mScope, mDreamManager,
+ mKeyguardUpdateMonitor);
condition.addCallback(mCallback);
verify(mCallback, never()).onConditionChanged(eq(condition));
@@ -96,7 +99,8 @@
final ArgumentCaptor<KeyguardUpdateMonitorCallback> callbackCaptor =
ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
when(mDreamManager.isDreaming()).thenReturn(true);
- final DreamCondition condition = new DreamCondition(mDreamManager, mKeyguardUpdateMonitor);
+ final DreamCondition condition = new DreamCondition(mScope, mDreamManager,
+ mKeyguardUpdateMonitor);
condition.addCallback(mCallback);
verify(mKeyguardUpdateMonitor).registerCallback(callbackCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
index 19135d0..e8cbdf3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
@@ -21,7 +21,7 @@
import com.android.systemui.Dumpable
import com.android.systemui.ProtoDumpable
import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.log.LogBuffer
import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
index 0c5a74c..02555cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
@@ -19,7 +19,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.Dumpable
import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.log.LogBuffer
import com.android.systemui.util.mockito.any
import java.io.PrintWriter
import org.junit.Before
@@ -60,16 +60,14 @@
// WHEN a dumpable is dumped explicitly
val args = arrayOf<String>()
- dumpManager.dumpTarget("dumpable2", pw, arrayOf(), tailLength = 0)
+ dumpManager.dumpTarget("dumpable2", pw, args, tailLength = 0)
// THEN only the requested one has their dump() method called
- verify(dumpable1, never())
- .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+ verify(dumpable1, never()).dump(any(), any())
verify(dumpable2).dump(pw, args)
- verify(dumpable3, never())
- .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
- verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
- verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
+ verify(dumpable3, never()).dump(any(), any())
+ verify(buffer1, never()).dump(any(), anyInt())
+ verify(buffer2, never()).dump(any(), anyInt())
}
@Test
@@ -82,17 +80,15 @@
dumpManager.registerBuffer("buffer2", buffer2)
// WHEN a buffer is dumped explicitly
- dumpManager.dumpTarget("buffer1", pw, arrayOf(), tailLength = 14)
+ val args = arrayOf<String>()
+ dumpManager.dumpTarget("buffer1", pw, args, tailLength = 14)
// THEN only the requested one has their dump() method called
- verify(dumpable1, never())
- .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
- verify(dumpable2, never())
- .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
- verify(dumpable2, never())
- .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+ verify(dumpable1, never()).dump(any(), any())
+ verify(dumpable2, never()).dump(any(), any())
+ verify(dumpable3, never()).dump(any(), any())
verify(buffer1).dump(pw, tailLength = 14)
- verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
+ verify(buffer2, never()).dump(any(), anyInt())
}
@Test
@@ -109,6 +105,122 @@
}
@Test
+ fun testDumpTarget_selectsShortestNamedDumpable() {
+ // GIVEN a variety of registered dumpables and buffers
+ dumpManager.registerCriticalDumpable("first-dumpable", dumpable1)
+ dumpManager.registerCriticalDumpable("scnd-dumpable", dumpable2)
+ dumpManager.registerCriticalDumpable("third-dumpable", dumpable3)
+
+ // WHEN a dumpable is dumped by a suffix that matches multiple options
+ val args = arrayOf<String>()
+ dumpManager.dumpTarget("dumpable", pw, args, tailLength = 0)
+
+ // THEN the matching dumpable with the shorter name is dumped
+ verify(dumpable1, never()).dump(any(), any())
+ verify(dumpable2).dump(pw, args)
+ verify(dumpable3, never()).dump(any(), any())
+ }
+
+ @Test
+ fun testDumpTarget_selectsShortestNamedBuffer() {
+ // GIVEN a variety of registered dumpables and buffers
+ dumpManager.registerBuffer("first-buffer", buffer1)
+ dumpManager.registerBuffer("scnd-buffer", buffer2)
+
+ // WHEN a dumpable is dumped by a suffix that matches multiple options
+ val args = arrayOf<String>()
+ dumpManager.dumpTarget("buffer", pw, args, tailLength = 14)
+
+ // THEN the matching buffer with the shorter name is dumped
+ verify(buffer1, never()).dump(any(), anyInt())
+ verify(buffer2).dump(pw, tailLength = 14)
+ }
+
+ @Test
+ fun testDumpTarget_selectsShortestNamedMatch_dumpable() {
+ // GIVEN a variety of registered dumpables and buffers
+ dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+ dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+ dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
+ dumpManager.registerBuffer("big-buffer1", buffer1)
+ dumpManager.registerBuffer("big-buffer2", buffer2)
+
+ // WHEN a dumpable is dumped by a suffix that matches multiple options
+ val args = arrayOf<String>()
+ dumpManager.dumpTarget("2", pw, args, tailLength = 14)
+
+ // THEN the matching dumpable with the shorter name is dumped
+ verify(dumpable1, never()).dump(any(), any())
+ verify(dumpable2).dump(pw, args)
+ verify(dumpable3, never()).dump(any(), any())
+ verify(buffer1, never()).dump(any(), anyInt())
+ verify(buffer2, never()).dump(any(), anyInt())
+ }
+
+ @Test
+ fun testDumpTarget_selectsShortestNamedMatch_buffer() {
+ // GIVEN a variety of registered dumpables and buffers
+ dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+ dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+ dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
+ dumpManager.registerBuffer("buffer1", buffer1)
+ dumpManager.registerBuffer("buffer2", buffer2)
+
+ // WHEN a dumpable is dumped by a suffix that matches multiple options
+ val args = arrayOf<String>()
+ dumpManager.dumpTarget("2", pw, args, tailLength = 14)
+
+ // THEN the matching buffer with the shorter name is dumped
+ verify(dumpable1, never()).dump(any(), any())
+ verify(dumpable2, never()).dump(any(), any())
+ verify(dumpable3, never()).dump(any(), any())
+ verify(buffer1, never()).dump(any(), anyInt())
+ verify(buffer2).dump(pw, tailLength = 14)
+ }
+
+ @Test
+ fun testDumpTarget_selectsTheAlphabeticallyFirstShortestMatch_dumpable() {
+ // GIVEN a variety of registered dumpables and buffers
+ dumpManager.registerCriticalDumpable("d1x", dumpable1)
+ dumpManager.registerCriticalDumpable("d2x", dumpable2)
+ dumpManager.registerCriticalDumpable("a3x", dumpable3)
+ dumpManager.registerBuffer("ab1x", buffer1)
+ dumpManager.registerBuffer("b2x", buffer2)
+
+ // WHEN a dumpable is dumped by a suffix that matches multiple options
+ val args = arrayOf<String>()
+ dumpManager.dumpTarget("x", pw, args, tailLength = 14)
+
+ // THEN the alphabetically first dumpable/buffer (of the 3 letter names) is dumped
+ verify(dumpable1, never()).dump(any(), any())
+ verify(dumpable2, never()).dump(any(), any())
+ verify(dumpable3).dump(pw, args)
+ verify(buffer1, never()).dump(any(), anyInt())
+ verify(buffer2, never()).dump(any(), anyInt())
+ }
+
+ @Test
+ fun testDumpTarget_selectsTheAlphabeticallyFirstShortestMatch_buffer() {
+ // GIVEN a variety of registered dumpables and buffers
+ dumpManager.registerCriticalDumpable("d1x", dumpable1)
+ dumpManager.registerCriticalDumpable("d2x", dumpable2)
+ dumpManager.registerCriticalDumpable("az1x", dumpable3)
+ dumpManager.registerBuffer("b1x", buffer1)
+ dumpManager.registerBuffer("b2x", buffer2)
+
+ // WHEN a dumpable is dumped by a suffix that matches multiple options
+ val args = arrayOf<String>()
+ dumpManager.dumpTarget("x", pw, args, tailLength = 14)
+
+ // THEN the alphabetically first dumpable/buffer (of the 3 letter names) is dumped
+ verify(dumpable1, never()).dump(any(), any())
+ verify(dumpable2, never()).dump(any(), any())
+ verify(dumpable3, never()).dump(any(), any())
+ verify(buffer1).dump(pw, tailLength = 14)
+ verify(buffer2, never()).dump(any(), anyInt())
+ }
+
+ @Test
fun testDumpDumpables() {
// GIVEN a variety of registered dumpables and buffers
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
@@ -125,8 +237,8 @@
verify(dumpable1).dump(pw, args)
verify(dumpable2).dump(pw, args)
verify(dumpable3).dump(pw, args)
- verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
- verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
+ verify(buffer1, never()).dump(any(), anyInt())
+ verify(buffer2, never()).dump(any(), anyInt())
}
@Test
@@ -142,12 +254,9 @@
dumpManager.dumpBuffers(pw, tailLength = 1)
// THEN all buffers are dumped (and no dumpables)
- verify(dumpable1, never())
- .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
- verify(dumpable2, never())
- .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
- verify(dumpable3, never())
- .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+ verify(dumpable1, never()).dump(any(), any())
+ verify(dumpable2, never()).dump(any(), any())
+ verify(dumpable3, never()).dump(any(), any())
verify(buffer1).dump(pw, tailLength = 1)
verify(buffer2).dump(pw, tailLength = 1)
}
@@ -168,10 +277,9 @@
// THEN only critical modules are dumped (and no buffers)
verify(dumpable1).dump(pw, args)
verify(dumpable2).dump(pw, args)
- verify(dumpable3, never())
- .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
- verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
- verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
+ verify(dumpable3, never()).dump(any(), any())
+ verify(buffer1, never()).dump(any(), anyInt())
+ verify(buffer2, never()).dump(any(), anyInt())
}
@Test
@@ -188,10 +296,8 @@
dumpManager.dumpNormal(pw, args, tailLength = 2)
// THEN the normal module and all buffers are dumped
- verify(dumpable1, never())
- .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
- verify(dumpable2, never())
- .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+ verify(dumpable1, never()).dump(any(), any())
+ verify(dumpable2, never()).dump(any(), any())
verify(dumpable3).dump(pw, args)
verify(buffer1).dump(pw, tailLength = 2)
verify(buffer2).dump(pw, tailLength = 2)
@@ -213,9 +319,7 @@
// THEN the unregistered dumpables (both normal and critical) are not dumped
verify(dumpable1).dump(pw, args)
- verify(dumpable2, never())
- .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
- verify(dumpable3, never())
- .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+ verify(dumpable2, never()).dump(any(), any())
+ verify(dumpable3, never()).dump(any(), any())
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
index 64547f4..bd029a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
@@ -16,9 +16,9 @@
package com.android.systemui.dump
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogcatEchoTracker
/**
* Creates a LogBuffer that will echo everything to logcat, which is useful for debugging tests.
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 8795ac0..c9ee1e8 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.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 @@
@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 @@
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 b9cfc65..e981d62 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.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 @@
@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 @@
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 @@
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 @@
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 @@
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/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 1d0b58a..d73c2c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -54,6 +54,7 @@
import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.shared.model.WakeSleepReason
import com.android.systemui.keyguard.shared.model.WakefulnessModel
@@ -234,6 +235,7 @@
faceDetectBuffer,
faceAuthBuffer,
keyguardTransitionInteractor,
+ featureFlags,
dumpManager,
)
}
@@ -612,6 +614,7 @@
authStatus()
detectStatus()
authRunning()
+ bypassEnabled()
lockedOut()
canFaceAuthRun()
authenticated()
@@ -847,7 +850,11 @@
fun schedulesFaceManagerWatchdogWhenKeyguardIsGoneFromDozing() =
testScope.runTest {
keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(from = KeyguardState.DOZING, to = KeyguardState.GONE)
+ TransitionStep(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.FINISHED
+ )
)
runCurrent()
@@ -858,7 +865,11 @@
fun schedulesFaceManagerWatchdogWhenKeyguardIsGoneFromAod() =
testScope.runTest {
keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(from = KeyguardState.AOD, to = KeyguardState.GONE)
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.FINISHED
+ )
)
runCurrent()
@@ -869,7 +880,11 @@
fun schedulesFaceManagerWatchdogWhenKeyguardIsGoneFromLockscreen() =
testScope.runTest {
keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.FINISHED
+ )
)
runCurrent()
@@ -880,7 +895,11 @@
fun schedulesFaceManagerWatchdogWhenKeyguardIsGoneFromBouncer() =
testScope.runTest {
keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE)
+ TransitionStep(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.FINISHED
+ )
)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index d0bfaa9..5afc405 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -23,8 +23,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
index bf3c73a..c2195c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
@@ -24,8 +24,8 @@
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogcatEchoTracker
import com.android.systemui.user.data.repository.FakeUserRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
new file mode 100644
index 0000000..069a486
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
@@ -0,0 +1,137 @@
+/*
+ * 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.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BurnInInteractorTest : SysuiTestCase() {
+ private val burnInOffset = 7
+ private var burnInProgress = 0f
+
+ @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
+
+ private lateinit var configurationRepository: FakeConfigurationRepository
+ private lateinit var systemClock: FakeSystemClock
+ private lateinit var testScope: TestScope
+ private lateinit var underTest: BurnInInteractor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ configurationRepository = FakeConfigurationRepository()
+ systemClock = FakeSystemClock()
+
+ whenever(burnInHelperWrapper.burnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset)
+ setBurnInProgress(.65f)
+
+ testScope = TestScope()
+ underTest =
+ BurnInInteractor(
+ context,
+ burnInHelperWrapper,
+ testScope.backgroundScope,
+ configurationRepository,
+ systemClock,
+ )
+ }
+
+ @Test
+ fun dozeTimeTick_updatesOnDozeTimeTick() =
+ testScope.runTest {
+ // Initial state set to 0
+ val lastDozeTimeTick by collectLastValue(underTest.dozeTimeTick)
+ assertEquals(0L, lastDozeTimeTick)
+
+ // WHEN dozeTimeTick updated
+ incrementUptimeMillis()
+ underTest.dozeTimeTick()
+
+ // THEN listeners were updated to the latest uptime millis
+ assertThat(systemClock.uptimeMillis()).isEqualTo(lastDozeTimeTick)
+ }
+
+ @Test
+ fun udfpsBurnInOffset_updatesOnResolutionScaleChange() =
+ testScope.runTest {
+ val udfpsBurnInOffsetX by collectLastValue(underTest.udfpsBurnInXOffset)
+ val udfpsBurnInOffsetY by collectLastValue(underTest.udfpsBurnInYOffset)
+ assertThat(udfpsBurnInOffsetX).isEqualTo(burnInOffset)
+ assertThat(udfpsBurnInOffsetY).isEqualTo(burnInOffset)
+
+ configurationRepository.setScaleForResolution(3f)
+ assertThat(udfpsBurnInOffsetX).isEqualTo(burnInOffset * 3)
+ assertThat(udfpsBurnInOffsetY).isEqualTo(burnInOffset * 3)
+
+ configurationRepository.setScaleForResolution(.5f)
+ assertThat(udfpsBurnInOffsetX).isEqualTo(burnInOffset / 2)
+ assertThat(udfpsBurnInOffsetY).isEqualTo(burnInOffset / 2)
+ }
+
+ @Test
+ fun udfpsBurnInProgress_updatesOnDozeTimeTick() =
+ testScope.runTest {
+ val udfpsBurnInProgress by collectLastValue(underTest.udfpsBurnInProgress)
+ assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress)
+
+ setBurnInProgress(.88f)
+ incrementUptimeMillis()
+ underTest.dozeTimeTick()
+ assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress)
+
+ setBurnInProgress(.92f)
+ incrementUptimeMillis()
+ underTest.dozeTimeTick()
+ assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress)
+
+ setBurnInProgress(.32f)
+ incrementUptimeMillis()
+ underTest.dozeTimeTick()
+ assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress)
+ }
+
+ private fun incrementUptimeMillis() {
+ systemClock.setUptimeMillis(systemClock.uptimeMillis() + 5)
+ }
+
+ private fun setBurnInProgress(progress: Float) {
+ burnInProgress = progress
+ whenever(burnInHelperWrapper.burnInProgressOffset()).thenReturn(burnInProgress)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index 5da1a84..e261982 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -35,12 +35,12 @@
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -100,7 +100,8 @@
mock(DismissCallbackRegistry::class.java),
context,
keyguardUpdateMonitor,
- mock(KeyguardBypassController::class.java),
+ mock(TrustRepository::class.java),
+ FakeFeatureFlags(),
),
AlternateBouncerInteractor(
mock(StatusBarStateController::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index 24a47b0..eed1e739 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -16,7 +16,7 @@
package com.android.systemui.keyguard.domain.interactor
-import android.os.Looper
+import android.hardware.biometrics.BiometricSourceType
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.testing.TestableResources
@@ -28,15 +28,17 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.BouncerViewDelegate
+import com.android.systemui.keyguard.data.repository.FakeTrustRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
@@ -67,16 +69,23 @@
@Mock private lateinit var mPrimaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
@Mock private lateinit var falsingCollector: FalsingCollector
@Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
- @Mock private lateinit var keyguardBypassController: KeyguardBypassController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- private val mainHandler = FakeHandler(Looper.getMainLooper())
+ private lateinit var mainHandler: FakeHandler
private lateinit var underTest: PrimaryBouncerInteractor
private lateinit var resources: TestableResources
+ private lateinit var trustRepository: FakeTrustRepository
+ private lateinit var featureFlags: FakeFeatureFlags
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(keyguardSecurityModel.getSecurityMode(anyInt()))
+ .thenReturn(KeyguardSecurityModel.SecurityMode.PIN)
+
DejankUtils.setImmediate(true)
+ mainHandler = FakeHandler(android.os.Looper.getMainLooper())
+ trustRepository = FakeTrustRepository()
+ featureFlags = FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, false) }
underTest =
PrimaryBouncerInteractor(
repository,
@@ -89,7 +98,8 @@
dismissCallbackRegistry,
context,
keyguardUpdateMonitor,
- keyguardBypassController,
+ trustRepository,
+ featureFlags,
)
whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
whenever(repository.primaryBouncerShow.value).thenReturn(false)
@@ -383,6 +393,55 @@
verify(repository).setSideFpsShowing(false)
}
+ @Test
+ fun delayBouncerWhenFaceAuthPossible() {
+ mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+
+ // GIVEN bouncer should be delayed due to face auth
+ featureFlags.apply { set(Flags.DELAY_BOUNCER, true) }
+ whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true)
+ whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
+ .thenReturn(true)
+
+ // WHEN bouncer show is requested
+ underTest.show(true)
+
+ // THEN primary show & primary showing soon aren't updated immediately
+ verify(repository, never()).setPrimaryShow(true)
+ verify(repository, never()).setPrimaryShowingSoon(false)
+
+ // WHEN all queued messages are dispatched
+ mainHandler.dispatchQueuedMessages()
+
+ // THEN primary show & primary showing soon are updated
+ verify(repository).setPrimaryShow(true)
+ verify(repository).setPrimaryShowingSoon(false)
+ }
+
+ @Test
+ fun delayBouncerWhenActiveUnlockPossible() {
+ mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+
+ // GIVEN bouncer should be delayed due to active unlock
+ featureFlags.apply { set(Flags.DELAY_BOUNCER, true) }
+ trustRepository.setCurrentUserActiveUnlockAvailable(true)
+ whenever(keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState()).thenReturn(true)
+
+ // WHEN bouncer show is requested
+ underTest.show(true)
+
+ // THEN primary show & primary showing soon were scheduled to update
+ verify(repository, never()).setPrimaryShow(true)
+ verify(repository, never()).setPrimaryShowingSoon(false)
+
+ // WHEN all queued messages are dispatched
+ mainHandler.dispatchQueuedMessages()
+
+ // THEN primary show & primary showing soon are updated
+ verify(repository).setPrimaryShow(true)
+ verify(repository).setPrimaryShowingSoon(false)
+ }
+
private fun updateSideFpsVisibilityParameters(
isVisible: Boolean,
sfpsEnabled: Boolean,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
index e35e971..5056b43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -25,9 +25,11 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.utils.os.FakeHandler
@@ -37,6 +39,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito
import org.mockito.MockitoAnnotations
@SmallTest
@@ -71,7 +74,8 @@
dismissCallbackRegistry,
context,
keyguardUpdateMonitor,
- keyguardBypassController,
+ Mockito.mock(TrustRepository::class.java),
+ FakeFeatureFlags(),
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
index a5b78b74..3efe382 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
@@ -17,8 +17,8 @@
package com.android.systemui.keyguard.ui
import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index 0e9c99e..e9f1ac1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -24,9 +24,11 @@
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
@@ -42,6 +44,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito
import org.mockito.MockitoAnnotations
@SmallTest
@@ -77,7 +80,8 @@
dismissCallbackRegistry,
context,
keyguardUpdateMonitor,
- keyguardBypassController,
+ Mockito.mock(TrustRepository::class.java),
+ FakeFeatureFlags(),
)
underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
}
diff --git a/packages/SystemUI/plugin/tests/log/LogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
similarity index 98%
rename from packages/SystemUI/plugin/tests/log/LogBufferTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
index a39b856..0cf6d3d 100644
--- a/packages/SystemUI/plugin/tests/log/LogBufferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
@@ -2,7 +2,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.log.LogBuffer
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
index a003e1d..43e430f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
@@ -19,7 +19,9 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableChange.Companion.IS_INITIAL_PREFIX
+import com.android.systemui.log.table.TableChange.Companion.MAX_STRING_LENGTH
import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertTrue
import org.junit.Test
@SmallTest
@@ -326,4 +328,152 @@
assertThat(underTest.getVal()).doesNotContain(IS_INITIAL_PREFIX)
}
+
+ @Test
+ fun constructor_columnAndValueTooLong_truncated() {
+ val underTest =
+ TableChange(
+ columnPrefix = "P".repeat(MAX_STRING_LENGTH + 10),
+ columnName = "N".repeat(MAX_STRING_LENGTH + 10),
+ type = TableChange.DataType.STRING,
+ str = "V".repeat(MAX_STRING_LENGTH + 10),
+ )
+
+ assertThat(underTest.getName()).contains("P".repeat(MAX_STRING_LENGTH))
+ assertThat(underTest.getName()).doesNotContain("P".repeat(MAX_STRING_LENGTH + 1))
+ assertThat(underTest.getName()).contains("N".repeat(MAX_STRING_LENGTH))
+ assertThat(underTest.getName()).doesNotContain("N".repeat(MAX_STRING_LENGTH + 1))
+ assertThat(underTest.getVal()).isEqualTo("V".repeat(MAX_STRING_LENGTH))
+ }
+
+ @Test
+ fun constructor_columnNameNotTooLong_noReallocation() {
+ val inputColumnName = "fakeName"
+ val inputValue = "fakeValue"
+ val underTest =
+ TableChange(
+ columnPrefix = "",
+ columnName = inputColumnName,
+ type = TableChange.DataType.STRING,
+ str = inputValue,
+ )
+
+ // Use referential equality to verify we didn't reallocate a new string when the string is
+ // *not* too long.
+ assertTrue(underTest.getColumnName() === inputColumnName)
+ }
+
+ @Test
+ fun reset_columnPrefixTooLong_truncated() {
+ val underTest = TableChange()
+
+ underTest.reset(
+ timestamp = 1L,
+ columnPrefix = "P".repeat(MAX_STRING_LENGTH + 10),
+ columnName = "name",
+ isInitial = false,
+ )
+
+ assertThat(underTest.getName()).contains("P".repeat(MAX_STRING_LENGTH))
+ assertThat(underTest.getName()).doesNotContain("P".repeat(MAX_STRING_LENGTH + 1))
+ }
+
+ @Test
+ fun reset_columnNameTooLong_truncated() {
+ val underTest = TableChange()
+
+ underTest.reset(
+ timestamp = 1L,
+ columnPrefix = "prefix",
+ columnName = "N".repeat(MAX_STRING_LENGTH + 10),
+ isInitial = false,
+ )
+
+ assertThat(underTest.getName()).contains("N".repeat(MAX_STRING_LENGTH))
+ assertThat(underTest.getName()).doesNotContain("N".repeat(MAX_STRING_LENGTH + 1))
+ }
+
+ @Test
+ fun reset_columnNameNotTooLong_noReallocation() {
+ val underTest = TableChange()
+ val shortColumnName = "shortColumnName"
+
+ underTest.reset(
+ timestamp = 1L,
+ columnPrefix = "prefix",
+ columnName = shortColumnName,
+ isInitial = false,
+ )
+
+ // Use referential equality to verify we didn't reallocate a new string when the string is
+ // *not* too long.
+ assertTrue(underTest.getColumnName() === shortColumnName)
+ }
+
+ @Test
+ fun setString_valueTooLong_truncated() {
+ val underTest = TableChange()
+
+ underTest.set("V".repeat(MAX_STRING_LENGTH + 1))
+
+ assertThat(underTest.getVal()).isEqualTo("V".repeat(MAX_STRING_LENGTH))
+ }
+
+ @Test
+ fun updateTo_newColumnPrefixTooLong_truncated() {
+ val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
+ underTest.set(42)
+
+ val new =
+ TableChange(
+ columnPrefix = "P".repeat(MAX_STRING_LENGTH + 10),
+ columnName = "name",
+ )
+ underTest.updateTo(new)
+
+ assertThat(underTest.getName()).contains("P".repeat(MAX_STRING_LENGTH))
+ assertThat(underTest.getName()).doesNotContain("P".repeat(MAX_STRING_LENGTH + 1))
+ }
+
+ @Test
+ fun updateTo_newColumnNameTooLong_truncated() {
+ val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
+ underTest.set(42)
+
+ val new =
+ TableChange(
+ columnPrefix = "prefix",
+ columnName = "N".repeat(MAX_STRING_LENGTH + 10),
+ )
+ underTest.updateTo(new)
+
+ assertThat(underTest.getName()).contains("N".repeat(MAX_STRING_LENGTH))
+ assertThat(underTest.getName()).doesNotContain("N".repeat(MAX_STRING_LENGTH + 1))
+ }
+
+ @Test
+ fun updateTo_columnNameNotTooLong_noReallocation() {
+ val underTest = TableChange()
+ val shortColumnName = "shortColumnName"
+ val new = TableChange(columnPrefix = "prefix", columnName = shortColumnName)
+
+ underTest.updateTo(new)
+
+ // Use referential equality to verify we didn't reallocate a new string when the string is
+ // *not* too long.
+ assertTrue(underTest.getColumnName() === shortColumnName)
+ }
+
+ @Test
+ fun updateTo_newValTooLong_truncated() {
+ val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
+ underTest.set("value")
+
+ val new = TableChange()
+ new.set("V".repeat(MAX_STRING_LENGTH + 10))
+
+ underTest.updateTo(new)
+
+ assertThat(underTest.getVal()).isEqualTo("V".repeat(MAX_STRING_LENGTH))
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
index a2b2322..12f4689 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
@@ -18,9 +18,10 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogcatEchoTracker
import com.android.systemui.log.table.TableChange.Companion.IS_INITIAL_PREFIX
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.table.TableChange.Companion.MAX_STRING_LENGTH
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
@@ -576,6 +577,112 @@
}
@Test
+ fun dumpChanges_tooLongColumnPrefix_viaLogChange_truncated() {
+ underTest.logChange(
+ prefix = "P".repeat(MAX_STRING_LENGTH + 10),
+ columnName = "name",
+ value = true,
+ )
+
+ val dumpedString = dumpChanges()
+
+ assertThat(dumpedString).contains("P".repeat(MAX_STRING_LENGTH))
+ assertThat(dumpedString).doesNotContain("P".repeat(MAX_STRING_LENGTH + 1))
+ }
+
+ @Test
+ fun dumpChanges_tooLongColumnPrefix_viaLogDiffs_truncated() {
+ val prevDiffable = object : TestDiffable() {}
+ val nextDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("status", "value")
+ }
+ }
+
+ // WHEN the column prefix is too large
+ underTest.logDiffs(
+ columnPrefix = "P".repeat(MAX_STRING_LENGTH + 10),
+ prevDiffable,
+ nextDiffable,
+ )
+
+ val dumpedString = dumpChanges()
+
+ // THEN it's truncated to the max length
+ assertThat(dumpedString).contains("P".repeat(MAX_STRING_LENGTH))
+ assertThat(dumpedString).doesNotContain("P".repeat(MAX_STRING_LENGTH + 1))
+ }
+
+ @Test
+ fun dumpChanges_tooLongColumnName_viaLogChange_truncated() {
+ underTest.logChange(
+ prefix = "prefix",
+ columnName = "N".repeat(MAX_STRING_LENGTH + 10),
+ value = 10,
+ )
+
+ val dumpedString = dumpChanges()
+
+ assertThat(dumpedString).contains("N".repeat(MAX_STRING_LENGTH))
+ assertThat(dumpedString).doesNotContain("N".repeat(MAX_STRING_LENGTH + 1))
+ }
+
+ @Test
+ fun dumpChanges_tooLongColumnName_viaLogDiffs_truncated() {
+ val prevDiffable = object : TestDiffable() {}
+ val nextDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ // WHEN the column name is too large
+ row.logChange(columnName = "N".repeat(MAX_STRING_LENGTH + 10), "value")
+ }
+ }
+
+ underTest.logDiffs(columnPrefix = "prefix", prevDiffable, nextDiffable)
+
+ val dumpedString = dumpChanges()
+
+ // THEN it's truncated to the max length
+ assertThat(dumpedString).contains("N".repeat(MAX_STRING_LENGTH))
+ assertThat(dumpedString).doesNotContain("N".repeat(MAX_STRING_LENGTH + 1))
+ }
+
+ @Test
+ fun dumpChanges_tooLongValue_viaLogChange_truncated() {
+ underTest.logChange(
+ prefix = "prefix",
+ columnName = "name",
+ value = "V".repeat(MAX_STRING_LENGTH + 10),
+ )
+
+ val dumpedString = dumpChanges()
+
+ assertThat(dumpedString).contains("V".repeat(MAX_STRING_LENGTH))
+ assertThat(dumpedString).doesNotContain("V".repeat(MAX_STRING_LENGTH + 1))
+ }
+
+ @Test
+ fun dumpChanges_tooLongValue_viaLogDiffs_truncated() {
+ val prevDiffable = object : TestDiffable() {}
+ val nextDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ // WHEN the value is too large
+ row.logChange("columnName", value = "V".repeat(MAX_STRING_LENGTH + 10))
+ }
+ }
+
+ underTest.logDiffs(columnPrefix = "prefix", prevDiffable, nextDiffable)
+
+ val dumpedString = dumpChanges()
+
+ // THEN it's truncated to the max length
+ assertThat(dumpedString).contains("V".repeat(MAX_STRING_LENGTH))
+ assertThat(dumpedString).doesNotContain("V".repeat(MAX_STRING_LENGTH + 1))
+ }
+
+ @Test
fun dumpChanges_rotatesIfBufferIsFull() {
lateinit var valToDump: String
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 0a1db60..d428db7b 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 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 @@
@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 @@
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 @@
}
@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 @@
@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/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt
index 8da1c64..b322bb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt
@@ -19,9 +19,9 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.LogcatEchoTracker
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt
index 95df484..64f3fd3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt
@@ -19,9 +19,9 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.LogcatEchoTracker
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt
index 0033757..2287da5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt
@@ -19,9 +19,9 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.LogcatEchoTracker
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 4977775..f1bbd84 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -132,7 +132,7 @@
}
@Test
- fun initRecentTasksWithAppSelectorTasks_enterprisePoliciesDisabled_bindsOnlyTasksWithHostProfile() {
+ fun initRecentTasksWithAppSelectorTasks_enterprisePoliciesDisabled_bindsAllTasks() {
givenEnterprisePoliciesFeatureFlag(enabled = false)
val tasks =
@@ -147,11 +147,15 @@
controller.init()
+ // TODO (b/263950746): Cross-profile filtering is removed for now. This should be brought
+ // back with the future fix
verify(view)
.bind(
listOf(
createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
)
)
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 0000000..36b913f
--- /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 5dbcd33..5f89705 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.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 @@
)
.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 @@
)
.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 @@
)
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL)
- createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
+ createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
verifyNoteTaskOpenInBubbleInUser(userTracker.userHandle)
}
@@ -541,7 +544,7 @@
)
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
- createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
+ createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
verifyNoteTaskOpenInBubbleInUser(userTracker.userHandle)
}
@@ -553,7 +556,7 @@
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 @@
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 @@
}
// 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 42ef2b5..4526580 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.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.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 @@
@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 @@
.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 @@
keyguardMonitor = mock(),
lazyRepository = { repository },
isEnabled = isEnabled,
+ backgroundExecutor = FakeExecutor(FakeSystemClock()),
+ roleManager = roleManager,
+ noteTaskInfoResolver = NoteTaskInfoResolver(roleManager, packageManager)
)
private fun createLockScreenStateVisible(): LockScreenState =
@@ -112,6 +147,27 @@
}
@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 @@
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 the notetaking shortcut"),
+ actionText = "Select app",
+ 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/process/condition/SystemProcessConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java
index fb71977..ff60fcd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java
@@ -37,6 +37,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import kotlinx.coroutines.CoroutineScope;
+
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
@@ -47,6 +49,9 @@
@Mock
Monitor.Callback mCallback;
+ @Mock
+ CoroutineScope mScope;
+
private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@Before
@@ -61,7 +66,7 @@
@Test
public void testConditionFailsWithNonSystemProcess() {
- final Condition condition = new SystemProcessCondition(mProcessWrapper);
+ final Condition condition = new SystemProcessCondition(mScope, mProcessWrapper);
when(mProcessWrapper.isSystemUser()).thenReturn(false);
final Monitor monitor = new Monitor(mExecutor);
@@ -82,7 +87,7 @@
@Test
public void testConditionSucceedsWithSystemProcess() {
- final Condition condition = new SystemProcessCondition(mProcessWrapper);
+ final Condition condition = new SystemProcessCondition(mScope, mProcessWrapper);
when(mProcessWrapper.isSystemUser()).thenReturn(true);
final Monitor monitor = new Monitor(mExecutor);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
index 26aa37d..d9db60d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
@@ -37,6 +37,8 @@
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.retail.data.repository.FakeRetailModeRepository;
+import com.android.systemui.retail.domain.interactor.RetailModeInteractorImpl;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.utils.leaks.LeakCheckedTest;
@@ -67,6 +69,8 @@
@Mock
private ActivityStarter mActivityStarter;
+ private FakeRetailModeRepository mRetailModeRepository;
+
private QSFooterViewController mController;
private View mEditButton;
@@ -74,6 +78,9 @@
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
+ mRetailModeRepository = new FakeRetailModeRepository();
+ mRetailModeRepository.setRetailMode(false);
+
mEditButton = new View(mContext);
injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
@@ -89,7 +96,8 @@
when(mView.findViewById(android.R.id.edit)).thenReturn(mEditButton);
mController = new QSFooterViewController(mView, mUserTracker, mFalsingManager,
- mActivityStarter, mQSPanelController);
+ mActivityStarter, mQSPanelController,
+ new RetailModeInteractorImpl(mRetailModeRepository));
mController.init();
}
@@ -132,4 +140,20 @@
captor.getValue().run();
verify(mQSPanelController).showEdit(mEditButton);
}
+
+ @Test
+ public void testEditButton_notRetailMode_visible() {
+ mRetailModeRepository.setRetailMode(false);
+
+ mController.setVisibility(View.VISIBLE);
+ assertThat(mEditButton.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void testEditButton_retailMode_notVisible() {
+ mRetailModeRepository.setRetailMode(true);
+
+ mController.setVisibility(View.VISIBLE);
+ assertThat(mEditButton.getVisibility()).isEqualTo(View.GONE);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
index 68c10f2..aacc695 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
@@ -20,7 +20,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.LogcatEchoTracker
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
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 88d7e9c..2dc78a3 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 @@
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/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
index 50a8d26..fda63ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.retail.data.repository.FakeRetailModeRepository
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -44,6 +45,7 @@
class TileSpecSettingsRepositoryTest : SysuiTestCase() {
private lateinit var secureSettings: FakeSettings
+ private lateinit var retailModeRepository: FakeRetailModeRepository
@Mock private lateinit var logger: QSPipelineLogger
@@ -57,9 +59,12 @@
MockitoAnnotations.initMocks(this)
secureSettings = FakeSettings()
+ retailModeRepository = FakeRetailModeRepository()
+ retailModeRepository.setRetailMode(false)
with(context.orCreateTestableResources) {
addOverride(R.string.quick_settings_tiles_default, DEFAULT_TILES)
+ addOverride(R.string.quick_settings_tiles_retail_mode, RETAIL_TILES)
}
underTest =
@@ -67,6 +72,7 @@
secureSettings,
context.resources,
logger,
+ retailModeRepository,
testDispatcher,
)
}
@@ -346,6 +352,26 @@
assertThat(tiles).isEqualTo("b".toTileSpecs())
}
+ @Test
+ fun retailMode_usesRetailTiles() =
+ testScope.runTest {
+ retailModeRepository.setRetailMode(true)
+
+ val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+ assertThat(tiles).isEqualTo(RETAIL_TILES.toTileSpecs())
+ }
+
+ @Test
+ fun retailMode_cannotModifyTiles() =
+ testScope.runTest {
+ retailModeRepository.setRetailMode(true)
+
+ underTest.setTiles(0, DEFAULT_TILES.toTileSpecs())
+
+ assertThat(loadTilesForUser(0)).isNull()
+ }
+
private fun getDefaultTileSpecs(): List<TileSpec> {
return QSHost.getDefaultSpecs(context.resources).map(TileSpec::create)
}
@@ -360,6 +386,7 @@
companion object {
private const val DEFAULT_TILES = "a,b,c"
+ private const val RETAIL_TILES = "d"
private const val SETTING = Settings.Secure.QS_TILES
private fun String.toTileSpecs(): List<TileSpec> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
index e106741..41545fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
@@ -30,6 +30,7 @@
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.testing.TestableResources;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
@@ -94,14 +95,18 @@
private RotationLockController mController;
private TestableLooper mTestableLooper;
private RotationLockTile mLockTile;
+ private TestableResources mTestableResources;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mTestableLooper = TestableLooper.get(this);
+ mTestableResources = mContext.getOrCreateTestableResources();
when(mHost.getContext()).thenReturn(mContext);
when(mHost.getUserContext()).thenReturn(mContext);
+ mTestableResources.addOverride(com.android.internal.R.bool.config_allowRotationResolver,
+ true);
mController = new RotationLockControllerImpl(mRotationPolicyWrapper,
mDeviceStateRotationLockSettingController, DEFAULT_SETTINGS);
@@ -208,6 +213,32 @@
}
@Test
+ public void testSecondaryString_rotationResolverDisabled_isEmpty() {
+ mTestableResources.addOverride(com.android.internal.R.bool.config_allowRotationResolver,
+ false);
+ mLockTile = new RotationLockTile(
+ mHost,
+ mUiEventLogger,
+ mTestableLooper.getLooper(),
+ new Handler(mTestableLooper.getLooper()),
+ new FalsingManagerFake(),
+ mMetricsLogger,
+ mStatusBarStateController,
+ mActivityStarter,
+ mQSLogger,
+ mController,
+ mPrivacyManager,
+ mBatteryController,
+ new FakeSettings()
+ );
+
+ mLockTile.refreshState();
+ mTestableLooper.processAllMessages();
+
+ assertEquals("", mLockTile.getState().secondaryLabel.toString());
+ }
+
+ @Test
public void testIcon_whenDisabled_isOffState() {
QSTile.BooleanState state = new QSTile.BooleanState();
disableAutoRotation();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
new file mode 100644
index 0000000..d7682b2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.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.retail.data.repository
+
+import android.provider.Settings
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.settings.FakeSettings
+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.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class RetailModeSettingsRepositoryTest : SysuiTestCase() {
+
+ private val globalSettings = FakeSettings()
+
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private val underTest =
+ RetailModeSettingsRepository(
+ globalSettings,
+ backgroundDispatcher = testDispatcher,
+ scope = testScope.backgroundScope,
+ )
+
+ @Test
+ fun retailMode_defaultFalse() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.retailMode)
+
+ assertThat(value).isFalse()
+ assertThat(underTest.inRetailMode).isFalse()
+ }
+
+ @Test
+ fun retailMode_false() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.retailMode)
+
+ globalSettings.putInt(SETTING, 0)
+
+ assertThat(value).isFalse()
+ assertThat(underTest.inRetailMode).isFalse()
+ }
+
+ @Test
+ fun retailMode_true() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.retailMode)
+
+ globalSettings.putInt(SETTING, 1)
+
+ assertThat(value).isTrue()
+ assertThat(underTest.inRetailMode).isTrue()
+ }
+
+ companion object {
+ private const val SETTING = Settings.Global.DEVICE_DEMO_MODE
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt
new file mode 100644
index 0000000..8f13169
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.retail.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.retail.data.repository.FakeRetailModeRepository
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class RetailModeInteractorImplTest : SysuiTestCase() {
+
+ private val retailModeRepository = FakeRetailModeRepository()
+
+ private val underTest = RetailModeInteractorImpl(retailModeRepository)
+
+ @Test
+ fun retailMode_false() {
+ retailModeRepository.setRetailMode(false)
+
+ assertThat(underTest.isInRetailMode).isFalse()
+ }
+
+ @Test
+ fun retailMode_true() {
+ retailModeRepository.setRetailMode(true)
+
+ assertThat(underTest.isInRetailMode).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index a1d78cb..7c30843b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -18,7 +18,6 @@
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -94,6 +93,7 @@
doReturn(mContext.getUserId()).when(mRecordingService).getUserId();
doReturn(mContext.getPackageName()).when(mRecordingService).getPackageName();
doReturn(mContext.getContentResolver()).when(mRecordingService).getContentResolver();
+ doReturn(mContext.getResources()).when(mRecordingService).getResources();
// Mock notifications
doNothing().when(mRecordingService).createRecordingNotification();
@@ -101,7 +101,7 @@
doReturn(mNotification).when(mRecordingService).createSaveNotification(any());
doNothing().when(mRecordingService).createErrorNotification();
doNothing().when(mRecordingService).showErrorToast(anyInt());
- doNothing().when(mRecordingService).stopForeground(anyBoolean());
+ doNothing().when(mRecordingService).stopForeground(anyInt());
doNothing().when(mRecordingService).startForeground(anyInt(), any());
doReturn(mScreenMediaRecorder).when(mRecordingService).getRecorder();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
index 31a33d4..cbd9dba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
@@ -30,6 +30,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -127,6 +128,9 @@
@Before
public void setUp() {
+ assumeFalse("Skip test: does not apply to watches",
+ mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH));
+
MockitoAnnotations.initMocks(this);
mMainHandler = mContext.getMainThreadHandler();
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 1edc63c..9a8ec88 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.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.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 @@
@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 @@
@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 @@
mNotificationRemoteInputManager,
mShadeExpansionStateManager,
mStatusBarKeyguardViewManager,
+ mLightBarController,
mNotificationStackScrollLayoutController,
mLockscreenShadeTransitionController,
mNotificationShadeDepthController,
@@ -675,7 +680,8 @@
mInteractionJankMonitor,
mShadeLog,
mKeyguardFaceAuthInteractor,
- mShadeRepository
+ mShadeRepository,
+ mCastController
);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
new file mode 100644
index 0000000..d4751c8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.shade
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableResources
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.fragments.FragmentService
+import com.android.systemui.navigationbar.NavigationModeController
+import com.android.systemui.recents.OverviewProxyService
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@SmallTest
+class NotificationsQSContainerControllerTest : SysuiTestCase() {
+
+ @Mock lateinit var view: NotificationsQuickSettingsContainer
+ @Mock lateinit var navigationModeController: NavigationModeController
+ @Mock lateinit var overviewProxyService: OverviewProxyService
+ @Mock lateinit var shadeHeaderController: ShadeHeaderController
+ @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+ @Mock lateinit var fragmentService: FragmentService
+
+ lateinit var underTest: NotificationsQSContainerController
+
+ private lateinit var fakeResources: TestableResources
+
+ private val delayableExecutor: DelayableExecutor = FakeExecutor(FakeSystemClock())
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ fakeResources = TestableResources(context.resources)
+
+ whenever(view.resources).thenReturn(fakeResources.resources)
+
+ underTest =
+ NotificationsQSContainerController(
+ view,
+ navigationModeController,
+ overviewProxyService,
+ shadeHeaderController,
+ shadeExpansionStateManager,
+ fragmentService,
+ delayableExecutor,
+ )
+ }
+
+ @Test
+ fun testSmallScreen_updateResources_splitShadeHeightIsSet() {
+ with(fakeResources) {
+ addOverride(R.bool.config_use_large_screen_shade_header, false)
+ addOverride(R.dimen.qs_header_height, 1)
+ addOverride(R.dimen.large_screen_shade_header_height, 2)
+ }
+
+ underTest.updateResources()
+
+ val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+ verify(view).applyConstraints(capture(captor))
+ assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(1)
+ }
+
+ @Test
+ fun testLargeScreen_updateResources_splitShadeHeightIsSet() {
+ with(fakeResources) {
+ addOverride(R.bool.config_use_large_screen_shade_header, true)
+ addOverride(R.dimen.qs_header_height, 1)
+ addOverride(R.dimen.large_screen_shade_header_height, 2)
+ }
+
+ underTest.updateResources()
+
+ val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+ verify(view).applyConstraints(capture(captor))
+ assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(2)
+ }
+}
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 8a9161e..1cf3873 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.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 @@
@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 @@
@Mock private ShadeLogger mShadeLogger;
@Mock private DumpManager mDumpManager;
@Mock private UiEventLogger mUiEventLogger;
+ @Mock private CastController mCastController;
private SysuiStatusBarStateController mStatusBarStateController;
@@ -221,6 +225,7 @@
mNotificationRemoteInputManager,
mShadeExpansionStateManager,
mStatusBarKeyguardViewManager,
+ mLightBarController,
mNotificationStackScrollLayoutController,
mLockscreenShadeTransitionController,
mNotificationShadeDepthController,
@@ -243,7 +248,8 @@
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/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index 9fe75ab..20da8a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -33,9 +33,9 @@
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt
new file mode 100644
index 0000000..1a0e932
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/CombinedConditionTest.kt
@@ -0,0 +1,450 @@
+/*
+ * 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.shared.condition
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.condition.Condition.START_EAGERLY
+import com.android.systemui.shared.condition.Condition.START_LAZILY
+import com.android.systemui.shared.condition.Condition.START_WHEN_NEEDED
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class CombinedConditionTest : SysuiTestCase() {
+
+ class FakeCondition
+ constructor(
+ scope: CoroutineScope,
+ initialValue: Boolean?,
+ overriding: Boolean = false,
+ @StartStrategy private val startStrategy: Int = START_WHEN_NEEDED,
+ ) : Condition(scope, initialValue, overriding) {
+ private var _started = false
+ val started: Boolean
+ get() = _started
+
+ override fun start() {
+ _started = true
+ }
+
+ override fun stop() {
+ _started = false
+ }
+
+ override fun getStartStrategy(): Int {
+ return startStrategy
+ }
+
+ fun setValue(value: Boolean?) {
+ value?.also { updateCondition(value) } ?: clearCondition()
+ }
+ }
+
+ @Test
+ fun testOrOperatorWithMixedConditions() = runSelfCancelingTest {
+ val startWhenNeededCondition =
+ FakeCondition(scope = this, initialValue = false, startStrategy = START_WHEN_NEEDED)
+ val eagerCondition =
+ FakeCondition(scope = this, initialValue = false, startStrategy = START_EAGERLY)
+ val lazyCondition =
+ FakeCondition(scope = this, initialValue = false, startStrategy = START_LAZILY)
+
+ val combinedCondition =
+ CombinedCondition(
+ scope = this,
+ conditions =
+ listOf(
+ eagerCondition,
+ lazyCondition,
+ startWhenNeededCondition,
+ ),
+ operand = Evaluator.OP_OR
+ )
+
+ val callback = Condition.Callback {}
+ combinedCondition.addCallback(callback)
+
+ assertThat(combinedCondition.isConditionMet).isFalse()
+ assertThat(eagerCondition.started).isTrue()
+ assertThat(lazyCondition.started).isTrue()
+ assertThat(startWhenNeededCondition.started).isTrue()
+
+ eagerCondition.setValue(true)
+ assertThat(combinedCondition.isConditionMet).isTrue()
+ assertThat(eagerCondition.started).isTrue()
+ assertThat(lazyCondition.started).isTrue()
+ assertThat(startWhenNeededCondition.started).isFalse()
+
+ startWhenNeededCondition.setValue(true)
+ assertThat(combinedCondition.isConditionMet).isTrue()
+ assertThat(eagerCondition.started).isTrue()
+ assertThat(lazyCondition.started).isTrue()
+ assertThat(startWhenNeededCondition.started).isFalse()
+
+ startWhenNeededCondition.setValue(false)
+ eagerCondition.setValue(false)
+ assertThat(combinedCondition.isConditionMet).isFalse()
+ assertThat(eagerCondition.started).isTrue()
+ assertThat(lazyCondition.started).isTrue()
+ assertThat(startWhenNeededCondition.started).isTrue()
+ }
+
+ @Test
+ fun testAndOperatorWithMixedConditions() = runSelfCancelingTest {
+ val startWhenNeededCondition =
+ FakeCondition(scope = this, initialValue = false, startStrategy = START_WHEN_NEEDED)
+ val eagerCondition =
+ FakeCondition(scope = this, initialValue = false, startStrategy = START_EAGERLY)
+ val lazyCondition =
+ FakeCondition(scope = this, initialValue = false, startStrategy = START_LAZILY)
+
+ val combinedCondition =
+ CombinedCondition(
+ scope = this,
+ conditions =
+ listOf(
+ startWhenNeededCondition,
+ lazyCondition,
+ eagerCondition,
+ ),
+ operand = Evaluator.OP_AND
+ )
+
+ val callback = Condition.Callback {}
+ combinedCondition.addCallback(callback)
+
+ assertThat(combinedCondition.isConditionMet).isFalse()
+ assertThat(eagerCondition.started).isTrue()
+ assertThat(lazyCondition.started).isFalse()
+ assertThat(startWhenNeededCondition.started).isFalse()
+
+ eagerCondition.setValue(true)
+ assertThat(combinedCondition.isConditionMet).isFalse()
+ assertThat(eagerCondition.started).isTrue()
+ assertThat(startWhenNeededCondition.started).isTrue()
+ assertThat(lazyCondition.started).isFalse()
+
+ startWhenNeededCondition.setValue(true)
+ assertThat(combinedCondition.isConditionMet).isFalse()
+ assertThat(eagerCondition.started).isTrue()
+ assertThat(startWhenNeededCondition.started).isFalse()
+ assertThat(lazyCondition.started).isTrue()
+
+ startWhenNeededCondition.setValue(false)
+ assertThat(combinedCondition.isConditionMet).isFalse()
+ assertThat(eagerCondition.started).isTrue()
+ assertThat(startWhenNeededCondition.started).isFalse()
+ assertThat(lazyCondition.started).isTrue()
+
+ startWhenNeededCondition.setValue(true)
+ lazyCondition.setValue(true)
+ assertThat(combinedCondition.isConditionMet).isTrue()
+ assertThat(eagerCondition.started).isTrue()
+ assertThat(startWhenNeededCondition.started).isTrue()
+ assertThat(lazyCondition.started).isTrue()
+ }
+
+ @Test
+ fun testAndOperatorWithStartWhenNeededConditions() = runSelfCancelingTest {
+ val conditions =
+ 0.rangeTo(2)
+ .map {
+ FakeCondition(
+ scope = this,
+ initialValue = false,
+ startStrategy = START_WHEN_NEEDED
+ )
+ }
+ .toList()
+
+ val combinedCondition =
+ CombinedCondition(scope = this, conditions = conditions, operand = Evaluator.OP_AND)
+
+ val callback = Condition.Callback {}
+ combinedCondition.addCallback(callback)
+ assertThat(combinedCondition.isConditionMet).isFalse()
+ // Only the first condition should be started
+ assertThat(areStarted(conditions)).containsExactly(true, false, false).inOrder()
+
+ conditions[0].setValue(true)
+ assertThat(combinedCondition.isConditionMet).isFalse()
+ assertThat(areStarted(conditions)).containsExactly(false, true, false).inOrder()
+
+ conditions[1].setValue(true)
+ assertThat(combinedCondition.isConditionMet).isFalse()
+ assertThat(areStarted(conditions)).containsExactly(false, false, true).inOrder()
+
+ conditions[2].setValue(true)
+ assertThat(combinedCondition.isConditionMet).isTrue()
+ assertThat(areStarted(conditions)).containsExactly(true, true, true).inOrder()
+
+ conditions[0].setValue(false)
+ assertThat(combinedCondition.isConditionMet).isFalse()
+ assertThat(areStarted(conditions)).containsExactly(true, false, false).inOrder()
+ }
+
+ @Test
+ fun testOrOperatorWithStartWhenNeededConditions() = runSelfCancelingTest {
+ val conditions =
+ 0.rangeTo(2)
+ .map {
+ FakeCondition(
+ scope = this,
+ initialValue = false,
+ startStrategy = START_WHEN_NEEDED
+ )
+ }
+ .toList()
+
+ val combinedCondition =
+ CombinedCondition(scope = this, conditions = conditions, operand = Evaluator.OP_OR)
+
+ val callback = Condition.Callback {}
+ combinedCondition.addCallback(callback)
+ // Default is to monitor all conditions when false
+ assertThat(combinedCondition.isConditionMet).isFalse()
+ assertThat(areStarted(conditions)).containsExactly(true, true, true).inOrder()
+
+ // Condition 2 is true, so we should only monitor condition 2
+ conditions[1].setValue(true)
+ assertThat(combinedCondition.isConditionMet).isTrue()
+ assertThat(areStarted(conditions)).containsExactly(false, true, false).inOrder()
+
+ // Condition 2 becomes false, so we go back to monitoring all conditions
+ conditions[1].setValue(false)
+ assertThat(combinedCondition.isConditionMet).isFalse()
+ assertThat(areStarted(conditions)).containsExactly(true, true, true).inOrder()
+
+ // Condition 3 becomes true, so we only monitor condition 3
+ conditions[2].setValue(true)
+ assertThat(combinedCondition.isConditionMet).isTrue()
+ assertThat(areStarted(conditions)).containsExactly(false, false, true).inOrder()
+
+ // Condition 2 becomes true, but we are still only monitoring condition 3
+ conditions[1].setValue(true)
+ assertThat(combinedCondition.isConditionMet).isTrue()
+ assertThat(areStarted(conditions)).containsExactly(false, false, true).inOrder()
+
+ // Condition 3 becomes false, so we should now only be monitoring condition 2
+ conditions[2].setValue(false)
+ assertThat(combinedCondition.isConditionMet).isTrue()
+ assertThat(areStarted(conditions)).containsExactly(false, true, false).inOrder()
+ }
+
+ @Test
+ fun testRemovingCallbackWillStopMonitoringAllConditions() = runSelfCancelingTest {
+ val conditions =
+ 0.rangeTo(2)
+ .map {
+ FakeCondition(
+ scope = this,
+ initialValue = false,
+ startStrategy = START_WHEN_NEEDED
+ )
+ }
+ .toList()
+
+ val combinedCondition =
+ CombinedCondition(scope = this, conditions = conditions, operand = Evaluator.OP_OR)
+
+ val callback = Condition.Callback {}
+ combinedCondition.addCallback(callback)
+ assertThat(areStarted(conditions)).containsExactly(true, true, true)
+
+ combinedCondition.removeCallback(callback)
+ assertThat(areStarted(conditions)).containsExactly(false, false, false)
+ }
+
+ @Test
+ fun testOverridingConditionSkipsOtherConditions() = runSelfCancelingTest {
+ val startWhenNeededCondition =
+ FakeCondition(scope = this, initialValue = false, startStrategy = START_WHEN_NEEDED)
+ val eagerCondition =
+ FakeCondition(scope = this, initialValue = false, startStrategy = START_EAGERLY)
+ val lazyCondition =
+ FakeCondition(scope = this, initialValue = false, startStrategy = START_LAZILY)
+ val overridingCondition1 =
+ FakeCondition(scope = this, initialValue = null, overriding = true)
+ val overridingCondition2 =
+ FakeCondition(scope = this, initialValue = null, overriding = true)
+
+ val combinedCondition =
+ CombinedCondition(
+ scope = this,
+ conditions =
+ listOf(
+ eagerCondition,
+ overridingCondition1,
+ lazyCondition,
+ startWhenNeededCondition,
+ overridingCondition2
+ ),
+ operand = Evaluator.OP_OR
+ )
+
+ val callback = Condition.Callback {}
+ combinedCondition.addCallback(callback)
+ assertThat(combinedCondition.isConditionMet).isFalse()
+ assertThat(eagerCondition.started).isTrue()
+ assertThat(lazyCondition.started).isTrue()
+ assertThat(startWhenNeededCondition.started).isTrue()
+ assertThat(overridingCondition1.started).isTrue()
+
+ // Overriding condition is true, so we should stop monitoring all other conditions
+ overridingCondition1.setValue(true)
+ assertThat(combinedCondition.isConditionMet).isTrue()
+ assertThat(eagerCondition.started).isFalse()
+ assertThat(lazyCondition.started).isFalse()
+ assertThat(startWhenNeededCondition.started).isFalse()
+ assertThat(overridingCondition1.started).isTrue()
+ assertThat(overridingCondition2.started).isFalse()
+
+ // Overriding condition is false, so we should only monitor other overriding conditions
+ overridingCondition1.setValue(false)
+ eagerCondition.setValue(true)
+ assertThat(combinedCondition.isConditionMet).isFalse()
+ assertThat(eagerCondition.started).isFalse()
+ assertThat(lazyCondition.started).isFalse()
+ assertThat(startWhenNeededCondition.started).isFalse()
+ assertThat(overridingCondition1.started).isTrue()
+ assertThat(overridingCondition2.started).isTrue()
+
+ // Second overriding condition is true, condition 1 is still true
+ overridingCondition2.setValue(true)
+ assertThat(combinedCondition.isConditionMet).isTrue()
+ assertThat(eagerCondition.started).isFalse()
+ assertThat(lazyCondition.started).isFalse()
+ assertThat(startWhenNeededCondition.started).isFalse()
+ assertThat(overridingCondition1.started).isFalse()
+ assertThat(overridingCondition2.started).isTrue()
+
+ // Overriding condition is cleared, condition 1 is still true
+ overridingCondition1.setValue(null)
+ overridingCondition2.setValue(null)
+ assertThat(combinedCondition.isConditionMet).isTrue()
+ assertThat(eagerCondition.started).isTrue()
+ assertThat(lazyCondition.started).isFalse()
+ assertThat(startWhenNeededCondition.started).isFalse()
+ assertThat(overridingCondition1.started).isTrue()
+ assertThat(overridingCondition2.started).isTrue()
+ }
+
+ @Test
+ fun testAndOperatorCorrectlyHandlesUnknownValues() = runSelfCancelingTest {
+ val conditions =
+ 0.rangeTo(2)
+ .map {
+ FakeCondition(scope = this, initialValue = false, startStrategy = START_EAGERLY)
+ }
+ .toList()
+
+ val combinedCondition =
+ CombinedCondition(scope = this, conditions = conditions, operand = Evaluator.OP_AND)
+
+ val callback = Condition.Callback {}
+ combinedCondition.addCallback(callback)
+
+ conditions[0].setValue(null)
+ conditions[1].setValue(true)
+ conditions[2].setValue(false)
+ assertThat(combinedCondition.isConditionMet).isFalse()
+ assertThat(combinedCondition.isConditionSet).isTrue()
+
+ // The condition should not be set since the value is unknown
+ conditions[2].setValue(true)
+ assertThat(combinedCondition.isConditionMet).isFalse()
+ assertThat(combinedCondition.isConditionSet).isFalse()
+
+ conditions[0].setValue(true)
+ assertThat(combinedCondition.isConditionMet).isTrue()
+ assertThat(combinedCondition.isConditionSet).isTrue()
+ }
+
+ @Test
+ fun testOrOperatorCorrectlyHandlesUnknownValues() = runSelfCancelingTest {
+ val conditions =
+ 0.rangeTo(2)
+ .map {
+ FakeCondition(scope = this, initialValue = false, startStrategy = START_EAGERLY)
+ }
+ .toList()
+
+ val combinedCondition =
+ CombinedCondition(scope = this, conditions = conditions, operand = Evaluator.OP_OR)
+
+ val callback = Condition.Callback {}
+ combinedCondition.addCallback(callback)
+
+ conditions[0].setValue(null)
+ conditions[1].setValue(true)
+ conditions[2].setValue(false)
+ assertThat(combinedCondition.isConditionMet).isTrue()
+ assertThat(combinedCondition.isConditionSet).isTrue()
+
+ conditions[1].setValue(false)
+ assertThat(combinedCondition.isConditionMet).isFalse()
+ // The condition should not be set since the value is unknown
+ assertThat(combinedCondition.isConditionSet).isFalse()
+ }
+
+ @Test
+ fun testEmptyConditions() = runSelfCancelingTest {
+ for (operand in intArrayOf(Evaluator.OP_OR, Evaluator.OP_AND)) {
+ val combinedCondition =
+ CombinedCondition(
+ scope = this,
+ conditions = emptyList(),
+ operand = operand,
+ )
+
+ val callback = Condition.Callback {}
+ combinedCondition.addCallback(callback)
+ assertThat(combinedCondition.isConditionMet).isFalse()
+ assertThat(combinedCondition.isConditionSet).isFalse()
+ }
+ }
+
+ private fun areStarted(conditions: List<FakeCondition>): List<Boolean> {
+ return conditions.map { it.started }
+ }
+
+ /**
+ * Executes the given block of execution within the scope of a dedicated [CoroutineScope] which
+ * is then automatically canceled and cleaned-up.
+ */
+ private fun runSelfCancelingTest(
+ block: suspend CoroutineScope.() -> Unit,
+ ) =
+ runBlocking(IMMEDIATE) {
+ val scope = CoroutineScope(coroutineContext + Job())
+ block(scope)
+ scope.cancel()
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt
index 2b4a7fb..0a8210d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt
@@ -32,7 +32,7 @@
fun flowInitiallyTrue() =
testScope.runTest {
val flow = flowOf(true)
- val condition = flow.toCondition(this)
+ val condition = flow.toCondition(scope = this, Condition.START_EAGERLY)
runCurrent()
assertThat(condition.isConditionSet).isFalse()
@@ -47,7 +47,7 @@
fun flowInitiallyFalse() =
testScope.runTest {
val flow = flowOf(false)
- val condition = flow.toCondition(this)
+ val condition = flow.toCondition(scope = this, Condition.START_EAGERLY)
runCurrent()
assertThat(condition.isConditionSet).isFalse()
@@ -62,7 +62,7 @@
fun emptyFlowWithNoInitialValue() =
testScope.runTest {
val flow = emptyFlow<Boolean>()
- val condition = flow.toCondition(this)
+ val condition = flow.toCondition(scope = this, Condition.START_EAGERLY)
condition.start()
runCurrent()
@@ -74,7 +74,12 @@
fun emptyFlowWithInitialValueOfTrue() =
testScope.runTest {
val flow = emptyFlow<Boolean>()
- val condition = flow.toCondition(scope = this, initialValue = true)
+ val condition =
+ flow.toCondition(
+ scope = this,
+ strategy = Condition.START_EAGERLY,
+ initialValue = true
+ )
condition.start()
runCurrent()
@@ -86,7 +91,12 @@
fun emptyFlowWithInitialValueOfFalse() =
testScope.runTest {
val flow = emptyFlow<Boolean>()
- val condition = flow.toCondition(scope = this, initialValue = false)
+ val condition =
+ flow.toCondition(
+ scope = this,
+ strategy = Condition.START_EAGERLY,
+ initialValue = false
+ )
condition.start()
runCurrent()
@@ -98,7 +108,7 @@
fun conditionUpdatesWhenFlowEmitsNewValue() =
testScope.runTest {
val flow = MutableStateFlow(false)
- val condition = flow.toCondition(this)
+ val condition = flow.toCondition(scope = this, strategy = Condition.START_EAGERLY)
condition.start()
runCurrent()
@@ -120,7 +130,7 @@
fun stoppingConditionUnsubscribesFromFlow() =
testScope.runTest {
val flow = MutableSharedFlow<Boolean>()
- val condition = flow.toCondition(this)
+ val condition = flow.toCondition(scope = this, strategy = Condition.START_EAGERLY)
runCurrent()
assertThat(flow.subscriptionCount.value).isEqualTo(0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
index aa1636d..de5824d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
@@ -39,12 +39,15 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.Arrays;
import java.util.HashSet;
+import kotlinx.coroutines.CoroutineScope;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class ConditionMonitorTest extends SysuiTestCase {
@@ -54,15 +57,18 @@
private HashSet<Condition> mConditions;
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+ @Mock
+ private CoroutineScope mScope;
+
private Monitor mConditionMonitor;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mCondition1 = spy(new FakeCondition());
- mCondition2 = spy(new FakeCondition());
- mCondition3 = spy(new FakeCondition());
+ mCondition1 = spy(new FakeCondition(mScope));
+ mCondition2 = spy(new FakeCondition(mScope));
+ mCondition3 = spy(new FakeCondition(mScope));
mConditions = new HashSet<>(Arrays.asList(mCondition1, mCondition2, mCondition3));
mConditionMonitor = new Monitor(mExecutor);
@@ -396,7 +402,7 @@
@Test
public void unsetCondition_shouldNotAffectValue() {
- final FakeCondition settableCondition = new FakeCondition(null, false);
+ final FakeCondition settableCondition = new FakeCondition(mScope, null, false);
mCondition1.fakeUpdateCondition(true);
mCondition2.fakeUpdateCondition(true);
mCondition3.fakeUpdateCondition(true);
@@ -414,7 +420,7 @@
@Test
public void setUnsetCondition_shouldAffectValue() {
- final FakeCondition settableCondition = new FakeCondition(null, false);
+ final FakeCondition settableCondition = new FakeCondition(mScope, null, false);
mCondition1.fakeUpdateCondition(true);
mCondition2.fakeUpdateCondition(true);
mCondition3.fakeUpdateCondition(true);
@@ -443,7 +449,7 @@
@Test
public void clearingOverridingCondition_shouldBeExcluded() {
- final FakeCondition overridingCondition = new FakeCondition(true, true);
+ final FakeCondition overridingCondition = new FakeCondition(mScope, true, true);
mCondition1.fakeUpdateCondition(false);
mCondition2.fakeUpdateCondition(false);
mCondition3.fakeUpdateCondition(false);
@@ -466,7 +472,7 @@
@Test
public void settingUnsetOverridingCondition_shouldBeIncluded() {
- final FakeCondition overridingCondition = new FakeCondition(null, true);
+ final FakeCondition overridingCondition = new FakeCondition(mScope, null, true);
mCondition1.fakeUpdateCondition(false);
mCondition2.fakeUpdateCondition(false);
mCondition3.fakeUpdateCondition(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java
index 8443221..6efade9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java
@@ -34,15 +34,23 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import kotlinx.coroutines.CoroutineScope;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class ConditionTest extends SysuiTestCase {
+ @Mock
+ CoroutineScope mScope;
+
private FakeCondition mCondition;
@Before
public void setup() {
- mCondition = spy(new FakeCondition());
+ MockitoAnnotations.initMocks(this);
+ mCondition = spy(new FakeCondition(mScope));
}
@Test
@@ -152,168 +160,4 @@
mCondition.clearCondition();
assertThat(mCondition.isConditionSet()).isFalse();
}
-
- @Test
- public void combineConditionsWithOr_allFalse_reportsNotMet() {
- mCondition.fakeUpdateCondition(false);
-
- final Condition combinedCondition = mCondition.or(
- new FakeCondition(/* initialValue= */ false));
-
- final Condition.Callback callback = mock(
- Condition.Callback.class);
- combinedCondition.addCallback(callback);
-
- assertThat(combinedCondition.isConditionSet()).isTrue();
- assertThat(combinedCondition.isConditionMet()).isFalse();
- verify(callback, times(1)).onConditionChanged(combinedCondition);
- }
-
- @Test
- public void combineConditionsWithOr_allTrue_reportsMet() {
- mCondition.fakeUpdateCondition(true);
-
- final Condition combinedCondition = mCondition.or(
- new FakeCondition(/* initialValue= */ true));
-
- final Condition.Callback callback = mock(
- Condition.Callback.class);
- combinedCondition.addCallback(callback);
-
- assertThat(combinedCondition.isConditionSet()).isTrue();
- assertThat(combinedCondition.isConditionMet()).isTrue();
- verify(callback, times(1)).onConditionChanged(combinedCondition);
- }
-
- @Test
- public void combineConditionsWithOr_singleTrue_reportsMet() {
- mCondition.fakeUpdateCondition(false);
-
- final Condition combinedCondition = mCondition.or(
- new FakeCondition(/* initialValue= */ true));
-
- final Condition.Callback callback = mock(
- Condition.Callback.class);
- combinedCondition.addCallback(callback);
-
- assertThat(combinedCondition.isConditionSet()).isTrue();
- assertThat(combinedCondition.isConditionMet()).isTrue();
- verify(callback, times(1)).onConditionChanged(combinedCondition);
- }
-
- @Test
- public void combineConditionsWithOr_unknownAndTrue_reportsMet() {
- mCondition.fakeUpdateCondition(true);
-
- // Combine with an unset condition.
- final Condition combinedCondition = mCondition.or(
- new FakeCondition(/* initialValue= */ null));
-
- final Condition.Callback callback = mock(
- Condition.Callback.class);
- combinedCondition.addCallback(callback);
-
- assertThat(combinedCondition.isConditionSet()).isTrue();
- assertThat(combinedCondition.isConditionMet()).isTrue();
- verify(callback, times(1)).onConditionChanged(combinedCondition);
- }
-
- @Test
- public void combineConditionsWithOr_unknownAndFalse_reportsNotMet() {
- mCondition.fakeUpdateCondition(false);
-
- // Combine with an unset condition.
- final Condition combinedCondition = mCondition.or(
- new FakeCondition(/* initialValue= */ null));
-
- final Condition.Callback callback = mock(
- Condition.Callback.class);
- combinedCondition.addCallback(callback);
-
- assertThat(combinedCondition.isConditionSet()).isFalse();
- assertThat(combinedCondition.isConditionMet()).isFalse();
- verify(callback, never()).onConditionChanged(combinedCondition);
- }
-
- @Test
- public void combineConditionsWithAnd_allFalse_reportsNotMet() {
- mCondition.fakeUpdateCondition(false);
-
- final Condition combinedCondition = mCondition.and(
- new FakeCondition(/* initialValue= */ false));
-
- final Condition.Callback callback = mock(
- Condition.Callback.class);
- combinedCondition.addCallback(callback);
-
- assertThat(combinedCondition.isConditionSet()).isTrue();
- assertThat(combinedCondition.isConditionMet()).isFalse();
- verify(callback, times(1)).onConditionChanged(combinedCondition);
- }
-
- @Test
- public void combineConditionsWithAnd_allTrue_reportsMet() {
- mCondition.fakeUpdateCondition(true);
-
- final Condition combinedCondition = mCondition.and(
- new FakeCondition(/* initialValue= */ true));
-
- final Condition.Callback callback = mock(
- Condition.Callback.class);
- combinedCondition.addCallback(callback);
-
- assertThat(combinedCondition.isConditionSet()).isTrue();
- assertThat(combinedCondition.isConditionMet()).isTrue();
- verify(callback, times(1)).onConditionChanged(combinedCondition);
- }
-
- @Test
- public void combineConditionsWithAnd_singleTrue_reportsNotMet() {
- mCondition.fakeUpdateCondition(true);
-
- final Condition combinedCondition = mCondition.and(
- new FakeCondition(/* initialValue= */ false));
-
- final Condition.Callback callback = mock(
- Condition.Callback.class);
- combinedCondition.addCallback(callback);
-
- assertThat(combinedCondition.isConditionSet()).isTrue();
- assertThat(combinedCondition.isConditionMet()).isFalse();
- verify(callback, times(1)).onConditionChanged(combinedCondition);
- }
-
- @Test
- public void combineConditionsWithAnd_unknownAndTrue_reportsNotMet() {
- mCondition.fakeUpdateCondition(true);
-
- // Combine with an unset condition.
- final Condition combinedCondition = mCondition.and(
- new FakeCondition(/* initialValue= */ null));
-
- final Condition.Callback callback = mock(
- Condition.Callback.class);
- combinedCondition.addCallback(callback);
-
- assertThat(combinedCondition.isConditionSet()).isFalse();
- assertThat(combinedCondition.isConditionMet()).isFalse();
- verify(callback, never()).onConditionChanged(combinedCondition);
- }
-
- @Test
- public void combineConditionsWithAnd_unknownAndFalse_reportsMet() {
- mCondition.fakeUpdateCondition(false);
-
- // Combine with an unset condition.
- final Condition combinedCondition = mCondition.and(
- new FakeCondition(/* initialValue= */ null));
-
- final Condition.Callback callback = mock(
- Condition.Callback.class);
- combinedCondition.addCallback(callback);
-
- assertThat(combinedCondition.isConditionSet()).isTrue();
- assertThat(combinedCondition.isConditionMet()).isFalse();
- verify(callback, times(1)).onConditionChanged(combinedCondition);
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/FakeCondition.java b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/FakeCondition.java
index 55a6d39..a325cbf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/FakeCondition.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/FakeCondition.java
@@ -16,21 +16,19 @@
package com.android.systemui.shared.condition;
+import kotlinx.coroutines.CoroutineScope;
+
/**
* Fake implementation of {@link Condition}, and provides a way for tests to update
* condition fulfillment.
*/
public class FakeCondition extends Condition {
- FakeCondition() {
- super();
+ FakeCondition(CoroutineScope scope) {
+ super(scope);
}
- FakeCondition(Boolean initialValue) {
- super(initialValue, false);
- }
-
- FakeCondition(Boolean initialValue, boolean overriding) {
- super(initialValue, overriding);
+ FakeCondition(CoroutineScope scope, Boolean initialValue, boolean overriding) {
+ super(scope, initialValue, overriding);
}
@Override
@@ -41,6 +39,11 @@
public void stop() {
}
+ @Override
+ protected int getStartStrategy() {
+ return START_EAGERLY;
+ }
+
public void fakeUpdateCondition(boolean isConditionMet) {
updateCondition(isConditionMet);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
index 5fc0ffe..8cb530c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt
@@ -4,7 +4,7 @@
import android.util.DisplayMetrics
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.log.LogBuffer
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.phone.LSShadeTransitionLogger
import com.android.systemui.statusbar.phone.LockscreenGestureLogger
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 beaf300..c49f179 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 @@
@Test
fun ignoreShadeBlurUntilHidden_schedulesFrame() {
notificationShadeDepthController.blursDisabledForAppLaunch = true
+ verify(blurUtils).prepareBlur(any(), anyInt())
verify(choreographer)
.postFrameCallback(eq(notificationShadeDepthController.updateBlurCallback))
}
@@ -419,6 +420,7 @@
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/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index 5431eba..c7ea09c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -70,7 +70,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index 9441d49..d5689dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -48,7 +48,7 @@
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.CarrierConfigTracker;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
index 4c1f0a8..35b9814 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
@@ -43,7 +43,7 @@
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.R;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.log.LogBuffer;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.CarrierConfigTracker;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
index bef9fcb..a37c386 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
@@ -19,9 +19,9 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogcatEchoTracker
import com.android.systemui.statusbar.StatusBarState
import com.google.common.truth.Truth.assertThat
import org.junit.Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
index a1168f8..f0abf2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
@@ -32,9 +32,9 @@
import android.view.View;
import android.view.animation.Interpolator;
+import com.android.app.animation.Interpolators;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.notification.stack.AnimationFilter;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ViewState;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 2831d2f..163369f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -41,6 +41,7 @@
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor;
import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -92,6 +93,7 @@
@Mock private BiometricUnlockController mBiometricUnlockController;
@Mock private AuthController mAuthController;
@Mock private DozeHost.Callback mCallback;
+ @Mock private BurnInInteractor mBurnInInteractor;
@Before
public void setup() {
@@ -102,7 +104,8 @@
() -> mAssistManager, mDozeScrimController,
mKeyguardUpdateMonitor, mPulseExpansionHandler,
mNotificationShadeWindowController, mNotificationWakeUpCoordinator,
- mAuthController, mNotificationIconAreaController);
+ mAuthController, mNotificationIconAreaController,
+ mBurnInInteractor);
mDozeServiceHost.initialize(
mCentralSurfaces,
@@ -213,4 +216,12 @@
assertFalse(mDozeServiceHost.isPulsePending());
verify(mDozeScrimController).pulseOutNow();
}
+ @Test
+ public void dozeTimeTickSentTBurnInInteractor() {
+ // WHEN dozeTimeTick
+ mDozeServiceHost.dozeTimeTick();
+
+ // THEN burnInInteractor's dozeTimeTick is updated
+ verify(mBurnInInteractor).dozeTimeTick();
+ }
}
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 529519a..a501556 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 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 @@
@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 @@
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 @@
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 0000000..d9c2cfa
--- /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/fragment/CollapsedStatusBarFragmentLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
index 3a0a94d..9bc49ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt
@@ -20,7 +20,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.LogcatEchoTracker
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
@@ -43,15 +43,39 @@
fun logDisableFlagChange_bufferHasStates() {
val state = DisableFlagsLogger.DisableState(0, 1)
- logger.logDisableFlagChange(state, state)
+ logger.logDisableFlagChange(state)
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
val actualString = stringWriter.toString()
- val expectedLogString = disableFlagsLogger.getDisableFlagsString(
- old = null, new = state, newAfterLocalModification = state
- )
+ val expectedLogString =
+ disableFlagsLogger.getDisableFlagsString(
+ old = null,
+ new = state,
+ newAfterLocalModification = null,
+ )
assertThat(actualString).contains(expectedLogString)
}
+
+ @Test
+ fun logVisibilityModel_bufferCorrect() {
+ logger.logVisibilityModel(
+ StatusBarVisibilityModel(
+ showClock = false,
+ showNotificationIcons = true,
+ showOngoingCallChip = false,
+ showSystemInfo = true,
+ )
+ )
+
+ val stringWriter = StringWriter()
+ buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+ val actualString = stringWriter.toString()
+
+ assertThat(actualString).contains("showClock=false")
+ assertThat(actualString).contains("showNotificationIcons=true")
+ assertThat(actualString).contains("showOngoingCallChip=false")
+ assertThat(actualString).contains("showSystemInfo=true")
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 2a3c775..2176992 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -16,6 +16,8 @@
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
+import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN;
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN;
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT;
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE;
@@ -52,9 +54,9 @@
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.LogcatEchoTracker;
import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.plugins.log.LogBuffer;
-import com.android.systemui.plugins.log.LogcatEchoTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.ShadeViewController;
@@ -93,6 +95,7 @@
public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
private NotificationIconAreaController mMockNotificationAreaController;
+ private ShadeExpansionStateManager mShadeExpansionStateManager;
private View mNotificationAreaInner;
private OngoingCallController mOngoingCallController;
private SystemStatusAnimationScheduler mAnimationScheduler;
@@ -173,6 +176,10 @@
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+
+ fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
+
+ assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
}
@Test
@@ -278,6 +285,10 @@
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE));
+
+ fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
+
+ Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE));
}
@Test
@@ -291,6 +302,70 @@
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
assertEquals(View.VISIBLE, getClockView().getVisibility());
+
+ fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_CLOCK, 0, false);
+
+ assertEquals(View.GONE, getClockView().getVisibility());
+ }
+
+ @Test
+ public void disable_shadeOpenAndShouldHide_everythingHidden() {
+ CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+
+ // WHEN the shade is open and configured to hide the status bar icons
+ mShadeExpansionStateManager.updateState(STATE_OPEN);
+ when(mShadeViewController.shouldHideStatusBarIconsWhenExpanded()).thenReturn(true);
+
+ fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+ // THEN all views are hidden
+ assertEquals(View.INVISIBLE, getClockView().getVisibility());
+ Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE));
+ assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
+ }
+
+ @Test
+ public void disable_shadeOpenButNotShouldHide_everythingShown() {
+ CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+
+ // WHEN the shade is open but *not* configured to hide the status bar icons
+ mShadeExpansionStateManager.updateState(STATE_OPEN);
+ when(mShadeViewController.shouldHideStatusBarIconsWhenExpanded()).thenReturn(false);
+
+ fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+ // THEN all views are shown
+ assertEquals(View.VISIBLE, getClockView().getVisibility());
+ Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE));
+ assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+ }
+
+ /** Regression test for b/279790651. */
+ @Test
+ public void disable_shadeOpenAndShouldHide_thenShadeNotOpenAndDozingUpdate_everythingShown() {
+ CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+
+ // WHEN the shade is open and configured to hide the status bar icons
+ mShadeExpansionStateManager.updateState(STATE_OPEN);
+ when(mShadeViewController.shouldHideStatusBarIconsWhenExpanded()).thenReturn(true);
+
+ fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+ // THEN all views are hidden
+ assertEquals(View.INVISIBLE, getClockView().getVisibility());
+ Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE));
+ assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
+
+ // WHEN the shade is updated to no longer be open
+ mShadeExpansionStateManager.updateState(STATE_CLOSED);
+
+ // AND we internally request an update via dozing change
+ fragment.onDozingChanged(true);
+
+ // THEN all views are shown
+ assertEquals(View.VISIBLE, getClockView().getVisibility());
+ Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE));
+ assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
}
@Test
@@ -323,7 +398,6 @@
assertEquals(View.VISIBLE,
mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE));
-
}
@Test
@@ -356,20 +430,26 @@
public void disable_ongoingCallEnded_chipHidden() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
- when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
-
// Ongoing call started
+ when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
assertEquals(View.VISIBLE,
mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
// Ongoing call ended
when(mOngoingCallController.hasOngoingCall()).thenReturn(false);
-
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
assertEquals(View.GONE,
mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+
+ // Ongoing call started
+ when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
+ fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+ assertEquals(View.VISIBLE,
+ mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
}
@Test
@@ -494,6 +574,8 @@
when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
mSecureSettings = mock(SecureSettings.class);
+ mShadeExpansionStateManager = new ShadeExpansionStateManager();
+
setUpNotificationIconAreaController();
return new CollapsedStatusBarFragment(
mStatusBarFragmentComponentFactory,
@@ -501,7 +583,7 @@
mAnimationScheduler,
mLocationPublisher,
mMockNotificationAreaController,
- new ShadeExpansionStateManager(),
+ mShadeExpansionStateManager,
mock(FeatureFlags.class),
mStatusBarIconController,
mIconManagerFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModelTest.kt
new file mode 100644
index 0000000..8e789cb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModelTest.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.fragment
+
+import android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS
+import android.app.StatusBarManager.DISABLE_CLOCK
+import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS
+import android.app.StatusBarManager.DISABLE_ONGOING_CALL_CHIP
+import android.app.StatusBarManager.DISABLE_SYSTEM_INFO
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.fragment.StatusBarVisibilityModel.Companion.createDefaultModel
+import com.android.systemui.statusbar.phone.fragment.StatusBarVisibilityModel.Companion.createModelFromFlags
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class StatusBarVisibilityModelTest : SysuiTestCase() {
+ @Test
+ fun createDefaultModel_everythingEnabled() {
+ val result = createDefaultModel()
+
+ val expected =
+ StatusBarVisibilityModel(
+ showClock = true,
+ showNotificationIcons = true,
+ showOngoingCallChip = true,
+ showSystemInfo = true,
+ )
+
+ assertThat(result).isEqualTo(expected)
+ }
+
+ @Test
+ fun createModelFromFlags_clockNotDisabled_showClockTrue() {
+ val result = createModelFromFlags(disabled1 = 0, disabled2 = 0)
+
+ assertThat(result.showClock).isTrue()
+ }
+
+ @Test
+ fun createModelFromFlags_clockDisabled_showClockFalse() {
+ val result = createModelFromFlags(disabled1 = DISABLE_CLOCK, disabled2 = 0)
+
+ assertThat(result.showClock).isFalse()
+ }
+
+ @Test
+ fun createModelFromFlags_notificationIconsNotDisabled_showNotificationIconsTrue() {
+ val result = createModelFromFlags(disabled1 = 0, disabled2 = 0)
+
+ assertThat(result.showNotificationIcons).isTrue()
+ }
+
+ @Test
+ fun createModelFromFlags_notificationIconsDisabled_showNotificationIconsFalse() {
+ val result = createModelFromFlags(disabled1 = DISABLE_NOTIFICATION_ICONS, disabled2 = 0)
+
+ assertThat(result.showNotificationIcons).isFalse()
+ }
+
+ @Test
+ fun createModelFromFlags_ongoingCallChipNotDisabled_showOngoingCallChipTrue() {
+ val result = createModelFromFlags(disabled1 = 0, disabled2 = 0)
+
+ assertThat(result.showOngoingCallChip).isTrue()
+ }
+
+ @Test
+ fun createModelFromFlags_ongoingCallChipDisabled_showOngoingCallChipFalse() {
+ val result = createModelFromFlags(disabled1 = DISABLE_ONGOING_CALL_CHIP, disabled2 = 0)
+
+ assertThat(result.showOngoingCallChip).isFalse()
+ }
+
+ @Test
+ fun createModelFromFlags_systemInfoAndIconsNotDisabled_showSystemInfoTrue() {
+ val result = createModelFromFlags(disabled1 = 0, disabled2 = 0)
+
+ assertThat(result.showSystemInfo).isTrue()
+ }
+
+ @Test
+ fun createModelFromFlags_disable1SystemInfoDisabled_showSystemInfoFalse() {
+ val result = createModelFromFlags(disabled1 = DISABLE_SYSTEM_INFO, disabled2 = 0)
+
+ assertThat(result.showSystemInfo).isFalse()
+ }
+
+ @Test
+ fun createModelFromFlags_disable2SystemIconsDisabled_showSystemInfoFalse() {
+ val result = createModelFromFlags(disabled1 = 0, disabled2 = DISABLE2_SYSTEM_ICONS)
+
+ assertThat(result.showSystemInfo).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModelTest.kt
index 03cd94f..c43778a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModelTest.kt
@@ -18,7 +18,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.log.LogMessageImpl
+import com.android.systemui.log.LogMessageImpl
import com.google.common.truth.Truth.assertThat
import org.junit.Test
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 db50163..b8e4306 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 @@
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/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index d9d8b63..3b0d512 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -585,8 +585,6 @@
// devices.
layout.setBaselineAligned(false);
- final boolean isRtl = mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
-
// Add smart replies
Button previous = null;
SmartReplyView.SmartReplies smartReplies =
@@ -606,11 +604,7 @@
if (previous != null) {
ViewGroup.MarginLayoutParams lp =
(ViewGroup.MarginLayoutParams) previous.getLayoutParams();
- if (isRtl) {
- lp.leftMargin = mSpacing;
- } else {
- lp.rightMargin = mSpacing;
- }
+ lp.setMarginEnd(mSpacing);
}
layout.addView(current);
previous = current;
@@ -634,11 +628,7 @@
if (previous != null) {
ViewGroup.MarginLayoutParams lp =
(ViewGroup.MarginLayoutParams) previous.getLayoutParams();
- if (isRtl) {
- lp.leftMargin = mSpacing;
- } else {
- lp.rightMargin = mSpacing;
- }
+ lp.setMarginEnd(mSpacing);
}
layout.addView(current);
previous = current;
@@ -937,8 +927,8 @@
.collect(Collectors.toList());
Button singleLineButton = buttons.get(0);
Button doubleLineButton = buttons.get(1);
- Drawable singleLineDrawable = singleLineButton.getCompoundDrawables()[0]; // left drawable
- Drawable doubleLineDrawable = doubleLineButton.getCompoundDrawables()[0]; // left drawable
+ Drawable singleLineDrawable = singleLineButton.getCompoundDrawablesRelative()[0]; // start
+ Drawable doubleLineDrawable = doubleLineButton.getCompoundDrawablesRelative()[0]; // start
assertEquals(singleLineDrawable.getBounds().width(),
doubleLineDrawable.getBounds().width());
assertEquals(singleLineDrawable.getBounds().height(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
index 2e66b20..4514249 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
@@ -19,9 +19,9 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.log.LogcatEchoTracker
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt
index 5b431e7..0983041 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt
@@ -14,6 +14,8 @@
package com.android.systemui.animation
+import com.android.app.animation.Interpolators
+
/** A [LaunchAnimator] to be used in tests. */
fun fakeLaunchAnimator(): LaunchAnimator {
return LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
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 0000000..fd91391
--- /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/keyguard/data/repository/FakeTrustRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
index 6690de8..f0dbc60 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
@@ -19,13 +19,23 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
class FakeTrustRepository : TrustRepository {
private val _isCurrentUserTrusted = MutableStateFlow(false)
override val isCurrentUserTrusted: Flow<Boolean>
get() = _isCurrentUserTrusted
+ private val _isCurrentUserActiveUnlockAvailable = MutableStateFlow(false)
+ override val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean> =
+ _isCurrentUserActiveUnlockAvailable.asStateFlow()
+
fun setCurrentUserTrusted(trust: Boolean) {
_isCurrentUserTrusted.value = trust
}
+
+ fun setCurrentUserActiveUnlockAvailable(available: Boolean) {
+ _isCurrentUserActiveUnlockAvailable.value = available
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/retail/data/repository/FakeRetailModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/retail/data/repository/FakeRetailModeRepository.kt
new file mode 100644
index 0000000..75b29ca
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/retail/data/repository/FakeRetailModeRepository.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.retail.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeRetailModeRepository : RetailModeRepository {
+
+ private val _retailMode = MutableStateFlow(false)
+ override val retailMode: StateFlow<Boolean> = _retailMode.asStateFlow()
+
+ private var _retailModeValue = false
+ override val inRetailMode: Boolean
+ get() = _retailModeValue
+
+ fun setRetailMode(value: Boolean) {
+ _retailMode.value = value
+ _retailModeValue = value
+ }
+}
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 f6b24da..84ace7c 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 void stopCasting(CastDevice device) {
}
+
+ @Override
+ public boolean hasConnectedCastDevice() {
+ return false;
+ }
}
diff --git a/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java b/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java
deleted file mode 100644
index 715697d..0000000
--- a/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.autofill;
-
-import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
-
-import static com.android.server.autofill.Helper.sVerbose;
-
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.app.AppGlobals;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.ICancellationSignal;
-import android.os.RemoteException;
-import android.service.autofill.Dataset;
-import android.service.autofill.FillResponse;
-import android.service.autofill.IFillCallback;
-import android.service.autofill.SaveInfo;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.util.Slog;
-import android.view.autofill.AutofillId;
-import android.view.autofill.IAutoFillManagerClient;
-import android.view.inputmethod.InlineSuggestionsRequest;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.infra.AndroidFuture;
-
-import java.util.List;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Maintains a client suggestions session with the
- * {@link android.view.autofill.AutofillRequestCallback} through the {@link IAutoFillManagerClient}.
- *
- */
-final class ClientSuggestionsSession {
-
- private static final String TAG = "ClientSuggestionsSession";
- private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 15 * DateUtils.SECOND_IN_MILLIS;
-
- private final int mSessionId;
- private final IAutoFillManagerClient mClient;
- private final Handler mHandler;
- private final ComponentName mComponentName;
-
- private final RemoteFillService.FillServiceCallbacks mCallbacks;
-
- private final Object mLock = new Object();
- @GuardedBy("mLock")
- private AndroidFuture<FillResponse> mPendingFillRequest;
- @GuardedBy("mLock")
- private int mPendingFillRequestId = INVALID_REQUEST_ID;
-
- ClientSuggestionsSession(int sessionId, IAutoFillManagerClient client, Handler handler,
- ComponentName componentName, RemoteFillService.FillServiceCallbacks callbacks) {
- mSessionId = sessionId;
- mClient = client;
- mHandler = handler;
- mComponentName = componentName;
- mCallbacks = callbacks;
- }
-
- void onFillRequest(int requestId, InlineSuggestionsRequest inlineRequest, int flags) {
- final AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
- final AtomicReference<AndroidFuture<FillResponse>> futureRef = new AtomicReference<>();
- final AndroidFuture<FillResponse> fillRequest = new AndroidFuture<>();
-
- mHandler.post(() -> {
- if (sVerbose) {
- Slog.v(TAG, "calling onFillRequest() for id=" + requestId);
- }
-
- try {
- mClient.requestFillFromClient(requestId, inlineRequest,
- new FillCallbackImpl(fillRequest, futureRef, cancellationSink));
- } catch (RemoteException e) {
- fillRequest.completeExceptionally(e);
- }
- });
-
- fillRequest.orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
- futureRef.set(fillRequest);
-
- synchronized (mLock) {
- mPendingFillRequest = fillRequest;
- mPendingFillRequestId = requestId;
- }
-
- fillRequest.whenComplete((res, err) -> mHandler.post(() -> {
- synchronized (mLock) {
- mPendingFillRequest = null;
- mPendingFillRequestId = INVALID_REQUEST_ID;
- }
- if (err == null) {
- processAutofillId(res);
- mCallbacks.onFillRequestSuccess(requestId, res,
- mComponentName.getPackageName(), flags);
- } else {
- Slog.e(TAG, "Error calling on client fill request", err);
- if (err instanceof TimeoutException) {
- dispatchCancellationSignal(cancellationSink.get());
- mCallbacks.onFillRequestTimeout(requestId);
- } else if (err instanceof CancellationException) {
- dispatchCancellationSignal(cancellationSink.get());
- } else {
- mCallbacks.onFillRequestFailure(requestId, err.getMessage());
- }
- }
- }));
- }
-
- /**
- * Gets the application info for the component.
- */
- @Nullable
- static ApplicationInfo getAppInfo(ComponentName comp, @UserIdInt int userId) {
- try {
- ApplicationInfo si = AppGlobals.getPackageManager().getApplicationInfo(
- comp.getPackageName(),
- PackageManager.GET_META_DATA,
- userId);
- if (si != null) {
- return si;
- }
- } catch (RemoteException e) {
- }
- return null;
- }
-
- /**
- * Gets the user-visible name of the application.
- */
- @Nullable
- @GuardedBy("mLock")
- static CharSequence getAppLabelLocked(Context context, ApplicationInfo appInfo) {
- return appInfo == null ? null : appInfo.loadSafeLabel(
- context.getPackageManager(), 0 /* do not ellipsize */,
- TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM);
- }
-
- /**
- * Gets the user-visible icon of the application.
- */
- @Nullable
- @GuardedBy("mLock")
- static Drawable getAppIconLocked(Context context, ApplicationInfo appInfo) {
- return appInfo == null ? null : appInfo.loadIcon(context.getPackageManager());
- }
-
- int cancelCurrentRequest() {
- synchronized (mLock) {
- return mPendingFillRequest != null && mPendingFillRequest.cancel(false)
- ? mPendingFillRequestId
- : INVALID_REQUEST_ID;
- }
- }
-
- /**
- * The {@link AutofillId} which the client gets from its view is not contain the session id,
- * but Autofill framework is using the {@link AutofillId} with a session id. So before using
- * those ids in the Autofill framework, applies the current session id.
- *
- * @param res which response need to apply for a session id
- */
- private void processAutofillId(FillResponse res) {
- if (res == null) {
- return;
- }
-
- final List<Dataset> datasets = res.getDatasets();
- if (datasets != null && !datasets.isEmpty()) {
- for (int i = 0; i < datasets.size(); i++) {
- final Dataset dataset = datasets.get(i);
- if (dataset != null) {
- applySessionId(dataset.getFieldIds());
- }
- }
- }
-
- final SaveInfo saveInfo = res.getSaveInfo();
- if (saveInfo != null) {
- applySessionId(saveInfo.getOptionalIds());
- applySessionId(saveInfo.getRequiredIds());
- applySessionId(saveInfo.getSanitizerValues());
- applySessionId(saveInfo.getTriggerId());
- }
- }
-
- private void applySessionId(List<AutofillId> ids) {
- if (ids == null || ids.isEmpty()) {
- return;
- }
-
- for (int i = 0; i < ids.size(); i++) {
- applySessionId(ids.get(i));
- }
- }
-
- private void applySessionId(AutofillId[][] ids) {
- if (ids == null) {
- return;
- }
- for (int i = 0; i < ids.length; i++) {
- applySessionId(ids[i]);
- }
- }
-
- private void applySessionId(AutofillId[] ids) {
- if (ids == null) {
- return;
- }
- for (int i = 0; i < ids.length; i++) {
- applySessionId(ids[i]);
- }
- }
-
- private void applySessionId(AutofillId id) {
- if (id == null) {
- return;
- }
- id.setSessionId(mSessionId);
- }
-
- private void dispatchCancellationSignal(@Nullable ICancellationSignal signal) {
- if (signal == null) {
- return;
- }
- try {
- signal.cancel();
- } catch (RemoteException e) {
- Slog.e(TAG, "Error requesting a cancellation", e);
- }
- }
-
- private class FillCallbackImpl extends IFillCallback.Stub {
- final AndroidFuture<FillResponse> mFillRequest;
- final AtomicReference<AndroidFuture<FillResponse>> mFutureRef;
- final AtomicReference<ICancellationSignal> mCancellationSink;
-
- FillCallbackImpl(AndroidFuture<FillResponse> fillRequest,
- AtomicReference<AndroidFuture<FillResponse>> futureRef,
- AtomicReference<ICancellationSignal> cancellationSink) {
- mFillRequest = fillRequest;
- mFutureRef = futureRef;
- mCancellationSink = cancellationSink;
- }
-
- @Override
- public void onCancellable(ICancellationSignal cancellation) {
- AndroidFuture<FillResponse> future = mFutureRef.get();
- if (future != null && future.isCancelled()) {
- dispatchCancellationSignal(cancellation);
- } else {
- mCancellationSink.set(cancellation);
- }
- }
-
- @Override
- public void onSuccess(FillResponse response) {
- mFillRequest.complete(response);
- }
-
- @Override
- public void onFailure(int requestId, CharSequence message) {
- String errorMessage = message == null ? "" : String.valueOf(message);
- mFillRequest.completeExceptionally(
- new RuntimeException(errorMessage));
- }
- }
-}
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java b/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java
index feae56e..b8bac61 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java
@@ -157,6 +157,8 @@
if (sDebug) {
Log.d(TAG, "onSuccess Response: " + response);
}
+ fieldClassificationServiceCallbacks
+ .onClassificationRequestSuccess(response);
}
@Override
@@ -165,6 +167,8 @@
if (sDebug) {
Log.d(TAG, "onFailure");
}
+ fieldClassificationServiceCallbacks
+ .onClassificationRequestFailure(0, null);
}
@Override
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index f83d734..9e46d73 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -36,7 +36,6 @@
import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
import static android.view.autofill.AutofillManager.COMMIT_REASON_UNKNOWN;
-import static android.view.autofill.AutofillManager.FLAG_ENABLED_CLIENT_SUGGESTIONS;
import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
@@ -205,6 +204,10 @@
RemoteFieldClassificationService.FieldClassificationServiceCallbacks {
private static final String TAG = "AutofillSession";
+ // This should never be true in production. This is only for local debugging.
+ // Otherwise it will spam logcat.
+ private static final boolean DBG = false;
+
private static final String ACTION_DELAYED_FILL =
"android.service.autofill.action.DELAYED_FILL";
private static final String EXTRA_REQUEST_ID = "android.service.autofill.extra.REQUEST_ID";
@@ -446,9 +449,6 @@
*/
private final PccAssistDataReceiverImpl mPccAssistReceiver = new PccAssistDataReceiverImpl();
- @Nullable
- private ClientSuggestionsSession mClientSuggestionsSession;
-
private final ClassificationState mClassificationState = new ClassificationState();
// TODO(b/216576510): Share one BroadcastReceiver between all Sessions instead of creating a
@@ -590,9 +590,6 @@
/** Whether the current {@link FillResponse} is expired. */
private boolean mExpiredResponse;
- /** Whether the client is using {@link android.view.autofill.AutofillRequestCallback}. */
- private boolean mClientSuggestionsEnabled;
-
/** Whether the fill dialog UI is disabled. */
private boolean mFillDialogDisabled;
}
@@ -623,21 +620,14 @@
}
mWaitForInlineRequest = inlineSuggestionsRequest != null;
mPendingInlineSuggestionsRequest = inlineSuggestionsRequest;
- mWaitForInlineRequest = inlineSuggestionsRequest != null;
- maybeRequestFillFromServiceLocked();
+ maybeRequestFillLocked();
viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
}
} : null;
}
- void newAutofillRequestLocked(@Nullable InlineSuggestionsRequest inlineRequest) {
- mPendingFillRequest = null;
- mWaitForInlineRequest = inlineRequest != null;
- mPendingInlineSuggestionsRequest = inlineRequest;
- }
-
@GuardedBy("mLock")
- void maybeRequestFillFromServiceLocked() {
+ void maybeRequestFillLocked() {
if (mPendingFillRequest == null) {
return;
}
@@ -647,15 +637,13 @@
return;
}
- if (mPendingInlineSuggestionsRequest.isServiceSupported()) {
- mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
- mPendingFillRequest.getFillContexts(),
- mPendingFillRequest.getHints(),
- mPendingFillRequest.getClientState(),
- mPendingFillRequest.getFlags(),
- mPendingInlineSuggestionsRequest,
- mPendingFillRequest.getDelayedFillIntentSender());
- }
+ mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(),
+ mPendingFillRequest.getFillContexts(),
+ mPendingFillRequest.getHints(),
+ mPendingFillRequest.getClientState(),
+ mPendingFillRequest.getFlags(),
+ mPendingInlineSuggestionsRequest,
+ mPendingFillRequest.getDelayedFillIntentSender());
}
mLastFillRequest = mPendingFillRequest;
@@ -777,7 +765,7 @@
: mDelayedFillPendingIntent.getIntentSender());
mPendingFillRequest = request;
- maybeRequestFillFromServiceLocked();
+ maybeRequestFillLocked();
}
if (mActivityToken != null) {
@@ -1099,39 +1087,30 @@
}
/**
- * Cancels the last request sent to the {@link #mRemoteFillService} or the
- * {@link #mClientSuggestionsSession}.
+ * Cancels the last request sent to the {@link #mRemoteFillService}.
*/
@GuardedBy("mLock")
private void cancelCurrentRequestLocked() {
- if (mRemoteFillService == null && mClientSuggestionsSession == null) {
- wtf(null, "cancelCurrentRequestLocked() called without a remote service or a "
- + "client suggestions session. mForAugmentedAutofillOnly: %s",
- mSessionFlags.mAugmentedAutofillOnly);
+ if (mRemoteFillService == null) {
+ wtf(null, "cancelCurrentRequestLocked() called without a remote service. "
+ + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly);
return;
}
+ final int canceledRequest = mRemoteFillService.cancelCurrentRequest();
- if (mRemoteFillService != null) {
- final int canceledRequest = mRemoteFillService.cancelCurrentRequest();
+ // Remove the FillContext as there will never be a response for the service
+ if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) {
+ final int numContexts = mContexts.size();
- // Remove the FillContext as there will never be a response for the service
- if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) {
- final int numContexts = mContexts.size();
-
- // It is most likely the last context, hence search backwards
- for (int i = numContexts - 1; i >= 0; i--) {
- if (mContexts.get(i).getRequestId() == canceledRequest) {
- if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest);
- mContexts.remove(i);
- break;
- }
+ // It is most likely the last context, hence search backwards
+ for (int i = numContexts - 1; i >= 0; i--) {
+ if (mContexts.get(i).getRequestId() == canceledRequest) {
+ if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest);
+ mContexts.remove(i);
+ break;
}
}
}
-
- if (mClientSuggestionsSession != null) {
- mClientSuggestionsSession.cancelCurrentRequest();
- }
}
private boolean isViewFocusedLocked(int flags) {
@@ -1225,30 +1204,17 @@
requestAssistStructureForPccLocked(flags | FLAG_PCC_DETECTION);
}
- // Only ask IME to create inline suggestions request when
- // 1. Autofill provider supports it or client enabled client suggestions.
- // 2. The render service is available.
- // 3. The view is focused. (The view may not be focused if the autofill is triggered
- // manually.)
+ // Only ask IME to create inline suggestions request if Autofill provider supports it and
+ // the render service is available except the autofill is triggered manually and the view
+ // is also not focused.
final RemoteInlineSuggestionRenderService remoteRenderService =
mService.getRemoteInlineSuggestionRenderServiceLocked();
- if ((mSessionFlags.mInlineSupportedByService || mSessionFlags.mClientSuggestionsEnabled)
- && remoteRenderService != null
- && (isViewFocusedLocked(flags) || (isRequestSupportFillDialog(flags)))) {
- final Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer;
- if (mSessionFlags.mClientSuggestionsEnabled) {
- final int finalRequestId = requestId;
- inlineSuggestionsRequestConsumer = (inlineSuggestionsRequest) -> {
- // Using client suggestions
- synchronized (mLock) {
- onClientFillRequestLocked(finalRequestId, inlineSuggestionsRequest);
- }
- viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
- };
- } else {
- inlineSuggestionsRequestConsumer = mAssistReceiver.newAutofillRequestLocked(
- viewState, /* isInlineRequest= */ true);
- }
+ if (mSessionFlags.mInlineSupportedByService
+ && remoteRenderService != null
+ && (isViewFocusedLocked(flags) || isRequestSupportFillDialog(flags))) {
+ Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
+ mAssistReceiver.newAutofillRequestLocked(viewState,
+ /* isInlineRequest= */ true);
if (inlineSuggestionsRequestConsumer != null) {
final AutofillId focusedId = mCurrentViewId;
final int requestIdCopy = requestId;
@@ -1264,18 +1230,10 @@
);
viewState.setState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST);
}
- } else if (mSessionFlags.mClientSuggestionsEnabled) {
- // Request client suggestions for the dropdown mode
- onClientFillRequestLocked(requestId, null);
} else {
mAssistReceiver.newAutofillRequestLocked(viewState, /* isInlineRequest= */ false);
}
- if (mSessionFlags.mClientSuggestionsEnabled) {
- // Using client suggestions, unnecessary request AssistStructure
- return;
- }
-
// Now request the assist structure data.
requestAssistStructureLocked(requestId, flags);
}
@@ -1286,6 +1244,8 @@
@GuardedBy("mLock")
private void requestAssistStructureForPccLocked(int flags) {
+ if (!mClassificationState.shouldTriggerRequest()) return;
+ mClassificationState.updatePendingRequest();
// Get request id
int requestId;
// TODO(b/158623971): Update this to prevent possible overflow
@@ -1380,11 +1340,6 @@
mSessionFlags = new SessionFlags();
mSessionFlags.mAugmentedAutofillOnly = forAugmentedAutofillOnly;
mSessionFlags.mInlineSupportedByService = mService.isInlineSuggestionsEnabledLocked();
- if (mContext.checkCallingPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS)
- == PackageManager.PERMISSION_GRANTED) {
- mSessionFlags.mClientSuggestionsEnabled =
- (mFlags & FLAG_ENABLED_CLIENT_SUGGESTIONS) != 0;
- }
setClientLocked(client);
}
@@ -1522,15 +1477,14 @@
if (requestLog != null) {
requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, -1);
}
- processNullResponseOrFallbackLocked(requestId, requestFlags);
+ processNullResponseLocked(requestId, requestFlags);
return;
}
// TODO: Check if this is required. We can still present datasets to the user even if
// traditional field classification is disabled.
fieldClassificationIds = response.getFieldClassificationIds();
- if (!mSessionFlags.mClientSuggestionsEnabled && fieldClassificationIds != null
- && !mService.isFieldClassificationEnabledLocked()) {
+ if (fieldClassificationIds != null && !mService.isFieldClassificationEnabledLocked()) {
Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
processNullResponseLocked(requestId, requestFlags);
return;
@@ -1623,12 +1577,18 @@
// TODO(b/266379948): Ideally wait for PCC request to finish for a while more
// (say 100ms) before proceeding further on.
+ if (DBG) {
+ Slog.d(TAG, "DBG: Initial response: " + response);
+ }
synchronized (mLock) {
response = getEffectiveFillResponse(response);
if (isEmptyResponse(response)) {
// Treat it as a null response.
processNullResponseLocked(requestId, requestFlags);
}
+ if (DBG) {
+ Slog.d(TAG, "DBG: Processed response: " + response);
+ }
processResponseLocked(response, null, requestFlags);
}
}
@@ -1643,9 +1603,7 @@
|| (ArrayUtils.isEmpty(saveInfo.getOptionalIds())
&& ArrayUtils.isEmpty(saveInfo.getRequiredIds())
&& ((saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) == 0)))
- && (ArrayUtils.isEmpty(response.getFieldClassificationIds())
- || (!mSessionFlags.mClientSuggestionsEnabled
- && !mService.isFieldClassificationEnabledLocked())));
+ && (ArrayUtils.isEmpty(response.getFieldClassificationIds())));
}
}
@@ -1655,12 +1613,25 @@
DatasetComputationContainer autofillProviderContainer = new DatasetComputationContainer();
computeDatasetsForProviderAndUpdateContainer(response, autofillProviderContainer);
+ if (DBG) {
+ Slog.d(TAG, "DBG: computeDatasetsForProviderAndUpdateContainer: "
+ + autofillProviderContainer);
+ }
if (!mService.getMaster().isPccClassificationEnabled()) {
+ if (sVerbose) {
+ Slog.v(TAG, "PCC classification is disabled");
+ }
return createShallowCopy(response, autofillProviderContainer);
}
synchronized (mLock) {
if (mClassificationState.mState != ClassificationState.STATE_RESPONSE
|| mClassificationState.mLastFieldClassificationResponse == null) {
+ if (sVerbose) {
+ Slog.v(TAG, "PCC classification no last response:"
+ + (mClassificationState.mLastFieldClassificationResponse == null)
+ + " ,ineligible state="
+ + (mClassificationState.mState != ClassificationState.STATE_RESPONSE));
+ }
return createShallowCopy(response, autofillProviderContainer);
}
if (!mClassificationState.processResponse()) return response;
@@ -1668,11 +1639,22 @@
boolean preferAutofillProvider = mService.getMaster().preferProviderOverPcc();
boolean shouldUseFallback = mService.getMaster().shouldUsePccFallback();
if (preferAutofillProvider && !shouldUseFallback) {
+ if (sVerbose) {
+ Slog.v(TAG, "preferAutofillProvider but no fallback");
+ }
return createShallowCopy(response, autofillProviderContainer);
}
+ if (DBG) {
+ synchronized (mLock) {
+ Slog.d(TAG, "DBG: ClassificationState: " + mClassificationState);
+ }
+ }
DatasetComputationContainer detectionPccContainer = new DatasetComputationContainer();
computeDatasetsForPccAndUpdateContainer(response, detectionPccContainer);
+ if (DBG) {
+ Slog.d(TAG, "DBG: computeDatasetsForPccAndUpdateContainer: " + detectionPccContainer);
+ }
DatasetComputationContainer resultContainer;
if (preferAutofillProvider) {
@@ -1746,6 +1728,20 @@
// FillResponse.
Set<Dataset> mDatasets = new LinkedHashSet<>();
ArrayMap<AutofillId, Set<Dataset>> mAutofillIdToDatasetMap = new ArrayMap<>();
+
+ public String toString() {
+ final StringBuilder builder = new StringBuilder("DatasetComputationContainer[");
+ if (mAutofillIds != null) {
+ builder.append(", autofillIds=").append(mAutofillIds);
+ }
+ if (mDatasets != null) {
+ builder.append(", mDatasets=").append(mDatasets);
+ }
+ if (mAutofillIdToDatasetMap != null) {
+ builder.append(", mAutofillIdToDatasetMap=").append(mAutofillIdToDatasetMap);
+ }
+ return builder.append(']').toString();
+ }
}
// Adds fallback datasets to the first container.
@@ -1897,7 +1893,6 @@
Dataset dataset = datasets.get(i);
if (dataset.getAutofillDatatypes() == null
|| dataset.getAutofillDatatypes().isEmpty()) continue;
- if (dataset.getFieldIds() != null && dataset.getFieldIds().size() > 0) continue;
ArrayList<AutofillId> fieldIds = new ArrayList<>();
ArrayList<AutofillValue> fieldValues = new ArrayList<>();
@@ -1906,9 +1901,10 @@
ArrayList<InlinePresentation> fieldInlinePresentations = new ArrayList<>();
ArrayList<InlinePresentation> fieldInlineTooltipPresentations = new ArrayList<>();
ArrayList<Dataset.DatasetFieldFilter> fieldFilters = new ArrayList<>();
+ Set<AutofillId> datasetAutofillIds = new ArraySet<>();
for (int j = 0; j < dataset.getAutofillDatatypes().size(); j++) {
- if (dataset.getAutofillDatatypes().get(0) == null) continue;
+ if (dataset.getAutofillDatatypes().get(j) == null) continue;
String hint = dataset.getAutofillDatatypes().get(j);
if (hintsToAutofillIdMap.containsKey(hint)) {
@@ -1917,6 +1913,7 @@
for (AutofillId autofillId : tempIds) {
eligibleAutofillIds.add(autofillId);
+ datasetAutofillIds.add(autofillId);
// For each of the field, copy over values.
fieldIds.add(autofillId);
fieldValues.add(dataset.getFieldValues().get(j));
@@ -1930,37 +1927,6 @@
dataset.getFieldInlineTooltipPresentation(j));
fieldFilters.add(dataset.getFilter(j));
}
-
- Dataset newDataset =
- new Dataset(
- fieldIds,
- fieldValues,
- fieldPresentations,
- fieldDialogPresentations,
- fieldInlinePresentations,
- fieldInlineTooltipPresentations,
- fieldFilters,
- new ArrayList<>(),
- dataset.getFieldContent(),
- null,
- null,
- null,
- null,
- dataset.getId(),
- dataset.getAuthentication());
- eligibleDatasets.add(newDataset);
-
- // Associate this dataset with all the ids that are represented with it.
- Set<Dataset> newDatasets;
- for (AutofillId autofillId : tempIds) {
- if (map.containsKey(autofillId)) {
- newDatasets = map.get(autofillId);
- } else {
- newDatasets = new ArraySet<>();
- }
- newDatasets.add(newDataset);
- map.put(autofillId, newDatasets);
- }
}
// TODO(b/266379948): handle the case:
// groupHintsToAutofillIdMap.containsKey(hint))
@@ -1968,6 +1934,34 @@
// TODO(b/266379948): also handle the case where there could be more types in
// the dataset, provided by the provider, however, they aren't applicable.
}
+ Dataset newDataset =
+ new Dataset(
+ fieldIds,
+ fieldValues,
+ fieldPresentations,
+ fieldDialogPresentations,
+ fieldInlinePresentations,
+ fieldInlineTooltipPresentations,
+ fieldFilters,
+ new ArrayList<>(),
+ dataset.getFieldContent(),
+ null,
+ null,
+ null,
+ null,
+ dataset.getId(),
+ dataset.getAuthentication());
+ eligibleDatasets.add(newDataset);
+ Set<Dataset> newDatasets;
+ for (AutofillId autofillId : datasetAutofillIds) {
+ if (map.containsKey(autofillId)) {
+ newDatasets = map.get(autofillId);
+ } else {
+ newDatasets = new ArraySet<>();
+ }
+ newDatasets.add(newDataset);
+ map.put(autofillId, newDatasets);
+ }
}
container.mAutofillIds = eligibleAutofillIds;
container.mDatasets = eligibleDatasets;
@@ -1975,40 +1969,6 @@
}
}
- @GuardedBy("mLock")
- private void processNullResponseOrFallbackLocked(int requestId, int flags) {
- if (!mSessionFlags.mClientSuggestionsEnabled) {
- processNullResponseLocked(requestId, flags);
- return;
- }
-
- // fallback to the default platform password manager
- mSessionFlags.mClientSuggestionsEnabled = false;
- mLastFillDialogTriggerIds = null;
- // Log the existing FillResponse event.
- mFillResponseEventLogger.logAndEndEvent();
-
- final InlineSuggestionsRequest inlineRequest =
- (mLastInlineSuggestionsRequest != null
- && mLastInlineSuggestionsRequest.first == requestId)
- ? mLastInlineSuggestionsRequest.second : null;
-
- // Start a new FillRequest logger for client suggestion fallback.
- mFillRequestEventLogger.startLogForNewRequest();
- mRequestCount++;
- mFillRequestEventLogger.maybeSetAppPackageUid(uid);
- mFillRequestEventLogger.maybeSetFlags(
- flags & ~FLAG_ENABLED_CLIENT_SUGGESTIONS);
- mFillRequestEventLogger.maybeSetRequestTriggerReason(
- TRIGGER_REASON_NORMAL_TRIGGER);
- mFillRequestEventLogger.maybeSetIsClientSuggestionFallback(true);
-
- mAssistReceiver.newAutofillRequestLocked(inlineRequest);
- requestAssistStructureLocked(requestId,
- flags & ~FLAG_ENABLED_CLIENT_SUGGESTIONS);
- return;
- }
-
// FillServiceCallbacks
@Override
@SuppressWarnings("GuardedBy")
@@ -4205,22 +4165,13 @@
filterText = value.getTextValue().toString();
}
- final CharSequence targetLabel;
- final Drawable targetIcon;
- synchronized (mLock) {
- if (mSessionFlags.mClientSuggestionsEnabled) {
- final ApplicationInfo appInfo = ClientSuggestionsSession.getAppInfo(mComponentName,
- mService.getUserId());
- targetLabel = ClientSuggestionsSession.getAppLabelLocked(
- mService.getMaster().getContext(), appInfo);
- targetIcon = ClientSuggestionsSession.getAppIconLocked(
- mService.getMaster().getContext(), appInfo);
- } else {
- targetLabel = mService.getServiceLabelLocked();
- targetIcon = mService.getServiceIconLocked();
- }
+ final CharSequence serviceLabel;
+ final Drawable serviceIcon;
+ synchronized (this.mService.mLock) {
+ serviceLabel = mService.getServiceLabelLocked();
+ serviceIcon = mService.getServiceIconLocked();
}
- if (targetLabel == null || targetIcon == null) {
+ if (serviceLabel == null || serviceIcon == null) {
wtf(null, "onFillReady(): no service label or icon");
return;
}
@@ -4281,7 +4232,7 @@
getUiForShowing().showFillUi(filledId, response, filterText,
mService.getServicePackageName(), mComponentName,
- targetLabel, targetIcon, this, mContext, id, mCompatMode);
+ serviceLabel, serviceIcon, this, mContext, id, mCompatMode);
synchronized (mLock) {
mPresentationStatsEventLogger.maybeSetCountShown(
@@ -4477,17 +4428,6 @@
return false;
}
- final InlineSuggestionsRequest request = inlineSuggestionsRequest.get();
- if (mSessionFlags.mClientSuggestionsEnabled && !request.isClientSupported()
- || !mSessionFlags.mClientSuggestionsEnabled && !request.isServiceSupported()) {
- if (sDebug) {
- Slog.d(TAG, "Inline suggestions not supported for "
- + (mSessionFlags.mClientSuggestionsEnabled ? "client" : "service")
- + ". Falling back to dropdown.");
- }
- return false;
- }
-
final RemoteInlineSuggestionRenderService remoteRenderService =
mService.getRemoteInlineSuggestionRenderServiceLocked();
if (remoteRenderService == null) {
@@ -4502,8 +4442,8 @@
}
final InlineFillUi.InlineFillUiInfo inlineFillUiInfo =
- new InlineFillUi.InlineFillUiInfo(request, focusedId,
- filterText, remoteRenderService, userId, id);
+ new InlineFillUi.InlineFillUiInfo(inlineSuggestionsRequest.get(), focusedId,
+ filterText, remoteRenderService, userId, id);
InlineFillUi inlineFillUi = InlineFillUi.forAutofill(inlineFillUiInfo, response,
new InlineFillUi.InlineSuggestionUiCallback() {
@Override
@@ -5154,26 +5094,6 @@
}
}
- @GuardedBy("mLock")
- private void onClientFillRequestLocked(int requestId,
- InlineSuggestionsRequest inlineSuggestionsRequest) {
- if (mClientSuggestionsSession == null) {
- mClientSuggestionsSession = new ClientSuggestionsSession(id, mClient, mHandler,
- mComponentName, this);
- }
-
- if (mContexts == null) {
- mContexts = new ArrayList<>(1);
- }
- mContexts.add(new FillContext(requestId, new AssistStructure(), mCurrentViewId));
-
- if (inlineSuggestionsRequest != null && !inlineSuggestionsRequest.isClientSupported()) {
- inlineSuggestionsRequest = null;
- }
-
- mClientSuggestionsSession.onFillRequest(requestId, inlineSuggestionsRequest, mFlags);
- }
-
/**
* The result of checking whether to show the save dialog, when session can be saved.
*
@@ -5447,6 +5367,26 @@
mState = STATE_PENDING_REQUEST;
mPendingFieldClassificationRequest = null;
}
+
+ @GuardedBy("mLock")
+ private boolean shouldTriggerRequest() {
+ return mState == STATE_INITIAL || mState == STATE_INVALIDATED;
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ public String toString() {
+ return "ClassificationState: ["
+ + "state=" + stateToString()
+ + ", mPendingFieldClassificationRequest=" + mPendingFieldClassificationRequest
+ + ", mLastFieldClassificationResponse=" + mLastFieldClassificationResponse
+ + ", mClassificationHintsMap=" + mClassificationHintsMap
+ + ", mClassificationGroupHintsMap=" + mClassificationGroupHintsMap
+ + ", mHintsToAutofillIdMap=" + mHintsToAutofillIdMap
+ + ", mGroupHintsToAutofillIdMap=" + mGroupHintsToAutofillIdMap
+ + "]";
+ }
+
}
@Override
@@ -5971,7 +5911,7 @@
return serviceInfo == null ? Process.INVALID_UID : serviceInfo.applicationInfo.uid;
}
- // DetectionServiceCallbacks
+ // FieldClassificationServiceCallbacks
public void onClassificationRequestSuccess(@Nullable FieldClassificationResponse response) {
mClassificationState.updateResponseReceived(response);
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index a3a0674..c8db662a 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -108,6 +108,9 @@
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 +120,7 @@
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 +204,8 @@
private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners =
new RemoteCallbackList<>();
+ private CrossDeviceSyncController mCrossDeviceSyncController;
+
public CompanionDeviceManagerService(Context context) {
super(context);
@@ -239,6 +245,8 @@
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();
@@ -320,12 +328,14 @@
// 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());
+ mDevicePresenceMonitor.getPendingConnectedDevices().get(userId);
+ if (blueToothDevices != null) {
+ 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());
+ }
}
}
}
@@ -1369,6 +1379,39 @@
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 3649240..3b108e6 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/CallMetadataSyncConnectionService.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
index 6f99d86..e436e93 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
@@ -128,9 +128,9 @@
private static final class CallMetadataSyncConnectionIdentifier {
private final int mAssociationId;
- private final long mCallId;
+ private final String mCallId;
- CallMetadataSyncConnectionIdentifier(int associationId, long callId) {
+ CallMetadataSyncConnectionIdentifier(int associationId, String callId) {
mAssociationId = associationId;
mCallId = callId;
}
@@ -139,7 +139,7 @@
return mAssociationId;
}
- public long getCallId() {
+ public String getCallId() {
return mCallId;
}
@@ -161,9 +161,7 @@
private abstract static class CallMetadataSyncConnectionCallback {
- abstract void sendCallAction(int associationId, long callId, int action);
-
- abstract void sendStateChange(int associationId, long callId, int newState);
+ abstract void sendCallAction(int associationId, String callId, int action);
}
private static class CallMetadataSyncConnection extends Connection {
@@ -184,7 +182,7 @@
mCallback = callback;
}
- public long getCallId() {
+ public String getCallId() {
return mCall.getId();
}
@@ -205,22 +203,22 @@
}
final Bundle extras = new Bundle();
- extras.putLong(CrossDeviceCall.EXTRA_CALL_ID, mCall.getId());
+ extras.putString(CrossDeviceCall.EXTRA_CALL_ID, mCall.getId());
putExtras(extras);
int capabilities = getConnectionCapabilities();
- if (mCall.hasControl(android.companion.Telecom.Call.PUT_ON_HOLD)) {
+ if (mCall.hasControl(android.companion.Telecom.PUT_ON_HOLD)) {
capabilities |= CAPABILITY_HOLD;
} else {
capabilities &= ~CAPABILITY_HOLD;
}
- if (mCall.hasControl(android.companion.Telecom.Call.MUTE)) {
+ if (mCall.hasControl(android.companion.Telecom.MUTE)) {
capabilities |= CAPABILITY_MUTE;
} else {
capabilities &= ~CAPABILITY_MUTE;
}
mAudioManager.setMicrophoneMute(
- mCall.hasControl(android.companion.Telecom.Call.UNMUTE));
+ mCall.hasControl(android.companion.Telecom.UNMUTE));
if (capabilities != getConnectionCapabilities()) {
setConnectionCapabilities(capabilities);
}
@@ -248,8 +246,8 @@
int capabilities = getConnectionCapabilities();
final boolean hasHoldControl = mCall.hasControl(
- android.companion.Telecom.Call.PUT_ON_HOLD)
- || mCall.hasControl(android.companion.Telecom.Call.TAKE_OFF_HOLD);
+ android.companion.Telecom.PUT_ON_HOLD)
+ || mCall.hasControl(android.companion.Telecom.TAKE_OFF_HOLD);
if (hasHoldControl != ((getConnectionCapabilities() & CAPABILITY_HOLD)
== CAPABILITY_HOLD)) {
if (hasHoldControl) {
@@ -258,7 +256,7 @@
capabilities &= ~CAPABILITY_HOLD;
}
}
- final boolean hasMuteControl = mCall.hasControl(android.companion.Telecom.Call.MUTE);
+ final boolean hasMuteControl = mCall.hasControl(android.companion.Telecom.MUTE);
if (hasMuteControl != ((getConnectionCapabilities() & CAPABILITY_MUTE)
== CAPABILITY_MUTE)) {
if (hasMuteControl) {
@@ -268,7 +266,7 @@
}
}
mAudioManager.setMicrophoneMute(
- mCall.hasControl(android.companion.Telecom.Call.UNMUTE));
+ mCall.hasControl(android.companion.Telecom.UNMUTE));
if (capabilities != getConnectionCapabilities()) {
setConnectionCapabilities(capabilities);
}
@@ -276,12 +274,12 @@
@Override
public void onAnswer(int videoState) {
- sendCallAction(android.companion.Telecom.Call.ACCEPT);
+ sendCallAction(android.companion.Telecom.ACCEPT);
}
@Override
public void onReject() {
- sendCallAction(android.companion.Telecom.Call.REJECT);
+ sendCallAction(android.companion.Telecom.REJECT);
}
@Override
@@ -296,33 +294,28 @@
@Override
public void onSilence() {
- sendCallAction(android.companion.Telecom.Call.SILENCE);
+ sendCallAction(android.companion.Telecom.SILENCE);
}
@Override
public void onHold() {
- sendCallAction(android.companion.Telecom.Call.PUT_ON_HOLD);
+ sendCallAction(android.companion.Telecom.PUT_ON_HOLD);
}
@Override
public void onUnhold() {
- sendCallAction(android.companion.Telecom.Call.TAKE_OFF_HOLD);
+ sendCallAction(android.companion.Telecom.TAKE_OFF_HOLD);
}
@Override
public void onMuteStateChanged(boolean isMuted) {
- sendCallAction(isMuted ? android.companion.Telecom.Call.MUTE
- : android.companion.Telecom.Call.UNMUTE);
+ sendCallAction(isMuted ? android.companion.Telecom.MUTE
+ : android.companion.Telecom.UNMUTE);
}
@Override
public void onDisconnect() {
- sendCallAction(android.companion.Telecom.Call.END);
- }
-
- @Override
- public void onStateChanged(int state) {
- mCallback.sendStateChange(mAssociationId, mCall.getId(), state);
+ sendCallAction(android.companion.Telecom.END);
}
private void sendCallAction(int action) {
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java
index 1e4bb9a..5b0c745 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java
@@ -33,14 +33,14 @@
/** A read-only snapshot of an {@link ContextSyncMessage}. */
class CallMetadataSyncData {
- final Map<Long, CallMetadataSyncData.Call> mCalls = new HashMap<>();
+ final Map<String, CallMetadataSyncData.Call> mCalls = new HashMap<>();
final List<CallMetadataSyncData.Call> mRequests = new ArrayList<>();
public void addCall(CallMetadataSyncData.Call call) {
mCalls.put(call.getId(), call);
}
- public boolean hasCall(long id) {
+ public boolean hasCall(String id) {
return mCalls.containsKey(id);
}
@@ -57,7 +57,7 @@
}
public static class Call implements Parcelable {
- private long mId;
+ private String mId;
private String mCallerId;
private byte[] mAppIcon;
private String mAppName;
@@ -67,7 +67,7 @@
public static Call fromParcel(Parcel parcel) {
final Call call = new Call();
- call.setId(parcel.readLong());
+ call.setId(parcel.readString());
call.setCallerId(parcel.readString());
call.setAppIcon(parcel.readBlob());
call.setAppName(parcel.readString());
@@ -82,7 +82,7 @@
@Override
public void writeToParcel(Parcel parcel, int parcelableFlags) {
- parcel.writeLong(mId);
+ parcel.writeString(mId);
parcel.writeString(mCallerId);
parcel.writeBlob(mAppIcon);
parcel.writeString(mAppName);
@@ -94,7 +94,7 @@
}
}
- void setId(long id) {
+ void setId(String id) {
mId = id;
}
@@ -122,7 +122,7 @@
mControls.add(control);
}
- long getId() {
+ String getId() {
return mId;
}
@@ -157,7 +157,7 @@
@Override
public boolean equals(Object other) {
if (other instanceof CallMetadataSyncData.Call) {
- return ((Call) other).getId() == getId();
+ return mId != null && mId.equals(((Call) other).getId());
}
return false;
}
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 ae4766a..0c23730 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,101 +40,142 @@
*/
public class CallMetadataSyncInCallService extends InCallService {
- private static final long NOT_VALID = -1L;
+ private static final String TAG = "CallMetadataIcs";
+
+ 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);
- }
- };
- 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();
- }
- 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:
- }
- }
-
- @Override
- void requestCrossDeviceSync(int userId) {
- }
-
- @Override
- void updateStatus(int userId, boolean shouldSyncCallMetadata) {
- if (userId == getUserId()) {
- mShouldSync = shouldSyncCallMetadata;
- if (shouldSyncCallMetadata) {
- initializeCalls();
+ if (mNumberOfActiveSyncAssociations > 0) {
+ final CrossDeviceCall crossDeviceCall = mCurrentCalls.get(call);
+ if (crossDeviceCall != null) {
+ crossDeviceCall.updateCallDetails(details);
+ sync(getUserId());
} else {
- mCurrentCalls.clear();
+ Slog.w(TAG, "Could not update details for nonexistent call");
}
}
}
};
+ 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() != null) {
+ // The call is already assigned an id; treat as control invocations.
+ for (int control : call.getControls()) {
+ processCallControlAction(call.getId(), control);
+ }
+ }
+ iterator.remove();
+ }
+ }
+
+ private void processCallControlAction(String crossDeviceCallId,
+ int callControlAction) {
+ final CrossDeviceCall crossDeviceCall = getCallForId(crossDeviceCallId,
+ mCurrentCalls.values());
+ switch (callControlAction) {
+ case android.companion.Telecom.ACCEPT:
+ if (crossDeviceCall != null) {
+ crossDeviceCall.doAccept();
+ }
+ break;
+ case android.companion.Telecom.REJECT:
+ if (crossDeviceCall != null) {
+ crossDeviceCall.doReject();
+ }
+ break;
+ case android.companion.Telecom.SILENCE:
+ doSilence();
+ break;
+ case android.companion.Telecom.MUTE:
+ doMute();
+ break;
+ case android.companion.Telecom.UNMUTE:
+ doUnmute();
+ break;
+ case android.companion.Telecom.END:
+ if (crossDeviceCall != null) {
+ crossDeviceCall.doEnd();
+ }
+ break;
+ case android.companion.Telecom.PUT_ON_HOLD:
+ if (crossDeviceCall != null) {
+ crossDeviceCall.doPutOnHold();
+ }
+ break;
+ case android.companion.Telecom.TAKE_OFF_HOLD:
+ if (crossDeviceCall != null) {
+ crossDeviceCall.doTakeOffHold();
+ }
+ break;
+ default:
+ }
+ }
+
+ @Override
+ void requestCrossDeviceSync(AssociationInfo associationInfo) {
+ if (associationInfo.getUserId() == getUserId()) {
+ sync(associationInfo);
+ }
+ }
+
+ @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());
}
}
@Nullable
@VisibleForTesting
- CrossDeviceCall getCallForId(long crossDeviceCallId, Collection<CrossDeviceCall> calls) {
- if (crossDeviceCallId == NOT_VALID) {
+ CrossDeviceCall getCallForId(String crossDeviceCallId, Collection<CrossDeviceCall> calls) {
+ if (crossDeviceCallId == null) {
return null;
}
for (CrossDeviceCall crossDeviceCall : calls) {
- if (crossDeviceCall.getId() == crossDeviceCallId) {
+ if (crossDeviceCallId.equals(crossDeviceCall.getId())) {
return crossDeviceCall;
}
}
@@ -139,33 +185,39 @@
@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 +235,12 @@
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/CrossDeviceCall.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
index ac981d4..168068e 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
@@ -23,7 +23,6 @@
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
-import android.os.Bundle;
import android.telecom.Call;
import android.telecom.CallAudioState;
import android.telecom.VideoProfile;
@@ -34,7 +33,7 @@
import java.io.ByteArrayOutputStream;
import java.util.HashSet;
import java.util.Set;
-import java.util.concurrent.atomic.AtomicLong;
+import java.util.UUID;
/** Data holder for a telecom call and additional metadata. */
public class CrossDeviceCall {
@@ -45,9 +44,7 @@
"com.android.companion.datatransfer.contextsync.extra.CALL_ID";
private static final int APP_ICON_BITMAP_DIMENSION = 256;
- private static final AtomicLong sNextId = new AtomicLong(1);
-
- private final long mId;
+ private final String mId;
private Call mCall;
@VisibleForTesting boolean mIsEnterprise;
@VisibleForTesting boolean mIsOtt;
@@ -64,14 +61,14 @@
CallAudioState callAudioState) {
this(packageManager, call.getDetails(), callAudioState);
mCall = call;
- final Bundle extras = new Bundle();
- extras.putLong(EXTRA_CALL_ID, mId);
- call.putExtras(extras);
+ call.putExtra(EXTRA_CALL_ID, mId);
}
CrossDeviceCall(PackageManager packageManager, Call.Details callDetails,
CallAudioState callAudioState) {
- mId = sNextId.getAndIncrement();
+ final String predefinedId = callDetails.getIntentExtras() != null
+ ? callDetails.getIntentExtras().getString(EXTRA_CALL_ID) : null;
+ mId = predefinedId != null ? predefinedId : UUID.randomUUID().toString();
mCallingAppPackageName =
callDetails.getAccountHandle().getComponentName().getPackageName();
mIsOtt = (callDetails.getCallCapabilities() & Call.Details.PROPERTY_SELF_MANAGED)
@@ -145,7 +142,7 @@
if (mStatus == android.companion.Telecom.Call.RINGING) {
mStatus = android.companion.Telecom.Call.RINGING_SILENCED;
}
- mControls.remove(android.companion.Telecom.Call.SILENCE);
+ mControls.remove(android.companion.Telecom.SILENCE);
}
@VisibleForTesting
@@ -156,26 +153,26 @@
mControls.clear();
if (mStatus == android.companion.Telecom.Call.RINGING
|| mStatus == android.companion.Telecom.Call.RINGING_SILENCED) {
- mControls.add(android.companion.Telecom.Call.ACCEPT);
- mControls.add(android.companion.Telecom.Call.REJECT);
+ mControls.add(android.companion.Telecom.ACCEPT);
+ mControls.add(android.companion.Telecom.REJECT);
if (mStatus == android.companion.Telecom.Call.RINGING) {
- mControls.add(android.companion.Telecom.Call.SILENCE);
+ mControls.add(android.companion.Telecom.SILENCE);
}
}
if (mStatus == android.companion.Telecom.Call.ONGOING
|| mStatus == android.companion.Telecom.Call.ON_HOLD) {
- mControls.add(android.companion.Telecom.Call.END);
+ mControls.add(android.companion.Telecom.END);
if (callDetails.can(Call.Details.CAPABILITY_HOLD)) {
mControls.add(
mStatus == android.companion.Telecom.Call.ON_HOLD
- ? android.companion.Telecom.Call.TAKE_OFF_HOLD
- : android.companion.Telecom.Call.PUT_ON_HOLD);
+ ? android.companion.Telecom.TAKE_OFF_HOLD
+ : android.companion.Telecom.PUT_ON_HOLD);
}
}
if (mStatus == android.companion.Telecom.Call.ONGOING && callDetails.can(
Call.Details.CAPABILITY_MUTE)) {
- mControls.add(mIsMuted ? android.companion.Telecom.Call.UNMUTE
- : android.companion.Telecom.Call.MUTE);
+ mControls.add(mIsMuted ? android.companion.Telecom.UNMUTE
+ : android.companion.Telecom.MUTE);
}
}
@@ -212,7 +209,7 @@
}
}
- public long getId() {
+ public String getId() {
return mId;
}
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 3d8fb7a..e5ab963 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 java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.companion.CompanionDeviceConfig;
+import com.android.server.companion.transport.CompanionTransportManager;
+
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 @@
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());
}
}
}
- /**
- * Opens channels to newly associated devices, and closes channels to newly disassociated
- * devices.
- *
- * TODO(b/265466098): this needs to be limited to just connected devices
- */
- 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);
- }
- }
- // Open channels to newly-connected devices.
- for (AssociationInfo newAssociationInfo : newAssociationInfoList) {
- if (!existingAssociationInfoList.contains(newAssociationInfo) && !mBlocklist.contains(
- newAssociationInfo.getId())) {
- openChannel(newAssociationInfo);
- }
- }
- mUserIdToAssociationInfo.put(userId, newAssociationInfoList);
- }
-
private boolean isAdminBlocked(int userId) {
return mContext.getSystemService(DevicePolicyManager.class)
.getBluetoothContactSharingDisabled(UserHandle.of(userId));
}
- /** 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);
- }
- }
- 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
- }
-
/**
* 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.
*/
- 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);
- }
- pos.end(callsToken);
+ 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());
}
- pos.end(telecomToken);
- pos.flush();
}
+ if (associationIds.isEmpty()) {
+ Slog.w(TAG, "No eligible devices to sync to");
+ return;
+ }
+
+ mCompanionTransportManager.sendMessage(MESSAGE_REQUEST_CONTEXT_SYNC,
+ createCallUpdateMessage(calls, userId),
+ associationIds.stream().mapToInt(Integer::intValue).toArray());
}
/**
- * Callback to be implemented by CompanionDeviceManagerService.
+ * 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 interface Callback {
- /**
- * Create a secure channel to send messages.
- */
- void createSecureChannel(int associationId, InputStream input, OutputStream output);
+ public void syncToSingleDevice(AssociationInfo associationInfo,
+ Collection<CrossDeviceCall> calls) {
+ if (isAssociationBlocked(associationInfo.getId())) {
+ Slog.e(TAG, "Cannot sync to requested device; connection is blocked");
+ return;
+ }
- /**
- * Close the secure channel created previously.
- */
- void closeSecureChannel(int associationId);
+ mCompanionTransportManager.sendMessage(MESSAGE_REQUEST_CONTEXT_SYNC,
+ createCallUpdateMessage(calls, associationInfo.getUserId()),
+ new int[]{associationInfo.getId()});
+ }
+
+ /**
+ * Sync data to associated devices.
+ *
+ * @param associationId The association whose data should be synced.
+ * @param message The message to sync.
+ */
+ 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));
+ }
+ }
+ } catch (IOException | ProtoParseException e) {
+ throw new RuntimeException(e);
+ }
+ return callMetadataSyncData;
+ }
+
+ @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.readString(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(String 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
similarity index 67%
rename from services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncCallback.java
rename to services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java
index 7c339d2..31e10a8 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 82d0325..a5410e4 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -30,6 +30,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
+import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -60,11 +61,13 @@
/** A set of ALL connected BT device (not only companion.) */
private final @NonNull Map<MacAddress, BluetoothDevice> mAllConnectedDevices = new HashMap<>();
-
- @GuardedBy("mPendingReportConnectedDevices")
+ /**
+ * A structure hold the connected BT devices that are pending to be reported to the companion
+ * app when the user unlocks the local device per userId.
+ */
+ @GuardedBy("mPendingConnectedDevices")
@NonNull
- final SparseArray<Set<BluetoothDevice>> mPendingReportConnectedDevices =
- new SparseArray<>();
+ final SparseArray<Set<BluetoothDevice>> mPendingConnectedDevices = new SparseArray<>();
BluetoothCompanionDeviceConnectionListener(UserManager userManager,
@NonNull AssociationStore associationStore, @NonNull Callback callback) {
@@ -98,19 +101,13 @@
}
// 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 "
+ Slog.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);
- }
-
+ synchronized (mPendingConnectedDevices) {
+ Set<BluetoothDevice> bluetoothDevices = mPendingConnectedDevices.get(
+ userId, new HashSet<>());
bluetoothDevices.add(device);
+ mPendingConnectedDevices.put(userId, bluetoothDevices);
}
} else {
@@ -144,8 +141,8 @@
// 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);
+ synchronized (mPendingConnectedDevices) {
+ Set<BluetoothDevice> bluetoothDevices = mPendingConnectedDevices.get(userId);
if (bluetoothDevices != null) {
bluetoothDevices.remove(device);
}
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 6c56500..f6e9415 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -303,9 +303,9 @@
/**
* Return a set of devices that pending to report connectivity
*/
- public SparseArray<Set<BluetoothDevice>> getPendingReportConnectedDevices() {
- synchronized (mBtConnectionListener.mPendingReportConnectedDevices) {
- return mBtConnectionListener.mPendingReportConnectedDevices;
+ public SparseArray<Set<BluetoothDevice>> getPendingConnectedDevices() {
+ synchronized (mBtConnectionListener.mPendingConnectedDevices) {
+ return mBtConnectionListener.mPendingConnectedDevices;
}
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 678d582..ca50af8 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -52,6 +52,7 @@
import static android.os.PowerExemptionManager.REASON_ACTIVE_DEVICE_ADMIN;
import static android.os.PowerExemptionManager.REASON_ACTIVITY_STARTER;
import static android.os.PowerExemptionManager.REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD;
+import static android.os.PowerExemptionManager.REASON_ALARM_MANAGER_ALARM_CLOCK;
import static android.os.PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE;
import static android.os.PowerExemptionManager.REASON_BACKGROUND_ACTIVITY_PERMISSION;
import static android.os.PowerExemptionManager.REASON_BACKGROUND_FGS_PERMISSION;
@@ -458,11 +459,12 @@
public void updateBackgroundRestrictedForUidPackage(int uid, String packageName,
boolean restricted) {
synchronized (mAm) {
- if (!isForegroundServiceAllowedInBackgroundRestricted(uid, packageName)) {
- stopAllForegroundServicesLocked(uid, packageName);
- }
mAm.mProcessList.updateBackgroundRestrictedForUidPackageLocked(
uid, packageName, restricted);
+ if (!isForegroundServiceAllowedInBackgroundRestricted(uid, packageName)
+ && !isTempAllowedByAlarmClock(uid)) {
+ stopAllForegroundServicesLocked(uid, packageName);
+ }
}
}
}
@@ -475,7 +477,11 @@
final ServiceRecord r = smap.mServicesByInstanceName.valueAt(i);
if (uid == r.serviceInfo.applicationInfo.uid
|| packageName.equals(r.serviceInfo.packageName)) {
- if (r.isForeground) {
+ // If the FGS is started by temp allowlist of alarm-clock
+ // (REASON_ALARM_MANAGER_ALARM_CLOCK), allow it to continue and do not stop it,
+ // even the app is background-restricted.
+ if (r.isForeground
+ && r.mAllowStartForegroundAtEntering != REASON_ALARM_MANAGER_ALARM_CLOCK) {
toStop.add(r);
}
}
@@ -762,6 +768,15 @@
}
}
+ 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 +833,9 @@
}
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
@@ -860,7 +878,9 @@
// start analogously to the legacy-app forced-restrictions case, regardless
// of its target SDK version.
boolean forcedStandby = false;
- if (bgLaunch && appRestrictedAnyInBackground(appUid, appPackageName)) {
+ if (bgLaunch
+ && appRestrictedAnyInBackground(appUid, appPackageName)
+ && !isTempAllowedByAlarmClock(appUid)) {
if (DEBUG_FOREGROUND_SERVICE) {
Slog.d(TAG, "Forcing bg-only service start only for " + r.shortInstanceName
+ " : bgLaunch=" + bgLaunch + " callerFg=" + callerFg);
@@ -1407,6 +1427,7 @@
}
private void stopServiceLocked(ServiceRecord service, boolean enqueueOomAdj) {
+ traceInstant("stopService(): ", service);
try {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "stopServiceLocked()");
if (service.delayed) {
@@ -1918,6 +1939,20 @@
&& isForegroundServiceAllowedInBackgroundRestricted(app);
}
+ /*
+ * If the FGS start is temp allowlisted by alarm-clock(REASON_ALARM_MANAGER_ALARM_CLOCK), it is
+ * allowed even the app is background-restricted.
+ */
+ private boolean isTempAllowedByAlarmClock(int uid) {
+ final ActivityManagerService.FgsTempAllowListItem item =
+ mAm.isAllowlistedForFgsStartLOSP(uid);
+ if (item != null) {
+ return item.mReasonCode == REASON_ALARM_MANAGER_ALARM_CLOCK;
+ } else {
+ return false;
+ }
+ }
+
void logFgsApiBeginLocked(int uid, int pid, int apiType) {
synchronized (mFGSLogger) {
mFGSLogger.logForegroundServiceApiEventBegin(uid, pid, apiType, "");
@@ -1946,6 +1981,7 @@
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()) {
@@ -2050,7 +2086,8 @@
// Apps that are TOP or effectively similar may call startForeground() on
// their services even if they are restricted from doing that while in bg.
if (!ignoreForeground
- && !isForegroundServiceAllowedInBackgroundRestricted(r.app)) {
+ && !isForegroundServiceAllowedInBackgroundRestricted(r.app)
+ && !isTempAllowedByAlarmClock(r.app.uid)) {
Slog.w(TAG,
"Service.startForeground() not allowed due to bg restriction: service "
+ r.shortInstanceName);
@@ -2484,6 +2521,7 @@
}
} else {
if (r.isForeground) {
+ traceInstant("stopForeground(): ", r);
final ServiceMap smap = getServiceMapLocked(r.userId);
if (smap != null) {
decActiveForegroundAppLocked(smap, r);
@@ -3311,6 +3349,7 @@
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 +3395,11 @@
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 +3450,7 @@
}
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 +3481,9 @@
} 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
@@ -8196,7 +8240,11 @@
: ForegroundServiceDelegationOptions.DELEGATION_SERVICE_DEFAULT,
0 /* api_sate */,
null /* api_type */,
- null /* api_timestamp */);
+ null /* api_timestamp */,
+ mAm.getUidStateLocked(r.appInfo.uid),
+ mAm.getUidProcessCapabilityLocked(r.appInfo.uid),
+ mAm.getUidStateLocked(r.mRecentCallingUid),
+ mAm.getUidProcessCapabilityLocked(r.mRecentCallingUid));
int event = 0;
if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a54e8e9..48898a6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5190,7 +5190,10 @@
throw new IllegalArgumentException(
"Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
}
- if (PendingIntent.isNewMutableDisallowedImplicitPendingIntent(flags, intent)) {
+ boolean isActivityResultType =
+ type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT;
+ if (PendingIntent.isNewMutableDisallowedImplicitPendingIntent(flags, intent,
+ isActivityResultType)) {
boolean isChangeEnabled = CompatChanges.isChangeEnabled(
PendingIntent.BLOCK_MUTABLE_IMPLICIT_PENDING_INTENT,
owningUid);
@@ -6915,7 +6918,7 @@
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 +6947,16 @@
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);
@@ -14234,7 +14247,8 @@
throw new SecurityException(
"Intent " + intent.getAction() + " may not be broadcast from an SDK sandbox"
+ " uid. Given caller package " + callerPackage + " (pid=" + callingPid
- + ", uid=" + callingUid + ")");
+ + ", realCallingUid=" + realCallingUid + ", callingUid= " + callingUid
+ + ")");
}
}
@@ -16047,6 +16061,8 @@
final int procState = uidRec != null
? uidRec.getSetProcState() : PROCESS_STATE_NONEXISTENT;
+ final int procAdj = uidRec != null
+ ? uidRec.getMinProcAdj() : ProcessList.INVALID_ADJ;
final long procStateSeq = uidRec != null ? uidRec.curProcStateSeq : 0;
final int capability = uidRec != null ? uidRec.getSetCapability() : 0;
final boolean ephemeral = uidRec != null ? uidRec.isEphemeral() : isEphemeralLocked(uid);
@@ -16062,7 +16078,7 @@
}
final int enqueuedChange = mUidObserverController.enqueueUidChange(
uidRec == null ? null : uidRec.pendingChange,
- uid, change, procState, procStateSeq, capability, ephemeral);
+ uid, change, procState, procAdj, procStateSeq, capability, ephemeral);
if (uidRec != null) {
uidRec.setLastReportedChange(enqueuedChange);
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 5a4d315..4a69f90 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -67,7 +67,6 @@
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
-import android.os.UserManager;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.IndentingPrintWriter;
@@ -78,6 +77,7 @@
import com.android.internal.os.TimeoutRecord;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
+import com.android.server.pm.UserJourneyLogger;
import com.android.server.pm.UserManagerInternal;
import dalvik.annotation.optimization.NeverCompile;
@@ -1518,7 +1518,7 @@
final UserInfo userInfo =
(umInternal != null) ? umInternal.getUserInfo(r.userId) : null;
if (userInfo != null) {
- userType = UserManager.getUserTypeForStatsd(userInfo.userType);
+ userType = UserJourneyLogger.getUserTypeForStatsd(userInfo.userType);
}
Slog.i(TAG_BROADCAST,
"BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED action:"
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 1426cfd..3e82d55 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -1015,6 +1015,12 @@
private static native String getFreezerCheckPath();
/**
+ * Check if task_profiles.json includes valid freezer profiles and actions
+ * @return false if there are invalid profiles or actions
+ */
+ private static native boolean isFreezerProfileValid();
+
+ /**
* Determines whether the freezer is supported by this system
*/
public static boolean isFreezerSupported() {
@@ -1031,16 +1037,19 @@
// Also check freezer binder ioctl
Slog.d(TAG_AM, "Checking binder freezer ioctl");
getBinderFreezeInfo(Process.myPid());
- supported = true;
+
+ // Check if task_profiles.json contains invalid profiles
+ Slog.d(TAG_AM, "Checking freezer profiles");
+ supported = isFreezerProfileValid();
} else {
- Slog.e(TAG_AM, "unexpected value in cgroup.freeze");
+ Slog.e(TAG_AM, "Unexpected value in cgroup.freeze");
}
} catch (java.io.FileNotFoundException e) {
- Slog.w(TAG_AM, "cgroup.freeze not present");
+ Slog.w(TAG_AM, "File cgroup.freeze not present");
} catch (RuntimeException e) {
- Slog.w(TAG_AM, "unable to read freezer info");
+ Slog.w(TAG_AM, "Unable to read freezer info");
} catch (Exception e) {
- Slog.w(TAG_AM, "unable to read cgroup.freeze: " + e.toString());
+ Slog.w(TAG_AM, "Unable to read cgroup.freeze: " + e.toString());
}
if (fr != null) {
@@ -2100,9 +2109,12 @@
final boolean frozen;
final ProcessCachedOptimizerRecord opt = proc.mOptRecord;
- opt.setPendingFreeze(false);
-
synchronized (mProcLock) {
+ // someone has canceled this freeze
+ if (!opt.isPendingFreeze()) {
+ return;
+ }
+ opt.setPendingFreeze(false);
pid = proc.getPid();
if (mFreezerOverride) {
@@ -2148,7 +2160,6 @@
try {
traceAppFreeze(proc.processName, pid, -1);
Process.setProcessFrozen(pid, proc.uid, true);
-
opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
opt.setFrozen(true);
opt.setHasCollectedFrozenPSS(false);
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index 8f84b08..7908907 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -28,12 +28,13 @@
import static android.os.Process.INVALID_UID;
import android.annotation.IntDef;
+import android.app.ActivityManager;
import android.app.ActivityManager.ForegroundServiceApiType;
import android.app.ForegroundServiceDelegationOptions;
import android.content.ComponentName;
import android.content.pm.ServiceInfo;
import android.util.ArrayMap;
-import android.util.Log;
+import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
@@ -191,13 +192,20 @@
final ArrayList<Integer> apiTypes = convertFgsTypeToApiTypes(record.foregroundServiceType);
final UidState uidState = mUids.get(uid);
if (uidState == null) {
- Log.e(TAG, "FGS stop call being logged with no start call for UID " + uid);
+ Slog.wtfStack(TAG, "FGS stop call being logged with no start call for UID for UID "
+ + uid
+ + " in package " + record.packageName);
return;
}
final ArrayList<Integer> apisFound = new ArrayList<>();
final ArrayList<Long> timestampsFound = new ArrayList<>();
for (int i = 0, size = apiTypes.size(); i < size; i++) {
- int apiType = apiTypes.get(i);
+ final int apiType = apiTypes.get(i);
+ if (!uidState.mOpenWithFgsCount.contains(apiType)) {
+ Slog.wtfStack(TAG, "Logger should be tracking FGS types correctly for UID " + uid
+ + " in package " + record.packageName);
+ continue;
+ }
// retrieve the eligible closed call
// we only want to log if this is the only
// open in flight call. If there are other calls,
@@ -214,7 +222,8 @@
final ArrayMap<ComponentName, ServiceRecord> runningFgsOfType =
uidState.mRunningFgs.get(apiType);
if (runningFgsOfType == null) {
- Log.w(TAG, "Could not find appropriate running FGS for FGS stop");
+ Slog.w(TAG, "Could not find appropriate running FGS for FGS stop for UID " + uid
+ + " in package " + record.packageName);
continue;
}
@@ -321,7 +330,7 @@
// it's not related to any FGS
UidState uidState = mUids.get(uid);
if (uidState == null) {
- Log.w(TAG, "API event end called before start!");
+ Slog.w(TAG, "API event end called before start!");
return -1;
}
if (uidState.mOpenWithFgsCount.contains(apiType)) {
@@ -466,7 +475,11 @@
: ForegroundServiceDelegationOptions.DELEGATION_SERVICE_DEFAULT,
apiState,
apiType,
- timestamp);
+ timestamp,
+ ActivityManager.PROCESS_STATE_UNKNOWN,
+ ActivityManager.PROCESS_CAPABILITY_NONE,
+ ActivityManager.PROCESS_STATE_UNKNOWN,
+ ActivityManager.PROCESS_CAPABILITY_NONE);
}
/**
@@ -500,7 +513,11 @@
0,
apiState,
apiType,
- timestamp);
+ timestamp,
+ ActivityManager.PROCESS_STATE_UNKNOWN,
+ ActivityManager.PROCESS_CAPABILITY_NONE,
+ ActivityManager.PROCESS_STATE_UNKNOWN,
+ ActivityManager.PROCESS_CAPABILITY_NONE);
}
/**
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 438a08c..0417b8c 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -644,6 +644,11 @@
}
}
+ @GuardedBy({"mService", "mProcLock"})
+ int getSetAdj() {
+ return mState.getSetAdj();
+ }
+
@GuardedBy(anyOf = {"mService", "mProcLock"})
IApplicationThread getThread() {
return mThread;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 78aafeb..6551db9 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -643,6 +643,7 @@
pw.print(prefix); pw.print("lastUntrustedSetFgsRestrictionAllowedTime=");
TimeUtils.formatDuration(mLastUntrustedSetFgsRestrictionAllowedTime, now, pw);
+ pw.println();
if (delayed) {
pw.print(prefix); pw.print("delayed="); pw.println(delayed);
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
index 51cb987..790cc7b 100644
--- a/services/core/java/com/android/server/am/UidObserverController.java
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -96,7 +96,7 @@
}
int enqueueUidChange(@Nullable ChangeRecord currentRecord, int uid, int change, int procState,
- long procStateSeq, int capability, boolean ephemeral) {
+ int procAdj, long procStateSeq, int capability, boolean ephemeral) {
synchronized (mLock) {
if (mPendingUidChanges.size() == 0) {
if (DEBUG_UID_OBSERVERS) {
@@ -117,6 +117,7 @@
changeRecord.uid = uid;
changeRecord.change = change;
changeRecord.procState = procState;
+ changeRecord.procAdj = procAdj;
changeRecord.procStateSeq = procStateSeq;
changeRecord.capability = capability;
changeRecord.ephemeral = ephemeral;
@@ -344,7 +345,7 @@
}
if ((reg.mWhich & ActivityManager.UID_OBSERVER_PROC_OOM_ADJ) != 0
&& (change & UidRecord.CHANGE_PROCADJ) != 0) {
- observer.onUidProcAdjChanged(item.uid);
+ observer.onUidProcAdjChanged(item.uid, item.procAdj);
}
}
final int duration = (int) (SystemClock.uptimeMillis() - start);
@@ -426,6 +427,7 @@
public int uid;
public int change;
public int procState;
+ public int procAdj;
public int capability;
public boolean ephemeral;
public long procStateSeq;
@@ -435,6 +437,7 @@
changeRecord.uid = uid;
changeRecord.change = change;
changeRecord.procState = procState;
+ changeRecord.procAdj = procAdj;
changeRecord.capability = capability;
changeRecord.ephemeral = ephemeral;
changeRecord.procStateSeq = procStateSeq;
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index e39ac2b..993088e 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -51,6 +51,12 @@
private boolean mProcAdjChanged;
@CompositeRWLock({"mService", "mProcLock"})
+ private int mCurAdj;
+
+ @CompositeRWLock({"mService", "mProcLock"})
+ private int mSetAdj;
+
+ @CompositeRWLock({"mService", "mProcLock"})
private int mCurCapability;
@CompositeRWLock({"mService", "mProcLock"})
@@ -201,12 +207,24 @@
mProcAdjChanged = false;
}
- @GuardedBy({"mService", "mProcLock"})
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
boolean getProcAdjChanged() {
return mProcAdjChanged;
}
@GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getMinProcAdj() {
+ int minAdj = ProcessList.UNKNOWN_ADJ;
+ for (int i = mProcRecords.size() - 1; i >= 0; i--) {
+ int adj = mProcRecords.valueAt(i).getSetAdj();
+ if (adj < minAdj) {
+ minAdj = adj;
+ }
+ }
+ return minAdj;
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
int getCurCapability() {
return mCurCapability;
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index a181402..334c145 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -46,13 +46,24 @@
import static com.android.server.am.UserState.STATE_RUNNING_LOCKED;
import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKED;
import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKING;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_ABORTED;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_INVALID_SESSION_ID;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_BEGIN;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_FINISH;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_NONE;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_START;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_STOP;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_SWITCH_FG;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_SWITCH_UI;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_UNLOCKED_USER;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_UNLOCKING_USER;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND_VISIBLE;
import static com.android.server.pm.UserManagerInternal.USER_START_MODE_FOREGROUND;
import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
import static com.android.server.pm.UserManagerInternal.userStartModeToString;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -123,6 +134,8 @@
import com.android.server.SystemService.UserCompletedEventType;
import com.android.server.SystemServiceManager;
import com.android.server.am.UserState.KeyEvictedCallback;
+import com.android.server.pm.UserJourneyLogger;
+import com.android.server.pm.UserJourneyLogger.UserJourneySession;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
import com.android.server.pm.UserManagerInternal.UserStartMode;
@@ -138,7 +151,6 @@
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
-import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
@@ -221,75 +233,6 @@
// TODO(b/197344658): Increase to 10s or 15s once we have a switch-UX-is-done invocation too.
private static final int USER_COMPLETED_EVENT_DELAY_MS = 5 * 1000;
- // Used for statsd logging with UserLifecycleJourneyReported + UserLifecycleEventOccurred atoms
- private static final long INVALID_SESSION_ID = 0;
-
- // The various user journeys, defined in the UserLifecycleJourneyReported atom for statsd
- private static final int USER_JOURNEY_UNKNOWN =
- FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__UNKNOWN;
- private static final int USER_JOURNEY_USER_SWITCH_FG =
- FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_SWITCH_FG;
- private static final int USER_JOURNEY_USER_SWITCH_UI =
- FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_SWITCH_UI;
- private static final int USER_JOURNEY_USER_START =
- FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_START;
- private static final int USER_JOURNEY_USER_CREATE =
- FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE;
- private static final int USER_JOURNEY_USER_STOP =
- FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_STOP;
- @IntDef(prefix = { "USER_JOURNEY" }, value = {
- USER_JOURNEY_UNKNOWN,
- USER_JOURNEY_USER_SWITCH_FG,
- USER_JOURNEY_USER_SWITCH_UI,
- USER_JOURNEY_USER_START,
- USER_JOURNEY_USER_CREATE,
- USER_JOURNEY_USER_STOP
- })
- @interface UserJourney {}
-
- // The various user lifecycle events, defined in the UserLifecycleEventOccurred atom for statsd
- private static final int USER_LIFECYCLE_EVENT_UNKNOWN =
- FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNKNOWN;
- private static final int USER_LIFECYCLE_EVENT_SWITCH_USER =
- FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__SWITCH_USER;
- private static final int USER_LIFECYCLE_EVENT_START_USER =
- FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__START_USER;
- private static final int USER_LIFECYCLE_EVENT_CREATE_USER =
- FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__CREATE_USER;
- private static final int USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED =
- FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__USER_RUNNING_LOCKED;
- private static final int USER_LIFECYCLE_EVENT_UNLOCKING_USER =
- FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNLOCKING_USER;
- private static final int USER_LIFECYCLE_EVENT_UNLOCKED_USER =
- FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNLOCKED_USER;
- private static final int USER_LIFECYCLE_EVENT_STOP_USER =
- FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__STOP_USER;
- @IntDef(prefix = { "USER_LIFECYCLE_EVENT" }, value = {
- USER_LIFECYCLE_EVENT_UNKNOWN,
- USER_LIFECYCLE_EVENT_SWITCH_USER,
- USER_LIFECYCLE_EVENT_START_USER,
- USER_LIFECYCLE_EVENT_CREATE_USER,
- USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
- USER_LIFECYCLE_EVENT_UNLOCKING_USER,
- USER_LIFECYCLE_EVENT_UNLOCKED_USER,
- USER_LIFECYCLE_EVENT_STOP_USER
- })
- @interface UserLifecycleEvent {}
-
- // User lifecyle event state, defined in the UserLifecycleEventOccurred atom for statsd
- private static final int USER_LIFECYCLE_EVENT_STATE_BEGIN =
- FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__BEGIN;
- private static final int USER_LIFECYCLE_EVENT_STATE_FINISH =
- FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__FINISH;
- private static final int USER_LIFECYCLE_EVENT_STATE_NONE =
- FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__NONE;
- @IntDef(prefix = { "USER_LIFECYCLE_EVENT_STATE" }, value = {
- USER_LIFECYCLE_EVENT_STATE_BEGIN,
- USER_LIFECYCLE_EVENT_STATE_FINISH,
- USER_LIFECYCLE_EVENT_STATE_NONE,
- })
- @interface UserLifecycleEventState {}
-
/**
* Maximum number of users we allow to be running at a time, including system user.
*
@@ -420,13 +363,6 @@
private final ArrayList<Integer> mLastActiveUsers = new ArrayList<>();
/**
- * {@link UserIdInt} to {@link UserJourneySession} mapping used for statsd logging for the
- * UserLifecycleJourneyReported and UserLifecycleEventOccurred atoms.
- */
- @GuardedBy("mUserIdToUserJourneyMap")
- private final SparseArray<UserJourneySession> mUserIdToUserJourneyMap = new SparseArray<>();
-
- /**
* Map of userId to {@link UserCompletedEventType} event flags, indicating which as-yet-
* unreported user-starting events have transpired for the given user.
*/
@@ -621,8 +557,9 @@
// but we might immediately step into RUNNING below if the user
// storage is already unlocked.
if (uss.setState(STATE_BOOTING, STATE_RUNNING_LOCKED)) {
- logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
- USER_LIFECYCLE_EVENT_STATE_NONE);
+ mInjector.getUserJourneyLogger()
+ .logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
+ EVENT_STATE_NONE);
mInjector.getUserManagerInternal().setUserState(userId, uss.state);
// Do not report secondary users, runtime restarts or first boot/upgrade
if (userId == UserHandle.USER_SYSTEM
@@ -646,7 +583,10 @@
// user transitions to RUNNING_LOCKED. However, in "headless system user mode", the
// system user is explicitly started before the device has finished booting. In
// that case, we need to wait until onBootComplete() to send the broadcast.
- if (!(mInjector.isHeadlessSystemUserMode() && uss.mHandle.isSystem())) {
+ // Similarly, this occurs after a user switch, but in HSUM we switch to the main
+ // user before boot is complete, so again this should be delayed until
+ // onBootComplete if boot has not yet completed.
+ if (mAllowUserUnlocking) {
// ACTION_LOCKED_BOOT_COMPLETED
sendLockedBootCompletedBroadcast(resultTo, userId);
}
@@ -694,8 +634,9 @@
private boolean finishUserUnlocking(final UserState uss) {
final int userId = uss.mHandle.getIdentifier();
EventLog.writeEvent(EventLogTags.UC_FINISH_USER_UNLOCKING, userId);
- logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_UNLOCKING_USER,
- USER_LIFECYCLE_EVENT_STATE_BEGIN);
+ mInjector.getUserJourneyLogger()
+ .logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_UNLOCKING_USER,
+ EVENT_STATE_BEGIN);
// If the user key hasn't been unlocked yet, we cannot proceed.
if (!StorageManager.isUserKeyUnlocked(userId)) return false;
synchronized (mLock) {
@@ -1073,9 +1014,7 @@
return;
}
- logUserJourneyInfo(null, getUserInfo(userId), USER_JOURNEY_USER_STOP);
- logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER,
- USER_LIFECYCLE_EVENT_STATE_BEGIN);
+ logUserJourneyBegin(userId, USER_JOURNEY_USER_STOP);
if (stopUserCallback != null) {
uss.mStopCallbacks.add(stopUserCallback);
@@ -1138,9 +1077,16 @@
synchronized (mLock) {
if (uss.state != UserState.STATE_STOPPING) {
// Whoops, we are being started back up. Abort, abort!
- logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER,
- USER_LIFECYCLE_EVENT_STATE_NONE);
- clearSessionId(userId);
+ UserJourneySession session = mInjector.getUserJourneyLogger()
+ .logUserJourneyFinishWithError(-1, getUserInfo(userId),
+ USER_JOURNEY_USER_STOP, ERROR_CODE_ABORTED);
+ if (session != null) {
+ mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, session);
+ } else {
+ mInjector.getUserJourneyLogger()
+ .logUserJourneyFinishWithError(-1, getUserInfo(userId),
+ USER_JOURNEY_USER_STOP, ERROR_CODE_INVALID_SESSION_ID);
+ }
return;
}
uss.setState(UserState.STATE_SHUTDOWN);
@@ -1247,9 +1193,11 @@
mInjector.getUserManager().removeUserEvenWhenDisallowed(userId);
}
- logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER,
- USER_LIFECYCLE_EVENT_STATE_FINISH);
- clearSessionId(userId);
+ UserJourneySession session = mInjector.getUserJourneyLogger()
+ .logUserJourneyFinish(-1, userInfo, USER_JOURNEY_USER_STOP);
+ if (session != null) {
+ mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, session);
+ }
if (lockUser) {
dispatchUserLocking(userIdToLock, keyEvictedCallbacks);
@@ -1259,9 +1207,11 @@
// which was paused while the SHUTDOWN flow of the user was in progress.
resumePendingUserStarts(userId);
} else {
- logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER,
- USER_LIFECYCLE_EVENT_STATE_NONE);
- clearSessionId(userId);
+ UserJourneySession session = mInjector.getUserJourneyLogger()
+ .finishAndClearIncompleteUserJourney(userId, USER_JOURNEY_USER_STOP);
+ if (session != null) {
+ mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, session);
+ }
}
}
@@ -2617,9 +2567,9 @@
// we should *not* transition users out of the BOOTING state using finishUserBoot(), as that
// doesn't handle issuing the needed onUserStarting() call, and it would just race with an
// explicit start anyway. We do, however, need to send the "locked boot complete" broadcast
- // for the system user, as that got skipped earlier due to the *device* boot not being
- // complete yet. We also need to try to unlock all started users, since until now explicit
- // user starts didn't proceed to unlocking, due to it being too early in the device boot.
+ // as that got skipped earlier due to the *device* boot not being complete yet.
+ // We also need to try to unlock all started users, since until now explicit user starts
+ // didn't proceed to unlocking, due to it being too early in the device boot.
//
// USER_SYSTEM must be processed first. It will be first in the array, as its ID is lowest.
Preconditions.checkArgument(startedUsers.keyAt(0) == UserHandle.USER_SYSTEM);
@@ -2629,9 +2579,7 @@
if (!mInjector.isHeadlessSystemUserMode()) {
finishUserBoot(uss, resultTo);
} else {
- if (userId == UserHandle.USER_SYSTEM) {
- sendLockedBootCompletedBroadcast(resultTo, userId);
- }
+ sendLockedBootCompletedBroadcast(resultTo, userId);
maybeUnlockUser(userId);
}
}
@@ -3135,10 +3083,7 @@
public boolean handleMessage(Message msg) {
switch (msg.what) {
case START_USER_SWITCH_FG_MSG:
- logUserJourneyInfo(getUserInfo(getCurrentUserId()), getUserInfo(msg.arg1),
- USER_JOURNEY_USER_SWITCH_FG);
- logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_SWITCH_USER,
- USER_LIFECYCLE_EVENT_STATE_BEGIN);
+ logUserJourneyBegin(msg.arg1, USER_JOURNEY_USER_SWITCH_FG);
startUserInForeground(msg.arg1);
break;
case REPORT_USER_SWITCH_MSG:
@@ -3160,18 +3105,15 @@
mInjector.batteryStatsServiceNoteEvent(
BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
Integer.toString(msg.arg1), msg.arg1);
- logUserJourneyInfo(null, getUserInfo(msg.arg1), USER_JOURNEY_USER_START);
- logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_START_USER,
- USER_LIFECYCLE_EVENT_STATE_BEGIN);
+ logUserJourneyBegin(msg.arg1, USER_JOURNEY_USER_START);
mInjector.onUserStarting(/* userId= */ msg.arg1);
scheduleOnUserCompletedEvent(msg.arg1,
UserCompletedEventType.EVENT_TYPE_USER_STARTING,
USER_COMPLETED_EVENT_DELAY_MS);
- logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_START_USER,
- USER_LIFECYCLE_EVENT_STATE_FINISH);
- clearSessionId(msg.arg1, USER_JOURNEY_USER_START);
+ mInjector.getUserJourneyLogger().logUserJourneyFinish(-1 , getUserInfo(msg.arg1),
+ USER_JOURNEY_USER_START);
break;
case USER_UNLOCK_MSG:
final int userId = msg.arg1;
@@ -3180,10 +3122,11 @@
FgThread.getHandler().post(() -> {
mInjector.loadUserRecents(userId);
});
- logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_UNLOCKING_USER,
- USER_LIFECYCLE_EVENT_STATE_FINISH);
- logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_UNLOCKED_USER,
- USER_LIFECYCLE_EVENT_STATE_BEGIN);
+
+ mInjector.getUserJourneyLogger().logUserLifecycleEvent(msg.arg1,
+ USER_LIFECYCLE_EVENT_UNLOCKING_USER, EVENT_STATE_FINISH);
+ mInjector.getUserJourneyLogger().logUserLifecycleEvent(msg.arg1,
+ USER_LIFECYCLE_EVENT_UNLOCKED_USER, EVENT_STATE_BEGIN);
final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
t.traceBegin("finishUserUnlocked-" + userId);
@@ -3199,9 +3142,9 @@
// (No need to acquire lock to read mCurrentUserId since it is volatile.)
// TODO: Find something to wait for in the case of a profile.
mCurrentUserId == msg.arg1 ? USER_COMPLETED_EVENT_DELAY_MS : 1000);
- logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_UNLOCKED_USER,
- USER_LIFECYCLE_EVENT_STATE_FINISH);
- clearSessionId(msg.arg1);
+ mInjector.getUserJourneyLogger().logUserLifecycleEvent(msg.arg1,
+ USER_LIFECYCLE_EVENT_UNLOCKED_USER, EVENT_STATE_FINISH);
+ // Unlocking user is not a journey no need to clear sessionId
break;
case USER_CURRENT_MSG:
mInjector.batteryStatsServiceNoteEvent(
@@ -3224,22 +3167,24 @@
break;
case REPORT_USER_SWITCH_COMPLETE_MSG:
dispatchUserSwitchComplete(msg.arg1, msg.arg2);
- logUserLifecycleEvent(msg.arg2, USER_LIFECYCLE_EVENT_SWITCH_USER,
- USER_LIFECYCLE_EVENT_STATE_FINISH);
+ UserJourneySession session = mInjector.getUserJourneyLogger()
+ .logUserSwitchJourneyFinish(msg.arg1, getUserInfo(msg.arg2));
+ if (session != null) {
+ mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, session);
+ }
break;
case REPORT_LOCKED_BOOT_COMPLETE_MSG:
dispatchLockedBootComplete(msg.arg1);
break;
case START_USER_SWITCH_UI_MSG:
final Pair<UserInfo, UserInfo> fromToUserPair = (Pair<UserInfo, UserInfo>) msg.obj;
- logUserJourneyInfo(fromToUserPair.first, fromToUserPair.second,
- USER_JOURNEY_USER_SWITCH_UI);
- logUserLifecycleEvent(fromToUserPair.second.id, USER_LIFECYCLE_EVENT_SWITCH_USER,
- USER_LIFECYCLE_EVENT_STATE_BEGIN);
+ logUserJourneyBegin(fromToUserPair.second.id, USER_JOURNEY_USER_SWITCH_UI);
showUserSwitchDialog(fromToUserPair);
break;
case CLEAR_USER_JOURNEY_SESSION_MSG:
- logAndClearSessionId(msg.arg1);
+ mInjector.getUserJourneyLogger()
+ .finishAndClearIncompleteUserJourney(msg.arg1, msg.arg2);
+ mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, msg.obj);
break;
case COMPLETE_USER_SWITCH_MSG:
completeUserSwitch(msg.arg1, msg.arg2);
@@ -3317,123 +3262,29 @@
* statsd helper method for logging the start of a user journey via a UserLifecycleEventOccurred
* atom given the originating and targeting users for the journey.
*/
- private void logUserJourneyInfo(UserInfo origin, UserInfo target, @UserJourney int journey) {
- final long newSessionId = ThreadLocalRandom.current().nextLong(1, Long.MAX_VALUE);
- synchronized (mUserIdToUserJourneyMap) {
- UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(target.id);
- if (userJourneySession != null) {
- // TODO(b/157007231): Move this logic to a separate class/file.
- if ((userJourneySession.mJourney == USER_JOURNEY_USER_SWITCH_UI
- || userJourneySession.mJourney == USER_JOURNEY_USER_SWITCH_FG)
- && (journey == USER_JOURNEY_USER_START
- || journey == USER_JOURNEY_USER_STOP)) {
- /*
- * There is already a user switch journey, and a user start or stop journey for
- * the same target user received. New journey is most likely a part of user
- * switch journey so no need to create a new journey.
- */
- if (DEBUG_MU) {
- Slogf.d(TAG, journey + " not logged as it is expected to be part of "
- + userJourneySession.mJourney);
- }
- return;
- }
- /*
- * Possible reasons for this condition to be true:
- * - A user switch journey is received while another user switch journey is in
- * process for the same user.
- * - A user switch journey is received while user start journey is in process for
- * the same user.
- * - A user start journey is received while another user start journey is in process
- * for the same user.
- * In all cases potentially an incomplete, timed-out session or multiple
- * simultaneous requests. It is not possible to keep track of multiple sessions for
- * the same user, so previous session is abandoned.
- */
- FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED,
- userJourneySession.mSessionId, target.id, USER_LIFECYCLE_EVENT_UNKNOWN,
- USER_LIFECYCLE_EVENT_STATE_NONE);
- }
-
+ private void logUserJourneyBegin(int targetId,
+ @UserJourneyLogger.UserJourney int journey) {
+ UserJourneySession oldSession = mInjector.getUserJourneyLogger()
+ .finishAndClearIncompleteUserJourney(targetId, journey);
+ if (oldSession != null) {
if (DEBUG_MU) {
Slogf.d(TAG,
- "Starting a new journey: " + journey + " with session id: " + newSessionId);
+ "Starting a new journey: " + journey + " with session id: "
+ + oldSession);
}
-
- userJourneySession = new UserJourneySession(newSessionId, journey);
- mUserIdToUserJourneyMap.put(target.id, userJourneySession);
/*
- * User lifecyle journey would be complete when {@code #clearSessionId} is called after
- * the last expected lifecycle event for the journey. It may be possible that the last
- * event is not called, e.g., user not unlocked after user switching. In such cases user
- * journey is cleared after {@link USER_JOURNEY_TIMEOUT}.
+ * User lifecycle journey would be complete when {@code #clearSessionId} is called
+ * after the last expected lifecycle event for the journey. It may be possible that
+ * the last event is not called, e.g., user not unlocked after user switching. In such
+ * cases user journey is cleared after {@link USER_JOURNEY_TIMEOUT}.
*/
- mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG);
- mHandler.sendMessageDelayed(mHandler.obtainMessage(CLEAR_USER_JOURNEY_SESSION_MSG,
- target.id, /* arg2= */ 0), USER_JOURNEY_TIMEOUT_MS);
+ mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG, oldSession);
}
+ UserJourneySession newSession = mInjector.getUserJourneyLogger()
+ .logUserJourneyBegin(targetId, journey);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(CLEAR_USER_JOURNEY_SESSION_MSG,
+ targetId, /* arg2= */ journey, newSession), USER_JOURNEY_TIMEOUT_MS);
- FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED, newSessionId,
- journey, origin != null ? origin.id : -1,
- target.id, UserManager.getUserTypeForStatsd(target.userType), target.flags);
- }
-
- /**
- * statsd helper method for logging the given event for the UserLifecycleEventOccurred statsd
- * atom.
- */
- private void logUserLifecycleEvent(@UserIdInt int userId, @UserLifecycleEvent int event,
- @UserLifecycleEventState int eventState) {
- final long sessionId;
- synchronized (mUserIdToUserJourneyMap) {
- final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(userId);
- if (userJourneySession == null || userJourneySession.mSessionId == INVALID_SESSION_ID) {
- Slogf.w(TAG, "UserLifecycleEvent " + event
- + " received without an active userJourneySession.");
- return;
- }
- sessionId = userJourneySession.mSessionId;
- }
-
- FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED, sessionId, userId,
- event, eventState);
- }
-
- /**
- * Clears the {@link UserJourneySession} for a given {@link UserIdInt} and {@link UserJourney}.
- */
- private void clearSessionId(@UserIdInt int userId, @UserJourney int journey) {
- synchronized (mUserIdToUserJourneyMap) {
- final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(userId);
- if (userJourneySession != null && userJourneySession.mJourney == journey) {
- clearSessionId(userId);
- }
- }
- }
-
- /**
- * Clears the {@link UserJourneySession} for a given {@link UserIdInt}.
- */
- private void clearSessionId(@UserIdInt int userId) {
- synchronized (mUserIdToUserJourneyMap) {
- mHandler.removeMessages(CLEAR_USER_JOURNEY_SESSION_MSG);
- mUserIdToUserJourneyMap.delete(userId);
- }
- }
-
- /**
- * Log a final event of the {@link UserJourneySession} and clear it.
- */
- private void logAndClearSessionId(@UserIdInt int userId) {
- synchronized (mUserIdToUserJourneyMap) {
- final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(userId);
- if (userJourneySession != null) {
- FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED,
- userJourneySession.mSessionId, userId, USER_LIFECYCLE_EVENT_UNKNOWN,
- USER_LIFECYCLE_EVENT_STATE_NONE);
- }
- clearSessionId(userId);
- }
}
private BroadcastOptions getTemporaryAppAllowlistBroadcastOptions(
@@ -3471,23 +3322,6 @@
return mLastUserUnlockingUptime;
}
- /**
- * Helper class to store user journey and session id.
- *
- * <p> User journey tracks a chain of user lifecycle events occurring during different user
- * activities such as user start, user switch, and user creation.
- */
- // TODO(b/157007231): Move this class and user journey tracking logic to a separate file.
- private static class UserJourneySession {
- final long mSessionId;
- @UserJourney final int mJourney;
-
- UserJourneySession(long sessionId, @UserJourney int journey) {
- mJourney = journey;
- mSessionId = sessionId;
- }
- }
-
private static class UserProgressListener extends IProgressListener.Stub {
private volatile long mUnlockStarted;
@Override
@@ -3562,6 +3396,10 @@
return new Handler(mService.mUiHandler.getLooper(), callback);
}
+ protected UserJourneyLogger getUserJourneyLogger() {
+ return getUserManager().getUserJourneyLogger();
+ }
+
protected Context getContext() {
return mService.mContext;
}
@@ -3747,7 +3585,8 @@
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 649305f..4e7865c 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 @@
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;
@@ -34,10 +33,13 @@
import android.graphics.drawable.Animatable2;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
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 +53,9 @@
import com.android.internal.R;
import com.android.internal.util.ObjectUtils;
import com.android.internal.util.UserIcons;
+import com.android.server.wm.WindowManagerService;
+
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Dialog to show during the user switch. This dialog shows target user's name and their profile
@@ -63,18 +68,26 @@
// User switching doesn't happen that frequently, so it doesn't hurt to have it always on
protected static final boolean DEBUG = true;
+
private static final long DIALOG_SHOW_HIDE_ANIMATION_DURATION_MS = 300;
private final boolean mDisableAnimations;
+ // Time to wait for the onAnimationEnd() callbacks before moving on
+ private static final int ANIMATION_TIMEOUT_MS = 1000;
+ private final Handler mHandler = new Handler(Looper.myLooper());
+
protected final UserInfo mOldUser;
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 +97,10 @@
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,38 +182,38 @@
: 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);
+ asyncTraceBegin("dialog", 0);
super.show();
}
@Override
public void dismiss() {
super.dismiss();
- asyncTraceEnd("", 0);
+ stopFreezingScreen();
+ asyncTraceEnd("dialog", 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,14 +222,36 @@
}
}
- private void startShowAnimation(Runnable onAnimationEnd) {
- asyncTraceBegin("-showAnimation", 1);
- startDialogAnimation(new AlphaAnimation(0, 1), () -> {
- asyncTraceEnd("-showAnimation", 1);
+ private void startFreezingScreen() {
+ if (!mNeedToFreezeScreen) {
+ return;
+ }
+ traceBegin("startFreezingScreen");
+ mWindowManager.startFreezingScreen(0, 0);
+ traceEnd("startFreezingScreen");
+ }
- asyncTraceBegin("-spinnerAnimation", 2);
+ private void stopFreezingScreen() {
+ if (!mNeedToFreezeScreen) {
+ return;
+ }
+ traceBegin("stopFreezingScreen");
+ mWindowManager.stopFreezingScreen();
+ traceEnd("stopFreezingScreen");
+ }
+
+ private void startShowAnimation(Runnable onAnimationEnd) {
+ if (mDisableAnimations) {
+ onAnimationEnd.run();
+ return;
+ }
+ asyncTraceBegin("showAnimation", 1);
+ startDialogAnimation("show", new AlphaAnimation(0, 1), () -> {
+ asyncTraceEnd("showAnimation", 1);
+
+ asyncTraceBegin("spinnerAnimation", 2);
startProgressAnimation(() -> {
- asyncTraceEnd("-spinnerAnimation", 2);
+ asyncTraceEnd("spinnerAnimation", 2);
onAnimationEnd.run();
});
@@ -222,27 +259,53 @@
}
private void startDismissAnimation(Runnable onAnimationEnd) {
- asyncTraceBegin("-dismissAnimation", 3);
- startDialogAnimation(new AlphaAnimation(1, 0), () -> {
- asyncTraceEnd("-dismissAnimation", 3);
+ if (mDisableAnimations || mNeedToFreezeScreen) {
+ // animations are disabled or screen is frozen, no need to play an animation
+ onAnimationEnd.run();
+ return;
+ }
+ asyncTraceBegin("dismissAnimation", 3);
+ startDialogAnimation("dismiss", new AlphaAnimation(1, 0), () -> {
+ asyncTraceEnd("dismissAnimation", 3);
onAnimationEnd.run();
});
}
private void startProgressAnimation(Runnable onAnimationEnd) {
- final ImageView progressCircular = findViewById(R.id.progress_circular);
- final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) progressCircular.getDrawable();
+ final AnimatedVectorDrawable avd = getSpinnerAVD();
+ if (mDisableAnimations || avd == null) {
+ onAnimationEnd.run();
+ return;
+ }
+ final Runnable onAnimationEndWithTimeout = animationWithTimeout("spinner", onAnimationEnd);
avd.registerAnimationCallback(new Animatable2.AnimationCallback() {
@Override
public void onAnimationEnd(Drawable drawable) {
- onAnimationEnd.run();
+ onAnimationEndWithTimeout.run();
}
});
avd.start();
}
- private void startDialogAnimation(Animation animation, Runnable onAnimationEnd) {
+ private AnimatedVectorDrawable getSpinnerAVD() {
+ final ImageView view = findViewById(R.id.progress_circular);
+ if (view != null) {
+ final Drawable drawable = view.getDrawable();
+ if (drawable instanceof AnimatedVectorDrawable) {
+ return (AnimatedVectorDrawable) drawable;
+ }
+ }
+ return null;
+ }
+
+ private void startDialogAnimation(String name, Animation animation, Runnable onAnimationEnd) {
+ final View view = findViewById(R.id.content);
+ if (mDisableAnimations || view == null) {
+ onAnimationEnd.run();
+ return;
+ }
+ final Runnable onAnimationEndWithTimeout = animationWithTimeout(name, onAnimationEnd);
animation.setDuration(DIALOG_SHOW_HIDE_ANIMATION_DURATION_MS);
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
@@ -252,7 +315,7 @@
@Override
public void onAnimationEnd(Animation animation) {
- onAnimationEnd.run();
+ onAnimationEndWithTimeout.run();
}
@Override
@@ -260,14 +323,42 @@
}
});
- findViewById(R.id.content).startAnimation(animation);
+ view.startAnimation(animation);
+ }
+
+ private Runnable animationWithTimeout(String name, Runnable onAnimationEnd) {
+ final AtomicBoolean isFirst = new AtomicBoolean(true);
+ final Runnable onAnimationEndOrTimeout = () -> {
+ if (isFirst.getAndSet(false)) {
+ mHandler.removeCallbacksAndMessages(null);
+ onAnimationEnd.run();
+ }
+ };
+ mHandler.postDelayed(() -> {
+ Slog.w(TAG, name + " animation not completed in " + ANIMATION_TIMEOUT_MS + " ms");
+ onAnimationEndOrTimeout.run();
+ }, ANIMATION_TIMEOUT_MS);
+
+ return onAnimationEndOrTimeout;
}
private void asyncTraceBegin(String subTag, int subCookie) {
+ if (DEBUG) Slog.d(TAG, "asyncTraceBegin-" + subTag);
Trace.asyncTraceBegin(TRACE_TAG, TAG + subTag, mTraceCookie + subCookie);
}
private void asyncTraceEnd(String subTag, int subCookie) {
Trace.asyncTraceEnd(TRACE_TAG, TAG + subTag, mTraceCookie + subCookie);
+ if (DEBUG) Slog.d(TAG, "asyncTraceEnd-" + subTag);
+ }
+
+ private void traceBegin(String msg) {
+ if (DEBUG) Slog.d(TAG, "traceBegin-" + msg);
+ Trace.traceBegin(TRACE_TAG, msg);
+ }
+
+ private void traceEnd(String msg) {
+ Trace.traceEnd(TRACE_TAG);
+ if (DEBUG) Slog.d(TAG, "traceEnd-" + msg);
}
}
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index d369af6..e4a5a3e 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -402,11 +402,15 @@
public enum FrameRate {
FPS_DEFAULT(0),
FPS_30(30),
+ FPS_36(36),
FPS_40(40),
FPS_45(45),
+ FPS_48(48),
FPS_60(60),
+ FPS_72(72),
FPS_90(90),
FPS_120(120),
+ FPS_144(144),
FPS_INVALID(-1);
public final int fps;
@@ -423,16 +427,24 @@
switch (raw) {
case "30":
return FrameRate.FPS_30.fps;
+ case "36":
+ return FrameRate.FPS_36.fps;
case "40":
return FrameRate.FPS_40.fps;
case "45":
return FrameRate.FPS_45.fps;
+ case "48":
+ return FrameRate.FPS_48.fps;
case "60":
return FrameRate.FPS_60.fps;
+ case "72":
+ return FrameRate.FPS_72.fps;
case "90":
return FrameRate.FPS_90.fps;
case "120":
return FrameRate.FPS_120.fps;
+ case "144":
+ return FrameRate.FPS_144.fps;
case "disable":
case "":
return FrameRate.FPS_DEFAULT.fps;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index ada92f5..bc4e8df 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -88,14 +88,14 @@
private final @NonNull AudioSystemAdapter mAudioSystem;
/** ID for Communication strategy retrieved form audio policy manager */
- private int mCommunicationStrategyId = -1;
+ /*package*/ int mCommunicationStrategyId = -1;
/** ID for Accessibility strategy retrieved form audio policy manager */
private int mAccessibilityStrategyId = -1;
/** Active communication device reported by audio policy manager */
- private AudioDeviceInfo mActiveCommunicationDevice;
+ /*package*/ AudioDeviceInfo mActiveCommunicationDevice;
/** Last preferred device set for communication strategy */
private AudioDeviceAttributes mPreferredCommunicationDevice;
@@ -755,6 +755,19 @@
mIsLeOutput = false;
}
+ BtDeviceInfo(@NonNull BtDeviceInfo src, int state) {
+ mDevice = src.mDevice;
+ mState = state;
+ mProfile = src.mProfile;
+ mSupprNoisy = src.mSupprNoisy;
+ mVolume = src.mVolume;
+ mIsLeOutput = src.mIsLeOutput;
+ mEventSource = src.mEventSource;
+ mAudioSystemDevice = src.mAudioSystemDevice;
+ mMusicDevice = src.mMusicDevice;
+ mCodec = src.mCodec;
+ }
+
// redefine equality op so we can match messages intended for this device
@Override
public boolean equals(Object o) {
@@ -821,7 +834,7 @@
* @param info struct with the (dis)connection information
*/
/*package*/ void queueOnBluetoothActiveDeviceChanged(@NonNull BtDeviceChangedData data) {
- if (data.mInfo.getProfile() == BluetoothProfile.A2DP && data.mPreviousDevice != null
+ if (data.mPreviousDevice != null
&& data.mPreviousDevice.equals(data.mNewDevice)) {
final String name = TextUtils.emptyIfNull(data.mNewDevice.getName());
new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE + MediaMetrics.SEPARATOR
@@ -830,7 +843,8 @@
.set(MediaMetrics.Property.STATUS, data.mInfo.getProfile())
.record();
synchronized (mDeviceStateLock) {
- postBluetoothA2dpDeviceConfigChange(data.mNewDevice);
+ postBluetoothDeviceConfigChange(createBtDeviceInfo(data, data.mNewDevice,
+ BluetoothProfile.STATE_CONNECTED));
}
} else {
synchronized (mDeviceStateLock) {
@@ -1064,8 +1078,8 @@
new AudioModeInfo(mode, pid, uid));
}
- /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) {
- sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device);
+ /*package*/ void postBluetoothDeviceConfigChange(@NonNull BtDeviceInfo info) {
+ sendLMsgNoDelay(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, info);
}
/*package*/ void startBluetoothScoForClient(IBinder cb, int pid, int scoAudioMode,
@@ -1092,21 +1106,21 @@
/*package*/ int setPreferredDevicesForStrategySync(int strategy,
@NonNull List<AudioDeviceAttributes> devices) {
- return mDeviceInventory.setPreferredDevicesForStrategySync(strategy, devices);
+ return mDeviceInventory.setPreferredDevicesForStrategyAndSave(strategy, devices);
}
/*package*/ int removePreferredDevicesForStrategySync(int strategy) {
- return mDeviceInventory.removePreferredDevicesForStrategySync(strategy);
+ return mDeviceInventory.removePreferredDevicesForStrategyAndSave(strategy);
}
/*package*/ int setDeviceAsNonDefaultForStrategySync(int strategy,
@NonNull AudioDeviceAttributes device) {
- return mDeviceInventory.setDeviceAsNonDefaultForStrategySync(strategy, device);
+ return mDeviceInventory.setDeviceAsNonDefaultForStrategyAndSave(strategy, device);
}
/*package*/ int removeDeviceAsNonDefaultForStrategySync(int strategy,
@NonNull AudioDeviceAttributes device) {
- return mDeviceInventory.removeDeviceAsNonDefaultForStrategySync(strategy, device);
+ return mDeviceInventory.removeDeviceAsNonDefaultForStrategyAndSave(strategy, device);
}
/*package*/ void registerStrategyPreferredDevicesDispatcher(
@@ -1131,11 +1145,11 @@
/*package*/ int setPreferredDevicesForCapturePresetSync(int capturePreset,
@NonNull List<AudioDeviceAttributes> devices) {
- return mDeviceInventory.setPreferredDevicesForCapturePresetSync(capturePreset, devices);
+ return mDeviceInventory.setPreferredDevicesForCapturePresetAndSave(capturePreset, devices);
}
/*package*/ int clearPreferredDevicesForCapturePresetSync(int capturePreset) {
- return mDeviceInventory.clearPreferredDevicesForCapturePresetSync(capturePreset);
+ return mDeviceInventory.clearPreferredDevicesForCapturePresetAndSave(capturePreset);
}
/*package*/ void registerCapturePresetDevicesRoleDispatcher(
@@ -1322,6 +1336,10 @@
sendIMsgNoDelay(MSG_I_SCO_AUDIO_STATE_CHANGED, SENDMSG_QUEUE, state);
}
+ /*package*/ void postNotifyPreferredAudioProfileApplied(BluetoothDevice btDevice) {
+ sendLMsgNoDelay(MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED, SENDMSG_QUEUE, btDevice);
+ }
+
/*package*/ static final class CommunicationDeviceInfo {
final @NonNull IBinder mCb; // Identifies the requesting client for death handler
final int mPid; // Requester process ID
@@ -1397,9 +1415,11 @@
}
}
- /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect) {
+ /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes,
+ boolean connect, @Nullable BluetoothDevice btDevice) {
synchronized (mDeviceStateLock) {
- return mDeviceInventory.handleDeviceConnection(attributes, connect, false /*for test*/);
+ return mDeviceInventory.handleDeviceConnection(
+ attributes, connect, false /*for test*/, btDevice);
}
}
@@ -1640,13 +1660,10 @@
(String) msg.obj, msg.arg1);
}
break;
- case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
- final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
+ case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE:
synchronized (mDeviceStateLock) {
- final int a2dpCodec = mBtHelper.getA2dpCodec(btDevice);
- mDeviceInventory.onBluetoothA2dpDeviceConfigChange(
- new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec),
- BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
+ mDeviceInventory.onBluetoothDeviceConfigChange(
+ (BtDeviceInfo) msg.obj, BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
}
break;
case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
@@ -1810,6 +1827,10 @@
case MSG_IL_SET_LEAUDIO_SUSPENDED: {
setLeAudioSuspended((msg.arg1 == 1), false /*internal*/, (String) msg.obj);
} break;
+ case MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED: {
+ final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
+ BtHelper.onNotifyPreferredAudioProfileApplied(btDevice);
+ } break;
default:
Log.wtf(TAG, "Invalid message " + msg.what);
}
@@ -1845,7 +1866,7 @@
private static final int MSG_IL_BTA2DP_TIMEOUT = 10;
// process change of A2DP device configuration, obj is BluetoothDevice
- private static final int MSG_L_A2DP_DEVICE_CONFIG_CHANGE = 11;
+ private static final int MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE = 11;
private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 12;
private static final int MSG_REPORT_NEW_ROUTES = 13;
@@ -1887,13 +1908,15 @@
private static final int MSG_IL_SET_A2DP_SUSPENDED = 50;
private static final int MSG_IL_SET_LEAUDIO_SUSPENDED = 51;
+ private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 52;
+
private static boolean isMessageHandledUnderWakelock(int msgId) {
switch(msgId) {
case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
case MSG_L_SET_BT_ACTIVE_DEVICE:
case MSG_IL_BTA2DP_TIMEOUT:
case MSG_IL_BTLEAUDIO_TIMEOUT:
- case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
+ case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE:
case MSG_TOGGLE_HDMI:
case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT:
case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT:
@@ -1985,7 +2008,7 @@
case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
case MSG_IL_BTA2DP_TIMEOUT:
case MSG_IL_BTLEAUDIO_TIMEOUT:
- case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
+ case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE:
if (sLastDeviceConnectMsgTime >= time) {
// add a little delay to make sure messages are ordered as expected
time = sLastDeviceConnectMsgTime + 30;
@@ -2005,7 +2028,7 @@
static {
MESSAGES_MUTE_MUSIC = new HashSet<>();
MESSAGES_MUTE_MUSIC.add(MSG_L_SET_BT_ACTIVE_DEVICE);
- MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONFIG_CHANGE);
+ MESSAGES_MUTE_MUSIC.add(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE);
MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT);
MESSAGES_MUTE_MUSIC.add(MSG_IIL_SET_FORCE_BT_A2DP_USE);
}
@@ -2026,7 +2049,7 @@
// Do not mute on bluetooth event if music is playing on a wired headset.
if ((message == MSG_L_SET_BT_ACTIVE_DEVICE
|| message == MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT
- || message == MSG_L_A2DP_DEVICE_CONFIG_CHANGE)
+ || message == MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE)
&& AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)
&& hasIntersection(mDeviceInventory.DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET,
mAudioService.getDeviceSetForStream(AudioSystem.STREAM_MUSIC))) {
@@ -2165,18 +2188,19 @@
if (preferredCommunicationDevice == null) {
AudioDeviceAttributes defaultDevice = getDefaultCommunicationDevice();
if (defaultDevice != null) {
- setPreferredDevicesForStrategySync(
+ mDeviceInventory.setPreferredDevicesForStrategy(
mCommunicationStrategyId, Arrays.asList(defaultDevice));
- setPreferredDevicesForStrategySync(
+ mDeviceInventory.setPreferredDevicesForStrategy(
mAccessibilityStrategyId, Arrays.asList(defaultDevice));
} else {
- removePreferredDevicesForStrategySync(mCommunicationStrategyId);
- removePreferredDevicesForStrategySync(mAccessibilityStrategyId);
+ mDeviceInventory.removePreferredDevicesForStrategy(mCommunicationStrategyId);
+ mDeviceInventory.removePreferredDevicesForStrategy(mAccessibilityStrategyId);
}
+ mDeviceInventory.applyConnectedDevicesRoles();
} else {
- setPreferredDevicesForStrategySync(
+ mDeviceInventory.setPreferredDevicesForStrategy(
mCommunicationStrategyId, Arrays.asList(preferredCommunicationDevice));
- setPreferredDevicesForStrategySync(
+ mDeviceInventory.setPreferredDevicesForStrategy(
mAccessibilityStrategyId, Arrays.asList(preferredCommunicationDevice));
}
onUpdatePhoneStrategyDevice(preferredCommunicationDevice);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 228bc87..773df37 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -34,26 +34,36 @@
import android.media.IStrategyNonDefaultDevicesDispatcher;
import android.media.IStrategyPreferredDevicesDispatcher;
import android.media.MediaMetrics;
+import android.media.MediaRecorder.AudioSource;
+import android.media.audiopolicy.AudioProductStrategy;
import android.media.permission.ClearCallingIdentityContext;
import android.media.permission.SafeCloseable;
import android.os.Binder;
+import android.os.Bundle;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.utils.EventLogger;
+import com.google.android.collect.Sets;
+
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
@@ -175,18 +185,26 @@
final RemoteCallbackList<ICapturePresetDevicesRoleDispatcher> mDevRoleCapturePresetDispatchers =
new RemoteCallbackList<ICapturePresetDevicesRoleDispatcher>();
+ final List<AudioProductStrategy> mStrategies;
+
/*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
- mDeviceBroker = broker;
- mAudioSystem = AudioSystemAdapter.getDefaultAdapter();
+ this(broker, AudioSystemAdapter.getDefaultAdapter());
}
//-----------------------------------------------------------
/** for mocking only, allows to inject AudioSystem adapter */
/*package*/ AudioDeviceInventory(@NonNull AudioSystemAdapter audioSystem) {
- mDeviceBroker = null;
- mAudioSystem = audioSystem;
+ this(null, audioSystem);
}
+ private AudioDeviceInventory(@Nullable AudioDeviceBroker broker,
+ @Nullable AudioSystemAdapter audioSystem) {
+ mDeviceBroker = broker;
+ mAudioSystem = audioSystem;
+ mStrategies = AudioProductStrategy.getAudioProductStrategies();
+ mBluetoothDualModeEnabled = SystemProperties.getBoolean(
+ "persist.bluetooth.enable_dual_mode_audio", false);
+ }
/*package*/ void setDeviceBroker(@NonNull AudioDeviceBroker broker) {
mDeviceBroker = broker;
}
@@ -203,8 +221,13 @@
int mDeviceCodecFormat;
final UUID mSensorUuid;
+ /** Disabled operating modes for this device. Use a negative logic so that by default
+ * an empty list means all modes are allowed.
+ * See BluetoothAdapter.AUDIO_MODE_DUPLEX and BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY */
+ @NonNull ArraySet<String> mDisabledModes = new ArraySet(0);
+
DeviceInfo(int deviceType, String deviceName, String deviceAddress,
- int deviceCodecFormat, UUID sensorUuid) {
+ int deviceCodecFormat, @Nullable UUID sensorUuid) {
mDeviceType = deviceType;
mDeviceName = deviceName == null ? "" : deviceName;
mDeviceAddress = deviceAddress == null ? "" : deviceAddress;
@@ -212,11 +235,31 @@
mSensorUuid = sensorUuid;
}
+ void setModeDisabled(String mode) {
+ mDisabledModes.add(mode);
+ }
+ void setModeEnabled(String mode) {
+ mDisabledModes.remove(mode);
+ }
+ boolean isModeEnabled(String mode) {
+ return !mDisabledModes.contains(mode);
+ }
+ boolean isOutputOnlyModeEnabled() {
+ return isModeEnabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY);
+ }
+ boolean isDuplexModeEnabled() {
+ return isModeEnabled(BluetoothAdapter.AUDIO_MODE_DUPLEX);
+ }
+
DeviceInfo(int deviceType, String deviceName, String deviceAddress,
int deviceCodecFormat) {
this(deviceType, deviceName, deviceAddress, deviceCodecFormat, null);
}
+ DeviceInfo(int deviceType, String deviceName, String deviceAddress) {
+ this(deviceType, deviceName, deviceAddress, AudioSystem.AUDIO_FORMAT_DEFAULT);
+ }
+
@Override
public String toString() {
return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType)
@@ -224,7 +267,8 @@
+ ") name:" + mDeviceName
+ " addr:" + mDeviceAddress
+ " codec: " + Integer.toHexString(mDeviceCodecFormat)
- + " sensorUuid: " + Objects.toString(mSensorUuid) + "]";
+ + " sensorUuid: " + Objects.toString(mSensorUuid)
+ + " disabled modes: " + mDisabledModes + "]";
}
@NonNull String getKey() {
@@ -276,9 +320,18 @@
pw.println(" " + prefix + " type:0x" + Integer.toHexString(keyType)
+ " (" + AudioSystem.getDeviceName(keyType)
+ ") addr:" + valueAddress); });
+ pw.println("\n" + prefix + "Preferred devices for capture preset:");
mPreferredDevicesForCapturePreset.forEach((capturePreset, devices) -> {
pw.println(" " + prefix + "capturePreset:" + capturePreset
+ " devices:" + devices); });
+ pw.println("\n" + prefix + "Applied devices roles for strategies:");
+ mAppliedStrategyRoles.forEach((key, devices) -> {
+ pw.println(" " + prefix + "strategy: " + key.first
+ + " role:" + key.second + " devices:" + devices); });
+ pw.println("\n" + prefix + "Applied devices roles for presets:");
+ mAppliedPresetRoles.forEach((key, devices) -> {
+ pw.println(" " + prefix + "preset: " + key.first
+ + " role:" + key.second + " devices:" + devices); });
}
//------------------------------------------------------------
@@ -299,15 +352,16 @@
AudioSystem.DEVICE_STATE_AVAILABLE,
di.mDeviceCodecFormat);
}
+ mAppliedStrategyRoles.clear();
+ applyConnectedDevicesRoles_l();
}
synchronized (mPreferredDevices) {
mPreferredDevices.forEach((strategy, devices) -> {
- mAudioSystem.setDevicesRoleForStrategy(
- strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices); });
+ setPreferredDevicesForStrategy(strategy, devices); });
}
synchronized (mNonDefaultDevices) {
mNonDefaultDevices.forEach((strategy, devices) -> {
- mAudioSystem.setDevicesRoleForStrategy(
+ addDevicesRoleForStrategy(
strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices); });
}
synchronized (mPreferredDevicesForCapturePreset) {
@@ -380,8 +434,7 @@
btInfo.mVolume * 10, btInfo.mAudioSystemDevice,
"onSetBtActiveDevice");
}
- makeA2dpDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
- "onSetBtActiveDevice", btInfo.mCodec);
+ makeA2dpDeviceAvailable(btInfo, "onSetBtActiveDevice");
}
break;
case BluetoothProfile.HEARING_AID:
@@ -397,10 +450,7 @@
if (switchToUnavailable) {
makeLeAudioDeviceUnavailableNow(address, btInfo.mAudioSystemDevice);
} else if (switchToAvailable) {
- makeLeAudioDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
- streamType, btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10,
- btInfo.mAudioSystemDevice,
- "onSetBtActiveDevice");
+ makeLeAudioDeviceAvailable(btInfo, streamType, "onSetBtActiveDevice");
}
break;
default: throw new IllegalArgumentException("Invalid profile "
@@ -411,30 +461,30 @@
@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
- /*package*/ void onBluetoothA2dpDeviceConfigChange(
- @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int event) {
+ /*package*/ void onBluetoothDeviceConfigChange(
+ @NonNull AudioDeviceBroker.BtDeviceInfo btInfo, int event) {
MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId
- + "onBluetoothA2dpDeviceConfigChange")
- .set(MediaMetrics.Property.EVENT, BtHelper.a2dpDeviceEventToString(event));
+ + "onBluetoothDeviceConfigChange")
+ .set(MediaMetrics.Property.EVENT, BtHelper.deviceEventToString(event));
- final BluetoothDevice btDevice = btInfo.getBtDevice();
+ final BluetoothDevice btDevice = btInfo.mDevice;
if (btDevice == null) {
mmi.set(MediaMetrics.Property.EARLY_RETURN, "btDevice null").record();
return;
}
if (AudioService.DEBUG_DEVICES) {
- Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice);
+ Log.d(TAG, "onBluetoothDeviceConfigChange btDevice=" + btDevice);
}
- int a2dpVolume = btInfo.getVolume();
- @AudioSystem.AudioFormatNativeEnumForBtCodec final int a2dpCodec = btInfo.getCodec();
+ int volume = btInfo.mVolume;
+ @AudioSystem.AudioFormatNativeEnumForBtCodec final int audioCodec = btInfo.mCodec;
String address = btDevice.getAddress();
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
address = "";
}
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
- "onBluetoothA2dpDeviceConfigChange addr=" + address
- + " event=" + BtHelper.a2dpDeviceEventToString(event)));
+ "onBluetoothDeviceConfigChange addr=" + address
+ + " event=" + BtHelper.deviceEventToString(event)));
synchronized (mDevicesLock) {
if (mDeviceBroker.hasScheduledA2dpConnection(btDevice)) {
@@ -449,53 +499,53 @@
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
final DeviceInfo di = mConnectedDevices.get(key);
if (di == null) {
- Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpDeviceConfigChange");
+ Log.e(TAG, "invalid null DeviceInfo in onBluetoothDeviceConfigChange");
mmi.set(MediaMetrics.Property.EARLY_RETURN, "null DeviceInfo").record();
return;
}
mmi.set(MediaMetrics.Property.ADDRESS, address)
.set(MediaMetrics.Property.ENCODING,
- AudioSystem.audioFormatToString(a2dpCodec))
- .set(MediaMetrics.Property.INDEX, a2dpVolume)
+ AudioSystem.audioFormatToString(audioCodec))
+ .set(MediaMetrics.Property.INDEX, volume)
.set(MediaMetrics.Property.NAME, di.mDeviceName);
- if (event == BtHelper.EVENT_ACTIVE_DEVICE_CHANGE) {
- // Device is connected
- if (a2dpVolume != -1) {
- mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC,
- // convert index to internal representation in VolumeStreamState
- a2dpVolume * 10,
- AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
- "onBluetoothA2dpDeviceConfigChange");
- }
- } else if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
- if (di.mDeviceCodecFormat != a2dpCodec) {
- di.mDeviceCodecFormat = a2dpCodec;
- mConnectedDevices.replace(key, di);
- }
- }
- final int res = mAudioSystem.handleDeviceConfigChange(
- AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
- BtHelper.getName(btDevice), a2dpCodec);
- if (res != AudioSystem.AUDIO_STATUS_OK) {
- AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
- "APM handleDeviceConfigChange failed for A2DP device addr=" + address
- + " codec=" + AudioSystem.audioFormatToString(a2dpCodec))
- .printLog(TAG));
+ if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
+ boolean a2dpCodecChange = false;
+ if (btInfo.mProfile == BluetoothProfile.A2DP) {
+ if (di.mDeviceCodecFormat != audioCodec) {
+ di.mDeviceCodecFormat = audioCodec;
+ mConnectedDevices.replace(key, di);
+ a2dpCodecChange = true;
+ }
+ final int res = mAudioSystem.handleDeviceConfigChange(
+ btInfo.mAudioSystemDevice, address,
+ BtHelper.getName(btDevice), audioCodec);
- int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
- // force A2DP device disconnection in case of error so that AudioService state is
- // consistent with audio policy manager state
- setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btDevice,
- BluetoothProfile.A2DP, BluetoothProfile.STATE_DISCONNECTED,
- musicDevice, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
- } else {
- AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
- "APM handleDeviceConfigChange success for A2DP device addr=" + address
- + " codec=" + AudioSystem.audioFormatToString(a2dpCodec))
- .printLog(TAG));
+ if (res != AudioSystem.AUDIO_STATUS_OK) {
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+ "APM handleDeviceConfigChange failed for A2DP device addr="
+ + address + " codec="
+ + AudioSystem.audioFormatToString(audioCodec))
+ .printLog(TAG));
+
+ // force A2DP device disconnection in case of error so that AudioService
+ // state is consistent with audio policy manager state
+ setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btInfo,
+ BluetoothProfile.STATE_DISCONNECTED));
+ } else {
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+ "APM handleDeviceConfigChange success for A2DP device addr="
+ + address
+ + " codec=" + AudioSystem.audioFormatToString(audioCodec))
+ .printLog(TAG));
+
+ }
+ }
+ if (!a2dpCodecChange) {
+ updateBluetoothPreferredModes_l(btDevice /*connectedDevice*/);
+ }
}
}
mmi.record();
@@ -578,7 +628,7 @@
}
if (!handleDeviceConnection(wdcs.mAttributes,
- wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest)) {
+ wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest, null)) {
// change of connection state failed, bailout
mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed")
.record();
@@ -710,43 +760,51 @@
//------------------------------------------------------------
// preferred/non-default device(s)
- /*package*/ int setPreferredDevicesForStrategySync(int strategy,
+ /*package*/ int setPreferredDevicesForStrategyAndSave(int strategy,
@NonNull List<AudioDeviceAttributes> devices) {
- int status = AudioSystem.ERROR;
-
- try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
- AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
- "setPreferredDevicesForStrategySync, strategy: " + strategy
- + " devices: " + devices)).printLog(TAG));
- status = mAudioSystem.setDevicesRoleForStrategy(
- strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
- }
-
+ final int status = setPreferredDevicesForStrategy(strategy, devices);
if (status == AudioSystem.SUCCESS) {
mDeviceBroker.postSaveSetPreferredDevicesForStrategy(strategy, devices);
}
return status;
}
- /*package*/ int removePreferredDevicesForStrategySync(int strategy) {
+ /*package*/ int setPreferredDevicesForStrategy(int strategy,
+ @NonNull List<AudioDeviceAttributes> devices) {
int status = AudioSystem.ERROR;
-
try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
- "removePreferredDevicesForStrategySync, strategy: "
- + strategy)).printLog(TAG));
-
- status = mAudioSystem.clearDevicesRoleForStrategy(
- strategy, AudioSystem.DEVICE_ROLE_PREFERRED);
+ "setPreferredDevicesForStrategy, strategy: " + strategy
+ + " devices: " + devices)).printLog(TAG));
+ status = setDevicesRoleForStrategy(
+ strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
}
+ return status;
+ }
+ /*package*/ int removePreferredDevicesForStrategyAndSave(int strategy) {
+ final int status = removePreferredDevicesForStrategy(strategy);
if (status == AudioSystem.SUCCESS) {
mDeviceBroker.postSaveRemovePreferredDevicesForStrategy(strategy);
}
return status;
}
- /*package*/ int setDeviceAsNonDefaultForStrategySync(int strategy,
+ /*package*/ int removePreferredDevicesForStrategy(int strategy) {
+ int status = AudioSystem.ERROR;
+
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
+ "removePreferredDevicesForStrategy, strategy: "
+ + strategy)).printLog(TAG));
+
+ status = clearDevicesRoleForStrategy(
+ strategy, AudioSystem.DEVICE_ROLE_PREFERRED);
+ }
+ return status;
+ }
+
+ /*package*/ int setDeviceAsNonDefaultForStrategyAndSave(int strategy,
@NonNull AudioDeviceAttributes device) {
int status = AudioSystem.ERROR;
@@ -755,9 +813,9 @@
devices.add(device);
AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
- "setDeviceAsNonDefaultForStrategySync, strategy: " + strategy
+ "setDeviceAsNonDefaultForStrategyAndSave, strategy: " + strategy
+ " device: " + device)).printLog(TAG));
- status = mAudioSystem.setDevicesRoleForStrategy(
+ status = addDevicesRoleForStrategy(
strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices);
}
@@ -767,7 +825,7 @@
return status;
}
- /*package*/ int removeDeviceAsNonDefaultForStrategySync(int strategy,
+ /*package*/ int removeDeviceAsNonDefaultForStrategyAndSave(int strategy,
@NonNull AudioDeviceAttributes device) {
int status = AudioSystem.ERROR;
@@ -776,10 +834,10 @@
devices.add(device);
AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
- "removeDeviceAsNonDefaultForStrategySync, strategy: "
+ "removeDeviceAsNonDefaultForStrategyAndSave, strategy: "
+ strategy + " devices: " + device)).printLog(TAG));
- status = mAudioSystem.removeDevicesRoleForStrategy(
+ status = removeDevicesRoleForStrategy(
strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices);
}
@@ -810,35 +868,72 @@
mNonDefDevDispatchers.unregister(dispatcher);
}
- /*package*/ int setPreferredDevicesForCapturePresetSync(
+ /*package*/ int setPreferredDevicesForCapturePresetAndSave(
int capturePreset, @NonNull List<AudioDeviceAttributes> devices) {
- int status = AudioSystem.ERROR;
-
- try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
- status = mAudioSystem.setDevicesRoleForCapturePreset(
- capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
- }
-
+ final int status = setPreferredDevicesForCapturePreset(capturePreset, devices);
if (status == AudioSystem.SUCCESS) {
mDeviceBroker.postSaveSetPreferredDevicesForCapturePreset(capturePreset, devices);
}
return status;
}
- /*package*/ int clearPreferredDevicesForCapturePresetSync(int capturePreset) {
- int status = AudioSystem.ERROR;
-
+ private int setPreferredDevicesForCapturePreset(
+ int capturePreset, @NonNull List<AudioDeviceAttributes> devices) {
+ int status = AudioSystem.ERROR;
try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
- status = mAudioSystem.clearDevicesRoleForCapturePreset(
- capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED);
+ status = setDevicesRoleForCapturePreset(
+ capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
}
+ return status;
+ }
+ /*package*/ int clearPreferredDevicesForCapturePresetAndSave(int capturePreset) {
+ final int status = clearPreferredDevicesForCapturePreset(capturePreset);
if (status == AudioSystem.SUCCESS) {
mDeviceBroker.postSaveClearPreferredDevicesForCapturePreset(capturePreset);
}
return status;
}
+ private int clearPreferredDevicesForCapturePreset(int capturePreset) {
+ int status = AudioSystem.ERROR;
+
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ status = clearDevicesRoleForCapturePreset(
+ capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED);
+ }
+ return status;
+ }
+
+ private int addDevicesRoleForCapturePreset(int capturePreset, int role,
+ @NonNull List<AudioDeviceAttributes> devices) {
+ return addDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
+ return mAudioSystem.addDevicesRoleForCapturePreset(p, r, d);
+ }, capturePreset, role, devices);
+ }
+
+ private int removeDevicesRoleForCapturePreset(int capturePreset, int role,
+ @NonNull List<AudioDeviceAttributes> devices) {
+ return removeDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
+ return mAudioSystem.removeDevicesRoleForCapturePreset(p, r, d);
+ }, capturePreset, role, devices);
+ }
+
+ private int setDevicesRoleForCapturePreset(int capturePreset, int role,
+ @NonNull List<AudioDeviceAttributes> devices) {
+ return setDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
+ return mAudioSystem.addDevicesRoleForCapturePreset(p, r, d);
+ }, (p, r, d) -> {
+ return mAudioSystem.clearDevicesRoleForCapturePreset(p, r);
+ }, capturePreset, role, devices);
+ }
+
+ private int clearDevicesRoleForCapturePreset(int capturePreset, int role) {
+ return clearDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
+ return mAudioSystem.clearDevicesRoleForCapturePreset(p, r);
+ }, capturePreset, role);
+ }
+
/*package*/ void registerCapturePresetDevicesRoleDispatcher(
@NonNull ICapturePresetDevicesRoleDispatcher dispatcher) {
mDevRoleCapturePresetDispatchers.register(dispatcher);
@@ -849,7 +944,208 @@
mDevRoleCapturePresetDispatchers.unregister(dispatcher);
}
- //-----------------------------------------------------------------------
+ private int addDevicesRoleForStrategy(int strategy, int role,
+ @NonNull List<AudioDeviceAttributes> devices) {
+ return addDevicesRole(mAppliedStrategyRoles, (s, r, d) -> {
+ return mAudioSystem.setDevicesRoleForStrategy(s, r, d);
+ }, strategy, role, devices);
+ }
+
+ private int removeDevicesRoleForStrategy(int strategy, int role,
+ @NonNull List<AudioDeviceAttributes> devices) {
+ return removeDevicesRole(mAppliedStrategyRoles, (s, r, d) -> {
+ return mAudioSystem.removeDevicesRoleForStrategy(s, r, d);
+ }, strategy, role, devices);
+ }
+
+ private int setDevicesRoleForStrategy(int strategy, int role,
+ @NonNull List<AudioDeviceAttributes> devices) {
+ return setDevicesRole(mAppliedStrategyRoles, (s, r, d) -> {
+ return mAudioSystem.setDevicesRoleForStrategy(s, r, d);
+ }, (s, r, d) -> {
+ return mAudioSystem.clearDevicesRoleForStrategy(s, r);
+ }, strategy, role, devices);
+ }
+
+ private int clearDevicesRoleForStrategy(int strategy, int role) {
+ return clearDevicesRole(mAppliedStrategyRoles, (s, r, d) -> {
+ return mAudioSystem.clearDevicesRoleForStrategy(s, r);
+ }, strategy, role);
+ }
+
+ //------------------------------------------------------------
+ // Cache for applied roles for strategies and devices. The cache avoids reapplying the
+ // same list of devices for a given role and strategy and the corresponding systematic
+ // redundant work in audio policy manager and audio flinger.
+ // The key is the pair <Strategy , Role> and the value is the current list of devices.
+
+ private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>>
+ mAppliedStrategyRoles = new ArrayMap<>();
+
+ // Cache for applied roles for capture presets and devices. The cache avoids reapplying the
+ // same list of devices for a given role and capture preset and the corresponding systematic
+ // redundant work in audio policy manager and audio flinger.
+ // The key is the pair <Preset , Role> and the value is the current list of devices.
+ private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>>
+ mAppliedPresetRoles = new ArrayMap<>();
+
+ interface AudioSystemInterface {
+ int deviceRoleAction(int usecase, int role, @Nullable List<AudioDeviceAttributes> devices);
+ }
+
+ private int addDevicesRole(
+ ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap,
+ AudioSystemInterface asi,
+ int useCase, int role, @NonNull List<AudioDeviceAttributes> devices) {
+ synchronized (rolesMap) {
+ Pair<Integer, Integer> key = new Pair<>(useCase, role);
+ List<AudioDeviceAttributes> roleDevices = new ArrayList<>();
+ List<AudioDeviceAttributes> appliedDevices = new ArrayList<>();
+
+ if (rolesMap.containsKey(key)) {
+ roleDevices = rolesMap.get(key);
+ for (AudioDeviceAttributes device : devices) {
+ if (!roleDevices.contains(device)) {
+ appliedDevices.add(device);
+ }
+ }
+ } else {
+ appliedDevices.addAll(devices);
+ }
+ if (appliedDevices.isEmpty()) {
+ return AudioSystem.SUCCESS;
+ }
+ final int status = asi.deviceRoleAction(useCase, role, appliedDevices);
+ if (status == AudioSystem.SUCCESS) {
+ roleDevices.addAll(appliedDevices);
+ rolesMap.put(key, roleDevices);
+ }
+ return status;
+ }
+ }
+
+ private int removeDevicesRole(
+ ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap,
+ AudioSystemInterface asi,
+ int useCase, int role, @NonNull List<AudioDeviceAttributes> devices) {
+ synchronized (rolesMap) {
+ Pair<Integer, Integer> key = new Pair<>(useCase, role);
+ if (!rolesMap.containsKey(key)) {
+ return AudioSystem.SUCCESS;
+ }
+ List<AudioDeviceAttributes> roleDevices = rolesMap.get(key);
+ List<AudioDeviceAttributes> appliedDevices = new ArrayList<>();
+ for (AudioDeviceAttributes device : devices) {
+ if (roleDevices.contains(device)) {
+ appliedDevices.add(device);
+ }
+ }
+ if (appliedDevices.isEmpty()) {
+ return AudioSystem.SUCCESS;
+ }
+ final int status = asi.deviceRoleAction(useCase, role, appliedDevices);
+ if (status == AudioSystem.SUCCESS) {
+ roleDevices.removeAll(appliedDevices);
+ if (roleDevices.isEmpty()) {
+ rolesMap.remove(key);
+ } else {
+ rolesMap.put(key, roleDevices);
+ }
+ }
+ return status;
+ }
+ }
+
+ private int setDevicesRole(
+ ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap,
+ AudioSystemInterface addOp,
+ AudioSystemInterface clearOp,
+ int useCase, int role, @NonNull List<AudioDeviceAttributes> devices) {
+ synchronized (rolesMap) {
+ Pair<Integer, Integer> key = new Pair<>(useCase, role);
+ List<AudioDeviceAttributes> roleDevices = new ArrayList<>();
+ List<AudioDeviceAttributes> appliedDevices = new ArrayList<>();
+
+ if (rolesMap.containsKey(key)) {
+ roleDevices = rolesMap.get(key);
+ boolean equal = false;
+ if (roleDevices.size() == devices.size()) {
+ roleDevices.retainAll(devices);
+ equal = roleDevices.size() == devices.size();
+ }
+ if (!equal) {
+ clearOp.deviceRoleAction(useCase, role, null);
+ roleDevices.clear();
+ appliedDevices.addAll(devices);
+ }
+ } else {
+ appliedDevices.addAll(devices);
+ }
+ if (appliedDevices.isEmpty()) {
+ return AudioSystem.SUCCESS;
+ }
+ final int status = addOp.deviceRoleAction(useCase, role, appliedDevices);
+ if (status == AudioSystem.SUCCESS) {
+ roleDevices.addAll(appliedDevices);
+ rolesMap.put(key, roleDevices);
+ }
+ return status;
+ }
+ }
+
+ private int clearDevicesRole(
+ ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap,
+ AudioSystemInterface asi, int useCase, int role) {
+ synchronized (rolesMap) {
+ Pair<Integer, Integer> key = new Pair<>(useCase, role);
+ if (!rolesMap.containsKey(key)) {
+ return AudioSystem.SUCCESS;
+ }
+ final int status = asi.deviceRoleAction(useCase, role, null);
+ if (status == AudioSystem.SUCCESS) {
+ rolesMap.remove(key);
+ }
+ return status;
+ }
+ }
+
+ @GuardedBy("mDevicesLock")
+ private void purgeDevicesRoles_l() {
+ purgeRoles(mAppliedStrategyRoles, (s, r, d) -> {
+ return mAudioSystem.removeDevicesRoleForStrategy(s, r, d); });
+ purgeRoles(mAppliedPresetRoles, (p, r, d) -> {
+ return mAudioSystem.removeDevicesRoleForCapturePreset(p, r, d); });
+ }
+
+ @GuardedBy("mDevicesLock")
+ private void purgeRoles(
+ ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap,
+ AudioSystemInterface asi) {
+ synchronized (rolesMap) {
+ Iterator<Map.Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>>> itRole =
+ rolesMap.entrySet().iterator();
+ while (itRole.hasNext()) {
+ Map.Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>> entry =
+ itRole.next();
+ Pair<Integer, Integer> keyRole = entry.getKey();
+ Iterator<AudioDeviceAttributes> itDev = rolesMap.get(keyRole).iterator();
+ while (itDev.hasNext()) {
+ AudioDeviceAttributes ada = itDev.next();
+ final String devKey = DeviceInfo.makeDeviceListKey(ada.getInternalType(),
+ ada.getAddress());
+ if (mConnectedDevices.get(devKey) == null) {
+ asi.deviceRoleAction(keyRole.first, keyRole.second, Arrays.asList(ada));
+ itDev.remove();
+ }
+ }
+ if (rolesMap.get(keyRole).isEmpty()) {
+ itRole.remove();
+ }
+ }
+ }
+ }
+
+//-----------------------------------------------------------------------
/**
* Check if a device is in the list of connected devices
@@ -871,10 +1167,11 @@
* @param connect true if connection
* @param isForTesting if true, not calling AudioSystem for the connection as this is
* just for testing
+ * @param btDevice the corresponding Bluetooth device when relevant.
* @return false if an error was reported by AudioSystem
*/
/*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect,
- boolean isForTesting) {
+ boolean isForTesting, @Nullable BluetoothDevice btDevice) {
int device = attributes.getInternalType();
String address = attributes.getAddress();
String deviceName = attributes.getName();
@@ -889,6 +1186,7 @@
.set(MediaMetrics.Property.MODE, connect
? MediaMetrics.Value.CONNECT : MediaMetrics.Value.DISCONNECT)
.set(MediaMetrics.Property.NAME, deviceName);
+ boolean status = false;
synchronized (mDevicesLock) {
final String deviceKey = DeviceInfo.makeDeviceListKey(device, address);
if (AudioService.DEBUG_DEVICES) {
@@ -916,24 +1214,31 @@
.record();
return false;
}
- mConnectedDevices.put(deviceKey, new DeviceInfo(
- device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+ mConnectedDevices.put(deviceKey, new DeviceInfo(device, deviceName, address));
mDeviceBroker.postAccessoryPlugMediaUnmute(device);
- mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
- return true;
+ status = true;
} else if (!connect && isConnected) {
mAudioSystem.setDeviceConnectionState(attributes,
AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT);
// always remove even if disconnection failed
mConnectedDevices.remove(deviceKey);
- mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
- return true;
+ status = true;
}
- Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey
- + ", deviceSpec=" + di + ", connect=" + connect);
+ if (status) {
+ if (AudioSystem.isBluetoothScoDevice(device)) {
+ updateBluetoothPreferredModes_l(connect ? btDevice : null /*connectedDevice*/);
+ if (!connect) {
+ purgeDevicesRoles_l();
+ }
+ }
+ mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
+ } else {
+ Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey
+ + ", deviceSpec=" + di + ", connect=" + connect);
+ mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED).record();
+ }
}
- mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED).record();
- return false;
+ return status;
}
@@ -1142,15 +1447,20 @@
// Internal utilities
@GuardedBy("mDevicesLock")
- private void makeA2dpDeviceAvailable(String address, String name, String eventSource,
- int a2dpCodec) {
+ private void makeA2dpDeviceAvailable(AudioDeviceBroker.BtDeviceInfo btInfo,
+ String eventSource) {
+ final String address = btInfo.mDevice.getAddress();
+ final String name = BtHelper.getName(btInfo.mDevice);
+ final int a2dpCodec = btInfo.mCodec;
+
// enable A2DP before notifying A2DP connection to avoid unnecessary processing in
// audio policy manager
mDeviceBroker.setBluetoothA2dpOnInt(true, true /*fromA2dp*/, eventSource);
// at this point there could be another A2DP device already connected in APM, but it
// doesn't matter as this new one will overwrite the previous one
- final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
- AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name),
+ AudioDeviceAttributes ada = new AudioDeviceAttributes(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name);
+ final int res = mAudioSystem.setDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_AVAILABLE, a2dpCodec);
// TODO: log in MediaMetrics once distinction between connection failure and
@@ -1172,8 +1482,7 @@
// The convention for head tracking sensors associated with A2DP devices is to
// use a UUID derived from the MAC address as follows:
// time_low = 0, time_mid = 0, time_hi = 0, clock_seq = 0, node = MAC Address
- UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(
- new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
+ UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada);
final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
address, a2dpCodec, sensorUuid);
final String diKey = di.getKey();
@@ -1184,6 +1493,208 @@
mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/);
+
+ updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/);
+ }
+
+ static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER,
+ AudioSource.VOICE_RECOGNITION, AudioSource.VOICE_COMMUNICATION,
+ AudioSource.UNPROCESSED, AudioSource.VOICE_PERFORMANCE, AudioSource.HOTWORD};
+
+ // reflects system property persist.bluetooth.enable_dual_mode_audio
+ final boolean mBluetoothDualModeEnabled;
+ /**
+ * Goes over all connected Bluetooth devices and set the audio policy device role to DISABLED
+ * or not according to their own and other devices modes.
+ * The top priority is given to LE devices, then SCO ,then A2DP.
+ */
+ @GuardedBy("mDevicesLock")
+ private void applyConnectedDevicesRoles_l() {
+ if (!mBluetoothDualModeEnabled) {
+ return;
+ }
+ DeviceInfo leOutDevice =
+ getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_BLE_SET);
+ DeviceInfo leInDevice =
+ getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_IN_ALL_BLE_SET);
+ DeviceInfo a2dpDevice =
+ getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_A2DP_SET);
+ DeviceInfo scoOutDevice =
+ getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_SCO_SET);
+ DeviceInfo scoInDevice =
+ getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_IN_ALL_SCO_SET);
+ boolean disableA2dp = (leOutDevice != null && leOutDevice.isOutputOnlyModeEnabled());
+ boolean disableSco = (leOutDevice != null && leOutDevice.isDuplexModeEnabled())
+ || (leInDevice != null && leInDevice.isDuplexModeEnabled());
+ AudioDeviceAttributes communicationDevice =
+ mDeviceBroker.mActiveCommunicationDevice == null
+ ? null : ((mDeviceBroker.isInCommunication()
+ && mDeviceBroker.mActiveCommunicationDevice != null)
+ ? new AudioDeviceAttributes(mDeviceBroker.mActiveCommunicationDevice)
+ : null);
+
+ if (AudioService.DEBUG_DEVICES) {
+ Log.i(TAG, "applyConnectedDevicesRoles_l\n - leOutDevice: " + leOutDevice
+ + "\n - leInDevice: " + leInDevice
+ + "\n - a2dpDevice: " + a2dpDevice
+ + "\n - scoOutDevice: " + scoOutDevice
+ + "\n - scoInDevice: " + scoInDevice
+ + "\n - disableA2dp: " + disableA2dp
+ + ", disableSco: " + disableSco);
+ }
+
+ for (DeviceInfo di : mConnectedDevices.values()) {
+ if (!AudioSystem.isBluetoothDevice(di.mDeviceType)) {
+ continue;
+ }
+ AudioDeviceAttributes ada =
+ new AudioDeviceAttributes(di.mDeviceType, di.mDeviceAddress, di.mDeviceName);
+ if (AudioService.DEBUG_DEVICES) {
+ Log.i(TAG, " + checking Device: " + ada);
+ }
+ if (ada.equalTypeAddress(communicationDevice)) {
+ continue;
+ }
+
+ if (AudioSystem.isBluetoothOutDevice(di.mDeviceType)) {
+ for (AudioProductStrategy strategy : mStrategies) {
+ boolean disable = false;
+ if (strategy.getId() == mDeviceBroker.mCommunicationStrategyId) {
+ if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
+ disable = disableSco || !di.isDuplexModeEnabled();
+ } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) {
+ disable = !di.isDuplexModeEnabled();
+ }
+ } else {
+ if (AudioSystem.isBluetoothA2dpOutDevice(di.mDeviceType)) {
+ disable = disableA2dp || !di.isOutputOnlyModeEnabled();
+ } else if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
+ disable = disableSco || !di.isOutputOnlyModeEnabled();
+ } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) {
+ disable = !di.isOutputOnlyModeEnabled();
+ }
+ }
+ if (AudioService.DEBUG_DEVICES) {
+ Log.i(TAG, " - strategy: " + strategy.getId()
+ + ", disable: " + disable);
+ }
+ if (disable) {
+ addDevicesRoleForStrategy(strategy.getId(),
+ AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
+ } else {
+ removeDevicesRoleForStrategy(strategy.getId(),
+ AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
+ }
+ }
+ }
+ if (AudioSystem.isBluetoothInDevice(di.mDeviceType)) {
+ for (int capturePreset : CAPTURE_PRESETS) {
+ boolean disable = false;
+ if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
+ disable = disableSco || !di.isDuplexModeEnabled();
+ } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) {
+ disable = !di.isDuplexModeEnabled();
+ }
+ if (AudioService.DEBUG_DEVICES) {
+ Log.i(TAG, " - capturePreset: " + capturePreset
+ + ", disable: " + disable);
+ }
+ if (disable) {
+ addDevicesRoleForCapturePreset(capturePreset,
+ AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
+ } else {
+ removeDevicesRoleForCapturePreset(capturePreset,
+ AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
+ }
+ }
+ }
+ }
+ }
+
+ /* package */ void applyConnectedDevicesRoles() {
+ synchronized (mDevicesLock) {
+ applyConnectedDevicesRoles_l();
+ }
+ }
+
+ @GuardedBy("mDevicesLock")
+ int checkProfileIsConnected(int profile) {
+ switch (profile) {
+ case BluetoothProfile.HEADSET:
+ if (getFirstConnectedDeviceOfTypes(
+ AudioSystem.DEVICE_OUT_ALL_SCO_SET) != null
+ || getFirstConnectedDeviceOfTypes(
+ AudioSystem.DEVICE_IN_ALL_SCO_SET) != null) {
+ return profile;
+ }
+ break;
+ case BluetoothProfile.A2DP:
+ if (getFirstConnectedDeviceOfTypes(
+ AudioSystem.DEVICE_OUT_ALL_A2DP_SET) != null) {
+ return profile;
+ }
+ break;
+ case BluetoothProfile.LE_AUDIO:
+ case BluetoothProfile.LE_AUDIO_BROADCAST:
+ if (getFirstConnectedDeviceOfTypes(
+ AudioSystem.DEVICE_OUT_ALL_BLE_SET) != null
+ || getFirstConnectedDeviceOfTypes(
+ AudioSystem.DEVICE_IN_ALL_BLE_SET) != null) {
+ return profile;
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
+ }
+
+ @GuardedBy("mDevicesLock")
+ private void updateBluetoothPreferredModes_l(BluetoothDevice connectedDevice) {
+ if (!mBluetoothDualModeEnabled) {
+ return;
+ }
+ HashSet<String> processedAddresses = new HashSet<>(0);
+ for (DeviceInfo di : mConnectedDevices.values()) {
+ if (!AudioSystem.isBluetoothDevice(di.mDeviceType)
+ || processedAddresses.contains(di.mDeviceAddress)) {
+ continue;
+ }
+ Bundle preferredProfiles = BtHelper.getPreferredAudioProfiles(di.mDeviceAddress);
+ if (AudioService.DEBUG_DEVICES) {
+ Log.i(TAG, "updateBluetoothPreferredModes_l processing device address: "
+ + di.mDeviceAddress + ", preferredProfiles: " + preferredProfiles);
+ }
+ for (DeviceInfo di2 : mConnectedDevices.values()) {
+ if (!AudioSystem.isBluetoothDevice(di2.mDeviceType)
+ || !di.mDeviceAddress.equals(di2.mDeviceAddress)) {
+ continue;
+ }
+ int profile = BtHelper.getProfileFromType(di2.mDeviceType);
+ if (profile == 0) {
+ continue;
+ }
+ int preferredProfile = checkProfileIsConnected(
+ preferredProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX));
+ if (preferredProfile == profile || preferredProfile == 0) {
+ di2.setModeEnabled(BluetoothAdapter.AUDIO_MODE_DUPLEX);
+ } else {
+ di2.setModeDisabled(BluetoothAdapter.AUDIO_MODE_DUPLEX);
+ }
+ preferredProfile = checkProfileIsConnected(
+ preferredProfiles.getInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY));
+ if (preferredProfile == profile || preferredProfile == 0) {
+ di2.setModeEnabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY);
+ } else {
+ di2.setModeDisabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY);
+ }
+ }
+ processedAddresses.add(di.mDeviceAddress);
+ }
+ applyConnectedDevicesRoles_l();
+ if (connectedDevice != null) {
+ mDeviceBroker.postNotifyPreferredAudioProfileApplied(connectedDevice);
+ }
}
@GuardedBy("mDevicesLock")
@@ -1231,6 +1742,8 @@
// Remove A2DP routes as well
setCurrentAudioRouteNameIfPossible(null, true /*fromA2dp*/);
mmi.record();
+ updateBluetoothPreferredModes_l(null /*connectedDevice*/);
+ purgeDevicesRoles_l();
}
@GuardedBy("mDevicesLock")
@@ -1260,8 +1773,7 @@
AudioSystem.AUDIO_FORMAT_DEFAULT);
mConnectedDevices.put(
DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
- new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "",
- address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+ new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "", address));
}
@GuardedBy("mDevicesLock")
@@ -1287,8 +1799,7 @@
AudioSystem.AUDIO_FORMAT_DEFAULT);
mConnectedDevices.put(
DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address),
- new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name,
- address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+ new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name, address));
mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID);
mDeviceBroker.postApplyVolumeOnDevice(streamType,
AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
@@ -1326,29 +1837,56 @@
* @return true if a DEVICE_OUT_HEARING_AID is connected, false otherwise.
*/
boolean isHearingAidConnected() {
+ return getFirstConnectedDeviceOfTypes(
+ Sets.newHashSet(AudioSystem.DEVICE_OUT_HEARING_AID)) != null;
+ }
+
+ /**
+ * Returns a DeviceInfo for the first connected device matching one of the supplied types
+ */
+ private DeviceInfo getFirstConnectedDeviceOfTypes(Set<Integer> internalTypes) {
+ List<DeviceInfo> devices = getConnectedDevicesOfTypes(internalTypes);
+ return devices.isEmpty() ? null : devices.get(0);
+ }
+
+ /**
+ * Returns a list of connected devices matching one of the supplied types
+ */
+ private List<DeviceInfo> getConnectedDevicesOfTypes(Set<Integer> internalTypes) {
+ ArrayList<DeviceInfo> devices = new ArrayList<>();
synchronized (mDevicesLock) {
for (DeviceInfo di : mConnectedDevices.values()) {
- if (di.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
- return true;
+ if (internalTypes.contains(di.mDeviceType)) {
+ devices.add(di);
}
}
- return false;
}
+ return devices;
+ }
+
+ /* package */ AudioDeviceAttributes getDeviceOfType(int type) {
+ DeviceInfo di = getFirstConnectedDeviceOfTypes(Sets.newHashSet(type));
+ return di == null ? null : new AudioDeviceAttributes(
+ di.mDeviceType, di.mDeviceAddress, di.mDeviceName);
}
@GuardedBy("mDevicesLock")
- private void makeLeAudioDeviceAvailable(String address, String name, int streamType,
- int volumeIndex, int device, String eventSource) {
+ private void makeLeAudioDeviceAvailable(
+ AudioDeviceBroker.BtDeviceInfo btInfo, int streamType, String eventSource) {
+ final String address = btInfo.mDevice.getAddress();
+ final String name = BtHelper.getName(btInfo.mDevice);
+ final int volumeIndex = btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10;
+ final int device = btInfo.mAudioSystemDevice;
+
if (device != AudioSystem.DEVICE_NONE) {
/* Audio Policy sees Le Audio similar to A2DP. Let's make sure
* AUDIO_POLICY_FORCE_NO_BT_A2DP is not set
*/
mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource);
- final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
- device, address, name),
- AudioSystem.DEVICE_STATE_AVAILABLE,
- AudioSystem.AUDIO_FORMAT_DEFAULT);
+ AudioDeviceAttributes ada = new AudioDeviceAttributes(device, address, name);
+ final int res = AudioSystem.setDeviceConnectionState(ada,
+ AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT);
if (res != AudioSystem.AUDIO_STATUS_OK) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"APM failed to make available LE Audio device addr=" + address
@@ -1359,12 +1897,13 @@
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"LE Audio device addr=" + address + " now available").printLog(TAG));
}
-
// Reset LEA suspend state each time a new sink is connected
mDeviceBroker.clearLeAudioSuspended();
+ UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada);
mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address),
- new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+ new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT,
+ sensorUuid));
mDeviceBroker.postAccessoryPlugMediaUnmute(device);
setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
}
@@ -1380,6 +1919,8 @@
final int maxIndex = mDeviceBroker.getMaxVssVolumeForStream(streamType);
mDeviceBroker.postSetLeAudioVolumeIndex(leAudioVolIndex, maxIndex, streamType);
mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "makeLeAudioDeviceAvailable");
+
+ updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/);
}
@GuardedBy("mDevicesLock")
@@ -1404,6 +1945,8 @@
}
setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/);
+ updateBluetoothPreferredModes_l(null /*connectedDevice*/);
+ purgeDevicesRoles_l();
}
@GuardedBy("mDevicesLock")
@@ -1739,18 +2282,6 @@
}
}
- /* package */ AudioDeviceAttributes getDeviceOfType(int type) {
- synchronized (mDevicesLock) {
- for (DeviceInfo di : mConnectedDevices.values()) {
- if (di.mDeviceType == type) {
- return new AudioDeviceAttributes(
- di.mDeviceType, di.mDeviceAddress, di.mDeviceName);
- }
- }
- }
- return null;
- }
-
//----------------------------------------------------------
// For tests only
@@ -1761,10 +2292,12 @@
*/
@VisibleForTesting
public boolean isA2dpDeviceConnected(@NonNull BluetoothDevice device) {
- final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
- device.getAddress());
- synchronized (mDevicesLock) {
- return (mConnectedDevices.get(key) != null);
+ for (DeviceInfo di : getConnectedDevicesOfTypes(
+ Sets.newHashSet(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP))) {
+ if (di.mDeviceAddress.equals(device.getAddress())) {
+ return true;
+ }
}
+ return false;
}
}
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index d066340..4343894 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -445,7 +445,7 @@
}
/**
- * Same as {@link AudioSystem#removeDevicesRoleForCapturePreset(int, int, int[], String[])}
+ * Same as {@link AudioSystem#removeDevicesRoleForCapturePreset(int, int, List)}
* @param capturePreset
* @param role
* @param devicesToRemove
@@ -458,6 +458,19 @@
}
/**
+ * Same as {@link AudioSystem#addDevicesRoleForCapturePreset(int, int, List)}
+ * @param capturePreset the capture preset to configure
+ * @param role the role of the devices
+ * @param devices the list of devices to be added as role for the given capture preset
+ * @return {@link #SUCCESS} if successfully added
+ */
+ public int addDevicesRoleForCapturePreset(
+ int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices) {
+ invalidateRoutingCache();
+ return AudioSystem.addDevicesRoleForCapturePreset(capturePreset, role, devices);
+ }
+
+ /**
* Same as {@link AudioSystem#}
* @param capturePreset
* @param role
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 8c27c3e..e46c3cc 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -33,6 +33,7 @@
import android.media.AudioSystem;
import android.media.BluetoothProfileConnectionInfo;
import android.os.Binder;
+import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
@@ -150,60 +151,12 @@
}
}
- //----------------------------------------------------------------------
- /*package*/ static class BluetoothA2dpDeviceInfo {
- private final @NonNull BluetoothDevice mBtDevice;
- private final int mVolume;
- private final @AudioSystem.AudioFormatNativeEnumForBtCodec int mCodec;
-
- BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice) {
- this(btDevice, -1, AudioSystem.AUDIO_FORMAT_DEFAULT);
- }
-
- BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice, int volume, int codec) {
- mBtDevice = btDevice;
- mVolume = volume;
- mCodec = codec;
- }
-
- public @NonNull BluetoothDevice getBtDevice() {
- return mBtDevice;
- }
-
- public int getVolume() {
- return mVolume;
- }
-
- public @AudioSystem.AudioFormatNativeEnumForBtCodec int getCodec() {
- return mCodec;
- }
-
- // redefine equality op so we can match messages intended for this device
- @Override
- public boolean equals(Object o) {
- if (o == null) {
- return false;
- }
- if (this == o) {
- return true;
- }
- if (o instanceof BluetoothA2dpDeviceInfo) {
- return mBtDevice.equals(((BluetoothA2dpDeviceInfo) o).getBtDevice());
- }
- return false;
- }
-
-
- }
-
// A2DP device events
/*package*/ static final int EVENT_DEVICE_CONFIG_CHANGE = 0;
- /*package*/ static final int EVENT_ACTIVE_DEVICE_CHANGE = 1;
- /*package*/ static String a2dpDeviceEventToString(int event) {
+ /*package*/ static String deviceEventToString(int event) {
switch (event) {
case EVENT_DEVICE_CONFIG_CHANGE: return "DEVICE_CONFIG_CHANGE";
- case EVENT_ACTIVE_DEVICE_CHANGE: return "ACTIVE_DEVICE_CHANGE";
default:
return new String("invalid event:" + event);
}
@@ -620,11 +573,12 @@
return btHeadsetDeviceToAudioDevice(mBluetoothHeadsetDevice);
}
- private AudioDeviceAttributes btHeadsetDeviceToAudioDevice(BluetoothDevice btDevice) {
+ private static AudioDeviceAttributes btHeadsetDeviceToAudioDevice(BluetoothDevice btDevice) {
if (btDevice == null) {
return new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, "");
}
String address = btDevice.getAddress();
+ String name = getName(btDevice);
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
address = "";
}
@@ -646,7 +600,7 @@
+ " btClass: " + (btClass == null ? "Unknown" : btClass)
+ " nativeType: " + nativeType + " address: " + address);
}
- return new AudioDeviceAttributes(nativeType, address);
+ return new AudioDeviceAttributes(nativeType, address, name);
}
private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) {
@@ -655,12 +609,9 @@
}
int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
AudioDeviceAttributes audioDevice = btHeadsetDeviceToAudioDevice(btDevice);
- String btDeviceName = getName(btDevice);
boolean result = false;
if (isActive) {
- result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
- audioDevice.getInternalType(), audioDevice.getAddress(), btDeviceName),
- isActive);
+ result |= mDeviceBroker.handleDeviceConnection(audioDevice, isActive, btDevice);
} else {
int[] outDeviceTypes = {
AudioSystem.DEVICE_OUT_BLUETOOTH_SCO,
@@ -669,14 +620,14 @@
};
for (int outDeviceType : outDeviceTypes) {
result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
- outDeviceType, audioDevice.getAddress(), btDeviceName),
- isActive);
+ outDeviceType, audioDevice.getAddress(), audioDevice.getName()),
+ isActive, btDevice);
}
}
// handleDeviceConnection() && result to make sure the method get executed
result = mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
- inDevice, audioDevice.getAddress(), btDeviceName),
- isActive) && result;
+ inDevice, audioDevice.getAddress(), audioDevice.getName()),
+ isActive, btDevice) && result;
return result;
}
@@ -973,6 +924,30 @@
}
}
+ /*package */ static int getProfileFromType(int deviceType) {
+ if (AudioSystem.isBluetoothA2dpOutDevice(deviceType)) {
+ return BluetoothProfile.A2DP;
+ } else if (AudioSystem.isBluetoothScoDevice(deviceType)) {
+ return BluetoothProfile.HEADSET;
+ } else if (AudioSystem.isBluetoothLeDevice(deviceType)) {
+ return BluetoothProfile.LE_AUDIO;
+ }
+ return 0; // 0 is not a valid profile
+ }
+
+ /*package */ static Bundle getPreferredAudioProfiles(String address) {
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ return adapter.getPreferredAudioProfiles(adapter.getRemoteDevice(address));
+ }
+
+ /**
+ * Notifies Bluetooth framework that new preferred audio profiles for Bluetooth devices
+ * have been applied.
+ */
+ public static void onNotifyPreferredAudioProfileApplied(BluetoothDevice btDevice) {
+ BluetoothAdapter.getDefaultAdapter().notifyActiveDeviceChangeApplied(btDevice);
+ }
+
/**
* Returns the string equivalent for the btDeviceClass class.
*/
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 7cdea8d..aece17e7 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -34,11 +34,13 @@
import android.media.ISoundDoseCallback;
import android.media.SoundDoseRecord;
import android.os.Binder;
+import android.os.HandlerExecutor;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
@@ -57,6 +59,7 @@
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;
@@ -119,6 +122,8 @@
private static final int SAFE_MEDIA_VOLUME_UNINITIALIZED = -1;
+ private static final String FEATURE_FLAG_ENABLE_CSD = "enable_csd";
+
private final EventLogger mLogger = new EventLogger(AudioService.LOG_NB_EVENTS_SOUND_DOSE,
"CSD updates");
@@ -168,7 +173,7 @@
@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 +200,7 @@
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 +227,7 @@
}
public void onNewCsdValue(float currentCsd, SoundDoseRecord[] records) {
- if (!mEnableCsd) {
+ if (!mEnableCsd.get()) {
Log.w(TAG, "onNewCsdValue: csd not supported, ignoring value");
return;
}
@@ -272,8 +277,6 @@
mContext = context;
- mEnableCsd = mContext.getResources().getBoolean(R.bool.config_audio_csd_enabled_default);
- initCsd();
initSafeVolumes();
mSafeMediaVolumeState = mSettings.getGlobalInt(audioService.getContentResolver(),
@@ -285,8 +288,16 @@
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);
+
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_MEDIA,
+ new HandlerExecutor(mAudioHandler),
+ p -> updateCsdEnabled("onPropertiesChanged"));
}
void initSafeVolumes() {
@@ -300,8 +311,6 @@
SAFE_MEDIA_VOLUME_UNINITIALIZED);
mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLE_BROADCAST,
SAFE_MEDIA_VOLUME_UNINITIALIZED);
- mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_HEARING_AID,
- SAFE_MEDIA_VOLUME_UNINITIALIZED);
mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES,
SAFE_MEDIA_VOLUME_UNINITIALIZED);
// TODO(b/278265907): enable A2DP when we can distinguish A2DP headsets
@@ -310,7 +319,7 @@
}
float getOutputRs2UpperBound() {
- if (!mEnableCsd) {
+ if (!mEnableCsd.get()) {
return 0.f;
}
@@ -329,7 +338,7 @@
}
void setOutputRs2UpperBound(float rs2Value) {
- if (!mEnableCsd) {
+ if (!mEnableCsd.get()) {
return;
}
@@ -347,7 +356,7 @@
}
float getCsd() {
- if (!mEnableCsd) {
+ if (!mEnableCsd.get()) {
return -1.f;
}
@@ -366,7 +375,7 @@
}
void setCsd(float csd) {
- if (!mEnableCsd) {
+ if (!mEnableCsd.get()) {
return;
}
@@ -400,7 +409,7 @@
}
void resetCsdTimeouts() {
- if (!mEnableCsd) {
+ if (!mEnableCsd.get()) {
return;
}
@@ -416,7 +425,7 @@
}
void forceUseFrameworkMel(boolean useFrameworkMel) {
- if (!mEnableCsd) {
+ if (!mEnableCsd.get()) {
return;
}
@@ -434,7 +443,7 @@
}
void forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices) {
- if (!mEnableCsd) {
+ if (!mEnableCsd.get()) {
return;
}
@@ -454,7 +463,7 @@
}
boolean isCsdEnabled() {
- if (!mEnableCsd) {
+ if (!mEnableCsd.get()) {
return false;
}
@@ -697,8 +706,8 @@
}
/*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 +728,11 @@
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 +754,7 @@
private void updateDoseAttenuation(int newIndex, int device, int streamType,
boolean isAbsoluteVolume) {
- if (!mEnableCsd) {
+ if (!mEnableCsd.get()) {
return;
}
@@ -775,17 +786,19 @@
}
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 +842,6 @@
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);
@@ -861,6 +873,28 @@
persistedState, /*arg2=*/0,
/*obj=*/null), /*delay=*/0);
}
+
+ updateCsdEnabled(caller);
+ }
+ }
+
+ private void updateCsdEnabled(String caller) {
+ boolean newEnableCsd = SystemProperties.getBoolean("audio.safemedia.force", false);
+ if (!newEnableCsd) {
+ final String featureFlagEnableCsdValue = DeviceConfig.getProperty(
+ DeviceConfig.NAMESPACE_MEDIA,
+ FEATURE_FLAG_ENABLE_CSD);
+ if (featureFlagEnableCsdValue != null) {
+ newEnableCsd = Boolean.parseBoolean(featureFlagEnableCsdValue);
+ } else {
+ newEnableCsd = mContext.getResources().getBoolean(
+ R.bool.config_safe_sound_dosage_enabled);
+ }
+ }
+
+ if (mEnableCsd.compareAndSet(!newEnableCsd, newEnableCsd)) {
+ Log.i(TAG, caller + ": enable CSD " + newEnableCsd);
+ initCsd();
}
}
@@ -913,7 +947,7 @@
// 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/biometrics/sensors/SensorList.java b/services/core/java/com/android/server/biometrics/sensors/SensorList.java
new file mode 100644
index 0000000..1cff92f
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/SensorList.java
@@ -0,0 +1,97 @@
+/*
+ * 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.biometrics.sensors;
+
+import android.app.IActivityManager;
+import android.app.SynchronousUserSwitchObserver;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Slog;
+import android.util.SparseArray;
+
+/**
+ * Keep track of the sensors that is supported by the HAL.
+ * @param <T> T is either face sensor or fingerprint sensor.
+ */
+public class SensorList<T> {
+ private static final String TAG = "SensorList";
+ private final SparseArray<T> mSensors;
+ private final IActivityManager mActivityManager;
+
+ public SensorList(IActivityManager activityManager) {
+ mSensors = new SparseArray<T>();
+ mActivityManager = activityManager;
+ }
+
+ /**
+ * Adding sensor to the map with the sensor id as key. Also, starts a session if the user Id is
+ * NULL.
+ */
+ public void addSensor(int sensorId, T sensor, int sessionUserId,
+ SynchronousUserSwitchObserver userSwitchObserver) {
+ mSensors.put(sensorId, sensor);
+ registerUserSwitchObserver(sessionUserId, userSwitchObserver);
+ }
+
+ private void registerUserSwitchObserver(int sessionUserId,
+ SynchronousUserSwitchObserver userSwitchObserver) {
+ try {
+ mActivityManager.registerUserSwitchObserver(userSwitchObserver,
+ TAG);
+ if (sessionUserId == UserHandle.USER_NULL) {
+ userSwitchObserver.onUserSwitching(UserHandle.USER_SYSTEM);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to register user switch observer");
+ }
+ }
+
+ /**
+ * Returns the sensor corresponding to the key at a specific position.
+ */
+ public T valueAt(int position) {
+ return mSensors.valueAt(position);
+ }
+
+ /**
+ * Returns the sensor associated with sensorId as key.
+ */
+ public T get(int sensorId) {
+ return mSensors.get(sensorId);
+ }
+
+ /**
+ * Returns the sensorId at the specified position.
+ */
+ public int keyAt(int position) {
+ return mSensors.keyAt(position);
+ }
+
+ /**
+ * Returns the number of sensors added.
+ */
+ public int size() {
+ return mSensors.size();
+ }
+
+ /**
+ * Returns true if a sensor exists for the specified sensorId.
+ */
+ public boolean contains(int sensorId) {
+ return mSensors.contains(sensorId);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index c5037b7..a501647 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
+import android.app.SynchronousUserSwitchObserver;
import android.app.TaskStackListener;
import android.content.Context;
import android.content.pm.UserInfo;
@@ -41,9 +42,9 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
-import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import android.view.Surface;
@@ -62,6 +63,7 @@
import com.android.server.biometrics.sensors.InvalidationRequesterClient;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.PerformanceTracker;
+import com.android.server.biometrics.sensors.SensorList;
import com.android.server.biometrics.sensors.face.FaceUtils;
import com.android.server.biometrics.sensors.face.ServiceProvider;
import com.android.server.biometrics.sensors.face.UsageStats;
@@ -86,7 +88,7 @@
@NonNull
@VisibleForTesting
- final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
+ final SensorList<Sensor> mFaceSensors;
@NonNull
private final Context mContext;
@NonNull
@@ -117,8 +119,8 @@
@Override
public void onTaskStackChanged() {
mHandler.post(() -> {
- for (int i = 0; i < mSensors.size(); i++) {
- final BaseClientMonitor client = mSensors.valueAt(i).getScheduler()
+ for (int i = 0; i < mFaceSensors.size(); i++) {
+ final BaseClientMonitor client = mFaceSensors.valueAt(i).getScheduler()
.getCurrentClient();
if (!(client instanceof AuthenticationClient)) {
Slog.e(getTag(), "Task stack changed for client: " + client);
@@ -133,7 +135,7 @@
&& !client.isAlreadyDone()) {
Slog.e(getTag(), "Stopping background authentication,"
+ " currentClient: " + client);
- mSensors.valueAt(i).getScheduler().cancelAuthenticationOrDetection(
+ mFaceSensors.valueAt(i).getScheduler().cancelAuthenticationOrDetection(
client.getToken(), client.getRequestId());
}
}
@@ -150,7 +152,7 @@
mContext = context;
mBiometricStateCallback = biometricStateCallback;
mHalInstanceName = halInstanceName;
- mSensors = new SparseArray<>();
+ mFaceSensors = new SensorList<>(ActivityManager.getService());
mHandler = new Handler(Looper.getMainLooper());
mUsageStats = new UsageStats(context);
mLockoutResetDispatcher = lockoutResetDispatcher;
@@ -178,8 +180,15 @@
false /* resetLockoutRequiresChallenge */);
final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
internalProp, lockoutResetDispatcher, mBiometricContext);
-
- mSensors.put(sensorId, sensor);
+ final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+ sensor.getLazySession().get().getUserId();
+ mFaceSensors.addSensor(sensorId, sensor, userId,
+ new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+ }
+ });
Slog.d(getTag(), "Added: " + internalProp);
}
}
@@ -223,8 +232,8 @@
Slog.e(getTag(), "Unable to linkToDeath", e);
}
- for (int i = 0; i < mSensors.size(); i++) {
- final int sensorId = mSensors.keyAt(i);
+ for (int i = 0; i < mFaceSensors.size(); i++) {
+ final int sensorId = mFaceSensors.keyAt(i);
scheduleLoadAuthenticatorIds(sensorId);
scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(),
null /* callback */);
@@ -234,20 +243,20 @@
}
private void scheduleForSensor(int sensorId, @NonNull BaseClientMonitor client) {
- if (!mSensors.contains(sensorId)) {
+ if (!mFaceSensors.contains(sensorId)) {
throw new IllegalStateException("Unable to schedule client: " + client
+ " for sensor: " + sensorId);
}
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ mFaceSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
}
private void scheduleForSensor(int sensorId, @NonNull BaseClientMonitor client,
ClientMonitorCallback callback) {
- if (!mSensors.contains(sensorId)) {
+ if (!mFaceSensors.contains(sensorId)) {
throw new IllegalStateException("Unable to schedule client: " + client
+ " for sensor: " + sensorId);
}
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
+ mFaceSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
}
private void scheduleLoadAuthenticatorIds(int sensorId) {
@@ -259,12 +268,12 @@
private void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
mHandler.post(() -> {
final FaceGetAuthenticatorIdClient client = new FaceGetAuthenticatorIdClient(
- mContext, mSensors.get(sensorId).getLazySession(), userId,
+ mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext,
- mSensors.get(sensorId).getAuthenticatorIds());
+ mFaceSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client);
});
@@ -283,15 +292,15 @@
@Override
public boolean containsSensor(int sensorId) {
- return mSensors.contains(sensorId);
+ return mFaceSensors.contains(sensorId);
}
@NonNull
@Override
public List<FaceSensorPropertiesInternal> getSensorProperties() {
final List<FaceSensorPropertiesInternal> props = new ArrayList<>();
- for (int i = 0; i < mSensors.size(); ++i) {
- props.add(mSensors.valueAt(i).getSensorProperties());
+ for (int i = 0; i < mFaceSensors.size(); ++i) {
+ props.add(mFaceSensors.valueAt(i).getSensorProperties());
}
return props;
}
@@ -299,7 +308,7 @@
@NonNull
@Override
public FaceSensorPropertiesInternal getSensorProperties(int sensorId) {
- return mSensors.get(sensorId).getSensorProperties();
+ return mFaceSensors.get(sensorId).getSensorProperties();
}
@NonNull
@@ -318,11 +327,11 @@
@NonNull IInvalidationCallback callback) {
mHandler.post(() -> {
final FaceInvalidationClient client = new FaceInvalidationClient(mContext,
- mSensors.get(sensorId).getLazySession(), userId, sensorId,
+ mFaceSensors.get(sensorId).getLazySession(), userId, sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext,
- mSensors.get(sensorId).getAuthenticatorIds(), callback);
+ mFaceSensors.get(sensorId).getAuthenticatorIds(), callback);
scheduleForSensor(sensorId, client);
});
}
@@ -335,7 +344,7 @@
@Override
public long getAuthenticatorId(int sensorId, int userId) {
- return mSensors.get(sensorId).getAuthenticatorIds().getOrDefault(userId, 0L);
+ return mFaceSensors.get(sensorId).getAuthenticatorIds().getOrDefault(userId, 0L);
}
@Override
@@ -348,7 +357,7 @@
@NonNull IFaceServiceReceiver receiver, String opPackageName) {
mHandler.post(() -> {
final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
- mSensors.get(sensorId).getLazySession(), token,
+ mFaceSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
BiometricsProtoEnums.CLIENT_UNKNOWN),
@@ -362,7 +371,8 @@
@NonNull String opPackageName, long challenge) {
mHandler.post(() -> {
final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
- mSensors.get(sensorId).getLazySession(), token, userId, opPackageName, sensorId,
+ mFaceSensors.get(sensorId).getLazySession(), token, userId,
+ opPackageName, sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext, challenge);
@@ -377,10 +387,10 @@
@Nullable Surface previewSurface, boolean debugConsent) {
final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> {
- final int maxTemplatesPerUser = mSensors.get(
+ final int maxTemplatesPerUser = mFaceSensors.get(
sensorId).getSensorProperties().maxEnrollmentsPerUser;
final FaceEnrollClient client = new FaceEnrollClient(mContext,
- mSensors.get(sensorId).getLazySession(), token,
+ mFaceSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures,
ENROLL_TIMEOUT_SEC, previewSurface, sensorId,
@@ -406,7 +416,7 @@
@Override
public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
mHandler.post(() ->
- mSensors.get(sensorId).getScheduler().cancelEnrollment(token, requestId));
+ mFaceSensors.get(sensorId).getScheduler().cancelEnrollment(token, requestId));
}
@Override
@@ -419,7 +429,7 @@
mHandler.post(() -> {
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FaceDetectClient client = new FaceDetectClient(mContext,
- mSensors.get(sensorId).getLazySession(),
+ mFaceSensors.get(sensorId).getLazySession(),
token, id, callback, options,
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
mBiometricContext, isStrongBiometric);
@@ -431,7 +441,7 @@
@Override
public void cancelFaceDetect(int sensorId, @NonNull IBinder token, long requestId) {
- mHandler.post(() -> mSensors.get(sensorId).getScheduler()
+ mHandler.post(() -> mFaceSensors.get(sensorId).getScheduler()
.cancelAuthenticationOrDetection(token, requestId));
}
@@ -446,12 +456,12 @@
final int sensorId = options.getSensorId();
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FaceAuthenticationClient client = new FaceAuthenticationClient(
- mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
- operationId, restricted, options, cookie,
+ mContext, mFaceSensors.get(sensorId).getLazySession(), token, requestId,
+ callback, operationId, restricted, options, cookie,
false /* requireConfirmation */,
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
mBiometricContext, isStrongBiometric,
- mUsageStats, mSensors.get(sensorId).getLockoutCache(),
+ mUsageStats, mFaceSensors.get(sensorId).getLockoutCache(),
allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));
scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@Override
@@ -486,7 +496,7 @@
@Override
public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
- mHandler.post(() -> mSensors.get(sensorId).getScheduler()
+ mHandler.post(() -> mFaceSensors.get(sensorId).getScheduler()
.cancelAuthenticationOrDetection(token, requestId));
}
@@ -514,13 +524,13 @@
int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
mHandler.post(() -> {
final FaceRemovalClient client = new FaceRemovalClient(mContext,
- mSensors.get(sensorId).getLazySession(), token,
+ mFaceSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), faceIds, userId,
opPackageName, FaceUtils.getInstance(sensorId), sensorId,
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext,
- mSensors.get(sensorId).getAuthenticatorIds());
+ mFaceSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
}
@@ -529,12 +539,12 @@
public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) {
mHandler.post(() -> {
final FaceResetLockoutClient client = new FaceResetLockoutClient(
- mContext, mSensors.get(sensorId).getLazySession(), userId,
+ mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext, hardwareAuthToken,
- mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
+ mFaceSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
Utils.getCurrentStrength(sensorId));
scheduleForSensor(sensorId, client);
@@ -553,7 +563,7 @@
return;
}
final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext,
- mSensors.get(sensorId).getLazySession(), token,
+ mFaceSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId,
mContext.getOpPackageName(), sensorId,
BiometricLogger.ofUnknown(mContext), mBiometricContext,
@@ -573,7 +583,7 @@
return;
}
final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext,
- mSensors.get(sensorId).getLazySession(), token, callback, userId,
+ mFaceSensors.get(sensorId).getLazySession(), token, callback, userId,
mContext.getOpPackageName(), sensorId, BiometricLogger.ofUnknown(mContext),
mBiometricContext);
scheduleForSensor(sensorId, client);
@@ -583,7 +593,7 @@
@Override
public void startPreparedClient(int sensorId, int cookie) {
mHandler.post(() -> {
- mSensors.get(sensorId).getScheduler().startPreparedClient(cookie);
+ mFaceSensors.get(sensorId).getScheduler().startPreparedClient(cookie);
});
}
@@ -599,13 +609,13 @@
mHandler.post(() -> {
final FaceInternalCleanupClient client =
new FaceInternalCleanupClient(mContext,
- mSensors.get(sensorId).getLazySession(), userId,
+ mFaceSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext,
FaceUtils.getInstance(sensorId),
- mSensors.get(sensorId).getAuthenticatorIds());
+ mFaceSensors.get(sensorId).getAuthenticatorIds());
if (favorHalEnrollments) {
client.setFavorHalEnrollments();
}
@@ -622,8 +632,8 @@
@Override
public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
boolean clearSchedulerBuffer) {
- if (mSensors.contains(sensorId)) {
- mSensors.get(sensorId).dumpProtoState(sensorId, proto, clearSchedulerBuffer);
+ if (mFaceSensors.contains(sensorId)) {
+ mFaceSensors.get(sensorId).dumpProtoState(sensorId, proto, clearSchedulerBuffer);
}
}
@@ -672,7 +682,7 @@
pw.println(mBiometricContext.getAuthSessionCoordinator());
pw.println("---AuthSessionCoordinator logs end ---");
- mSensors.get(sensorId).getScheduler().dump(pw);
+ mFaceSensors.get(sensorId).getScheduler().dump(pw);
mUsageStats.print(pw);
}
@@ -680,7 +690,7 @@
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@NonNull String opPackageName) {
- return mSensors.get(sensorId).createTestSession(callback);
+ return mFaceSensors.get(sensorId).createTestSession(callback);
}
@Override
@@ -692,9 +702,9 @@
Slog.e(getTag(), "HAL died");
mHandler.post(() -> {
mDaemon = null;
- for (int i = 0; i < mSensors.size(); i++) {
- final Sensor sensor = mSensors.valueAt(i);
- final int sensorId = mSensors.keyAt(i);
+ for (int i = 0; i < mFaceSensors.size(); i++) {
+ final Sensor sensor = mFaceSensors.valueAt(i);
+ final int sensorId = mFaceSensors.keyAt(i);
PerformanceTracker.getInstanceForSensorId(sensorId).incrementHALDeathCount();
sensor.onBinderDied();
}
@@ -708,7 +718,7 @@
@Override
public void scheduleWatchdog(int sensorId) {
Slog.d(getTag(), "Starting watchdog for face");
- final BiometricScheduler biometricScheduler = mSensors.get(sensorId).getScheduler();
+ final BiometricScheduler biometricScheduler = mFaceSensors.get(sensorId).getScheduler();
if (biometricScheduler == null) {
return;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 468bf55..ffbf4e1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -18,9 +18,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.SynchronousUserSwitchObserver;
-import android.app.UserSwitchObserver;
import android.content.Context;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricsProtoEnums;
@@ -94,14 +91,6 @@
@NonNull private final Supplier<AidlSession> mLazySession;
@Nullable private AidlSession mCurrentSession;
- private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
- @Override
- public void onUserSwitching(int newUserId) {
- mProvider.scheduleInternalCleanup(
- mSensorProperties.sensorId, newUserId, null /* callback */);
- }
- };
-
@VisibleForTesting
public static class HalSessionCallback extends ISessionCallback.Stub {
/**
@@ -558,12 +547,6 @@
mLockoutCache = new LockoutCache();
mAuthenticatorIds = new HashMap<>();
mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
-
- try {
- ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, mTag);
- } catch (RemoteException e) {
- Slog.e(mTag, "Unable to register user switch observer");
- }
}
@NonNull Supplier<AidlSession> getLazySession() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 23b6f84..58ece89 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
+import android.app.SynchronousUserSwitchObserver;
import android.app.TaskStackListener;
import android.content.Context;
import android.content.pm.UserInfo;
@@ -51,9 +52,9 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
-import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
@@ -71,6 +72,7 @@
import com.android.server.biometrics.sensors.InvalidationRequesterClient;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.PerformanceTracker;
+import com.android.server.biometrics.sensors.SensorList;
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
@@ -99,7 +101,7 @@
@NonNull
@VisibleForTesting
- final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
+ final SensorList<Sensor> mFingerprintSensors;
@NonNull
private final Context mContext;
@NonNull
@@ -127,8 +129,8 @@
@Override
public void onTaskStackChanged() {
mHandler.post(() -> {
- for (int i = 0; i < mSensors.size(); i++) {
- final BaseClientMonitor client = mSensors.valueAt(i).getScheduler()
+ for (int i = 0; i < mFingerprintSensors.size(); i++) {
+ final BaseClientMonitor client = mFingerprintSensors.valueAt(i).getScheduler()
.getCurrentClient();
if (!(client instanceof AuthenticationClient)) {
Slog.e(getTag(), "Task stack changed for client: " + client);
@@ -143,8 +145,9 @@
&& !client.isAlreadyDone()) {
Slog.e(getTag(), "Stopping background authentication,"
+ " currentClient: " + client);
- mSensors.valueAt(i).getScheduler().cancelAuthenticationOrDetection(
- client.getToken(), client.getRequestId());
+ mFingerprintSensors.valueAt(i).getScheduler()
+ .cancelAuthenticationOrDetection(
+ client.getToken(), client.getRequestId());
}
}
});
@@ -160,7 +163,7 @@
mContext = context;
mBiometricStateCallback = biometricStateCallback;
mHalInstanceName = halInstanceName;
- mSensors = new SparseArray<>();
+ mFingerprintSensors = new SensorList<>(ActivityManager.getService());
mHandler = new Handler(Looper.getMainLooper());
mLockoutResetDispatcher = lockoutResetDispatcher;
mActivityTaskManager = ActivityTaskManager.getInstance();
@@ -201,8 +204,15 @@
final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher,
mBiometricContext);
-
- mSensors.put(sensorId, sensor);
+ final int sessionUserId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+ sensor.getLazySession().get().getUserId();
+ mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
+ new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+ }
+ });
Slog.d(getTag(), "Added: " + internalProp);
}
}
@@ -250,8 +260,8 @@
Slog.e(getTag(), "Unable to linkToDeath", e);
}
- for (int i = 0; i < mSensors.size(); i++) {
- final int sensorId = mSensors.keyAt(i);
+ for (int i = 0; i < mFingerprintSensors.size(); i++) {
+ final int sensorId = mFingerprintSensors.keyAt(i);
scheduleLoadAuthenticatorIds(sensorId);
scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(),
null /* callback */);
@@ -261,33 +271,33 @@
}
private void scheduleForSensor(int sensorId, @NonNull BaseClientMonitor client) {
- if (!mSensors.contains(sensorId)) {
+ if (!mFingerprintSensors.contains(sensorId)) {
throw new IllegalStateException("Unable to schedule client: " + client
+ " for sensor: " + sensorId);
}
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ mFingerprintSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
}
private void scheduleForSensor(int sensorId, @NonNull BaseClientMonitor client,
ClientMonitorCallback callback) {
- if (!mSensors.contains(sensorId)) {
+ if (!mFingerprintSensors.contains(sensorId)) {
throw new IllegalStateException("Unable to schedule client: " + client
+ " for sensor: " + sensorId);
}
- mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
+ mFingerprintSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
}
@Override
public boolean containsSensor(int sensorId) {
- return mSensors.contains(sensorId);
+ return mFingerprintSensors.contains(sensorId);
}
@NonNull
@Override
public List<FingerprintSensorPropertiesInternal> getSensorProperties() {
final List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
- for (int i = 0; i < mSensors.size(); i++) {
- props.add(mSensors.valueAt(i).getSensorProperties());
+ for (int i = 0; i < mFingerprintSensors.size(); i++) {
+ props.add(mFingerprintSensors.valueAt(i).getSensorProperties());
}
return props;
}
@@ -295,12 +305,12 @@
@Nullable
@Override
public FingerprintSensorPropertiesInternal getSensorProperties(int sensorId) {
- if (mSensors.size() == 0) {
+ if (mFingerprintSensors.size() == 0) {
return null;
} else if (sensorId == SENSOR_ID_ANY) {
- return mSensors.valueAt(0).getSensorProperties();
+ return mFingerprintSensors.valueAt(0).getSensorProperties();
} else {
- final Sensor sensor = mSensors.get(sensorId);
+ final Sensor sensor = mFingerprintSensors.get(sensorId);
return sensor != null ? sensor.getSensorProperties() : null;
}
}
@@ -315,12 +325,12 @@
mHandler.post(() -> {
final FingerprintGetAuthenticatorIdClient client =
new FingerprintGetAuthenticatorIdClient(mContext,
- mSensors.get(sensorId).getLazySession(), userId,
+ mFingerprintSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext,
- mSensors.get(sensorId).getAuthenticatorIds());
+ mFingerprintSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client);
});
}
@@ -340,12 +350,12 @@
public void scheduleResetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken) {
mHandler.post(() -> {
final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(
- mContext, mSensors.get(sensorId).getLazySession(), userId,
+ mContext, mFingerprintSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext, hardwareAuthToken,
- mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
+ mFingerprintSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
Utils.getCurrentStrength(sensorId));
scheduleForSensor(sensorId, client);
});
@@ -357,7 +367,7 @@
mHandler.post(() -> {
final FingerprintGenerateChallengeClient client =
new FingerprintGenerateChallengeClient(mContext,
- mSensors.get(sensorId).getLazySession(), token,
+ mFingerprintSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
sensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
BiometricsProtoEnums.CLIENT_UNKNOWN),
@@ -372,7 +382,7 @@
mHandler.post(() -> {
final FingerprintRevokeChallengeClient client =
new FingerprintRevokeChallengeClient(mContext,
- mSensors.get(sensorId).getLazySession(), token,
+ mFingerprintSensors.get(sensorId).getLazySession(), token,
userId, opPackageName, sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
BiometricsProtoEnums.CLIENT_UNKNOWN),
@@ -388,16 +398,16 @@
@FingerprintManager.EnrollReason int enrollReason) {
final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> {
- final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties()
+ final int maxTemplatesPerUser = mFingerprintSensors.get(sensorId).getSensorProperties()
.maxEnrollmentsPerUser;
final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
- mSensors.get(sensorId).getLazySession(), token, id,
+ mFingerprintSensors.get(sensorId).getLazySession(), token, id,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext,
- mSensors.get(sensorId).getSensorProperties(),
+ mFingerprintSensors.get(sensorId).getSensorProperties(),
mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
maxTemplatesPerUser, enrollReason);
scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
@@ -419,7 +429,8 @@
@Override
public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
mHandler.post(() ->
- mSensors.get(sensorId).getScheduler().cancelEnrollment(token, requestId));
+ mFingerprintSensors.get(sensorId).getScheduler()
+ .cancelEnrollment(token, requestId));
}
@Override
@@ -432,7 +443,7 @@
final int sensorId = options.getSensorId();
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
- mSensors.get(sensorId).getLazySession(), token, id, callback,
+ mFingerprintSensors.get(sensorId).getLazySession(), token, id, callback,
options,
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
mBiometricContext,
@@ -454,15 +465,15 @@
final int sensorId = options.getSensorId();
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
- mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
- operationId, restricted, options, cookie,
+ mContext, mFingerprintSensors.get(sensorId).getLazySession(), token, requestId,
+ callback, operationId, restricted, options, cookie,
false /* requireConfirmation */,
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
mBiometricContext, isStrongBiometric,
- mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
+ mTaskStackListener, mFingerprintSensors.get(sensorId).getLockoutCache(),
mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
allowBackgroundAuthentication,
- mSensors.get(sensorId).getSensorProperties(), mHandler,
+ mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler,
Utils.getCurrentStrength(sensorId),
SystemClock.elapsedRealtimeClock());
scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@@ -505,12 +516,13 @@
@Override
public void startPreparedClient(int sensorId, int cookie) {
- mHandler.post(() -> mSensors.get(sensorId).getScheduler().startPreparedClient(cookie));
+ mHandler.post(() -> mFingerprintSensors.get(sensorId).getScheduler()
+ .startPreparedClient(cookie));
}
@Override
public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
- mHandler.post(() -> mSensors.get(sensorId).getScheduler()
+ mHandler.post(() -> mFingerprintSensors.get(sensorId).getScheduler()
.cancelAuthenticationOrDetection(token, requestId));
}
@@ -541,13 +553,13 @@
@NonNull String opPackageName) {
mHandler.post(() -> {
final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
- mSensors.get(sensorId).getLazySession(), token,
+ mFingerprintSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext,
- mSensors.get(sensorId).getAuthenticatorIds());
+ mFingerprintSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
}
@@ -564,13 +576,13 @@
mHandler.post(() -> {
final FingerprintInternalCleanupClient client =
new FingerprintInternalCleanupClient(mContext,
- mSensors.get(sensorId).getLazySession(), userId,
+ mFingerprintSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext,
FingerprintUtils.getInstance(sensorId),
- mSensors.get(sensorId).getAuthenticatorIds());
+ mFingerprintSensors.get(sensorId).getAuthenticatorIds());
if (favorHalEnrollments) {
client.setFavorHalEnrollments();
}
@@ -612,11 +624,11 @@
mHandler.post(() -> {
final FingerprintInvalidationClient client =
new FingerprintInvalidationClient(mContext,
- mSensors.get(sensorId).getLazySession(), userId, sensorId,
+ mFingerprintSensors.get(sensorId).getLazySession(), userId, sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext,
- mSensors.get(sensorId).getAuthenticatorIds(), callback);
+ mFingerprintSensors.get(sensorId).getAuthenticatorIds(), callback);
scheduleForSensor(sensorId, client);
});
}
@@ -629,40 +641,43 @@
@Override
public long getAuthenticatorId(int sensorId, int userId) {
- return mSensors.get(sensorId).getAuthenticatorIds().getOrDefault(userId, 0L);
+ return mFingerprintSensors.get(sensorId).getAuthenticatorIds().getOrDefault(userId, 0L);
}
@Override
public void onPointerDown(long requestId, int sensorId, PointerContext pc) {
- mSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(requestId, (client) -> {
- if (!(client instanceof Udfps)) {
- Slog.e(getTag(), "onPointerDown received during client: " + client);
- return;
- }
- ((Udfps) client).onPointerDown(pc);
- });
+ mFingerprintSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(
+ requestId, (client) -> {
+ if (!(client instanceof Udfps)) {
+ Slog.e(getTag(), "onPointerDown received during client: " + client);
+ return;
+ }
+ ((Udfps) client).onPointerDown(pc);
+ });
}
@Override
public void onPointerUp(long requestId, int sensorId, PointerContext pc) {
- mSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(requestId, (client) -> {
- if (!(client instanceof Udfps)) {
- Slog.e(getTag(), "onPointerUp received during client: " + client);
- return;
- }
- ((Udfps) client).onPointerUp(pc);
- });
+ mFingerprintSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(
+ requestId, (client) -> {
+ if (!(client instanceof Udfps)) {
+ Slog.e(getTag(), "onPointerUp received during client: " + client);
+ return;
+ }
+ ((Udfps) client).onPointerUp(pc);
+ });
}
@Override
public void onUiReady(long requestId, int sensorId) {
- mSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(requestId, (client) -> {
- if (!(client instanceof Udfps)) {
- Slog.e(getTag(), "onUiReady received during client: " + client);
- return;
- }
- ((Udfps) client).onUiReady();
- });
+ mFingerprintSensors.get(sensorId).getScheduler().getCurrentClientIfMatches(
+ requestId, (client) -> {
+ if (!(client instanceof Udfps)) {
+ Slog.e(getTag(), "onUiReady received during client: " + client);
+ return;
+ }
+ ((Udfps) client).onUiReady();
+ });
}
@Override
@@ -672,8 +687,8 @@
@Override
public void onPowerPressed() {
- for (int i = 0; i < mSensors.size(); i++) {
- final Sensor sensor = mSensors.valueAt(i);
+ for (int i = 0; i < mFingerprintSensors.size(); i++) {
+ final Sensor sensor = mFingerprintSensors.valueAt(i);
BaseClientMonitor client = sensor.getScheduler().getCurrentClient();
if (client == null) {
return;
@@ -698,8 +713,8 @@
@Override
public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
boolean clearSchedulerBuffer) {
- if (mSensors.contains(sensorId)) {
- mSensors.get(sensorId).dumpProtoState(sensorId, proto, clearSchedulerBuffer);
+ if (mFingerprintSensors.contains(sensorId)) {
+ mFingerprintSensors.get(sensorId).dumpProtoState(sensorId, proto, clearSchedulerBuffer);
}
}
@@ -748,14 +763,15 @@
pw.println(mBiometricContext.getAuthSessionCoordinator());
pw.println("---AuthSessionCoordinator logs end ---");
- mSensors.get(sensorId).getScheduler().dump(pw);
+ mFingerprintSensors.get(sensorId).getScheduler().dump(pw);
}
@NonNull
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@NonNull String opPackageName) {
- return mSensors.get(sensorId).createTestSession(callback, mBiometricStateCallback);
+ return mFingerprintSensors.get(sensorId).createTestSession(callback,
+ mBiometricStateCallback);
}
@Override
@@ -764,9 +780,9 @@
mHandler.post(() -> {
mDaemon = null;
- for (int i = 0; i < mSensors.size(); i++) {
- final Sensor sensor = mSensors.valueAt(i);
- final int sensorId = mSensors.keyAt(i);
+ for (int i = 0; i < mFingerprintSensors.size(); i++) {
+ final Sensor sensor = mFingerprintSensors.valueAt(i);
+ final int sensorId = mFingerprintSensors.keyAt(i);
PerformanceTracker.getInstanceForSensorId(sensorId).incrementHALDeathCount();
sensor.onBinderDied();
}
@@ -821,7 +837,8 @@
@Override
public void scheduleWatchdog(int sensorId) {
Slog.d(getTag(), "Starting watchdog for fingerprint");
- final BiometricScheduler biometricScheduler = mSensors.get(sensorId).getScheduler();
+ final BiometricScheduler biometricScheduler = mFingerprintSensors.get(sensorId)
+ .getScheduler();
if (biometricScheduler == null) {
return;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 22ca816..c0dde72 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -18,9 +18,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.SynchronousUserSwitchObserver;
-import android.app.UserSwitchObserver;
import android.content.Context;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricsProtoEnums;
@@ -96,14 +93,6 @@
@Nullable private AidlSession mCurrentSession;
@NonNull private final Supplier<AidlSession> mLazySession;
- private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
- @Override
- public void onUserSwitching(int newUserId) {
- mProvider.scheduleInternalCleanup(
- mSensorProperties.sensorId, newUserId, null /* callback */);
- }
- };
-
@VisibleForTesting
public static class HalSessionCallback extends ISessionCallback.Stub {
@@ -512,12 +501,6 @@
});
mAuthenticatorIds = new HashMap<>();
mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
-
- try {
- ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, mTag);
- } catch (RemoteException e) {
- Slog.e(mTag, "Unable to register user switch observer");
- }
}
@NonNull Supplier<AidlSession> getLazySession() {
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index 1f82961..6d43061 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -41,6 +41,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.BitUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.RingBuffer;
@@ -278,6 +279,11 @@
}
}
+ private boolean hasWifiTransport(Network network) {
+ final NetworkCapabilities nc = mCm.getNetworkCapabilities(network);
+ return nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
+ }
+
@Override
public synchronized void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader,
byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs) {
@@ -286,12 +292,21 @@
throw new IllegalArgumentException("Prefix " + prefix
+ " required in format <nethandle>:<interface>");
}
+ final long netHandle = Long.parseLong(prefixParts[0]);
+ final Network network = Network.fromNetworkHandle(netHandle);
final WakeupEvent event = new WakeupEvent();
event.iface = prefixParts[1];
event.uid = uid;
event.ethertype = ethertype;
- event.dstHwAddr = MacAddress.fromBytes(dstHw);
+ if (ArrayUtils.isEmpty(dstHw)) {
+ if (hasWifiTransport(network)) {
+ Log.e(TAG, "Empty mac address on WiFi transport, network: " + network);
+ }
+ event.dstHwAddr = null;
+ } else {
+ event.dstHwAddr = MacAddress.fromBytes(dstHw);
+ }
event.srcIp = srcIp;
event.dstIp = dstIp;
event.ipNextHeader = ipNextHeader;
@@ -306,14 +321,12 @@
final BatteryStatsInternal bsi = LocalServices.getService(BatteryStatsInternal.class);
if (bsi != null) {
- final long netHandle = Long.parseLong(prefixParts[0]);
final long elapsedMs = SystemClock.elapsedRealtime() + event.timestampMs
- System.currentTimeMillis();
- bsi.noteCpuWakingNetworkPacket(Network.fromNetworkHandle(netHandle), elapsedMs,
- event.uid);
+ bsi.noteCpuWakingNetworkPacket(network, elapsedMs, event.uid);
}
- final String dstMac = event.dstHwAddr.toString();
+ final String dstMac = String.valueOf(event.dstHwAddr);
FrameworkStatsLog.write(FrameworkStatsLog.PACKET_WAKEUP_OCCURRED,
uid, event.iface, ethertype, dstMac, srcIp, dstIp, ipNextHeader, srcPort, dstPort);
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 1dc2725..c678a92 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -85,6 +85,7 @@
import android.net.NetworkSpecifier;
import android.net.RouteInfo;
import android.net.TelephonyNetworkSpecifier;
+import android.net.TransportInfo;
import android.net.UidRangeParcel;
import android.net.UnderlyingNetworkInfo;
import android.net.Uri;
@@ -107,6 +108,8 @@
import android.net.ipsec.ike.exceptions.IkeNonProtocolException;
import android.net.ipsec.ike.exceptions.IkeProtocolException;
import android.net.ipsec.ike.exceptions.IkeTimeoutException;
+import android.net.vcn.VcnGatewayConnectionConfig;
+import android.net.vcn.VcnTransportInfo;
import android.os.Binder;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
@@ -453,22 +456,22 @@
private static class CarrierConfigInfo {
public final String mccMnc;
- public final int keepaliveDelayMs;
+ public final int keepaliveDelaySec;
public final int encapType;
public final int ipVersion;
- CarrierConfigInfo(String mccMnc, int keepaliveDelayMs,
+ CarrierConfigInfo(String mccMnc, int keepaliveDelaySec,
int encapType,
int ipVersion) {
this.mccMnc = mccMnc;
- this.keepaliveDelayMs = keepaliveDelayMs;
+ this.keepaliveDelaySec = keepaliveDelaySec;
this.encapType = encapType;
this.ipVersion = ipVersion;
}
@Override
public String toString() {
- return "CarrierConfigInfo(" + mccMnc + ") [keepaliveDelayMs=" + keepaliveDelayMs
+ return "CarrierConfigInfo(" + mccMnc + ") [keepaliveDelaySec=" + keepaliveDelaySec
+ ", encapType=" + encapType + ", ipVersion=" + ipVersion + "]";
}
}
@@ -3556,39 +3559,63 @@
}
private int guessEspIpVersionForNetwork() {
- final CarrierConfigInfo carrierconfig = getCarrierConfig();
+ if (mUnderlyingNetworkCapabilities.getTransportInfo() instanceof VcnTransportInfo) {
+ Log.d(TAG, "Running over VCN, esp IP version is auto");
+ return ESP_IP_VERSION_AUTO;
+ }
+ final CarrierConfigInfo carrierconfig = getCarrierConfigForUnderlyingNetwork();
final int ipVersion = (carrierconfig != null)
? carrierconfig.ipVersion : ESP_IP_VERSION_AUTO;
if (carrierconfig != null) {
- Log.d(TAG, "Get customized IP version(" + ipVersion + ") on SIM("
+ Log.d(TAG, "Get customized IP version (" + ipVersion + ") on SIM (mccmnc="
+ carrierconfig.mccMnc + ")");
}
return ipVersion;
}
private int guessEspEncapTypeForNetwork() {
- final CarrierConfigInfo carrierconfig = getCarrierConfig();
+ if (mUnderlyingNetworkCapabilities.getTransportInfo() instanceof VcnTransportInfo) {
+ Log.d(TAG, "Running over VCN, encap type is auto");
+ return ESP_ENCAP_TYPE_AUTO;
+ }
+ final CarrierConfigInfo carrierconfig = getCarrierConfigForUnderlyingNetwork();
final int encapType = (carrierconfig != null)
? carrierconfig.encapType : ESP_ENCAP_TYPE_AUTO;
if (carrierconfig != null) {
- Log.d(TAG, "Get customized encap type(" + encapType + ") on SIM("
+ Log.d(TAG, "Get customized encap type (" + encapType + ") on SIM (mccmnc="
+ carrierconfig.mccMnc + ")");
}
return encapType;
}
+
private int guessNattKeepaliveTimerForNetwork() {
- final CarrierConfigInfo carrierconfig = getCarrierConfig();
- final int natKeepalive = (carrierconfig != null)
- ? carrierconfig.keepaliveDelayMs : AUTOMATIC_KEEPALIVE_DELAY_SECONDS;
+ final TransportInfo transportInfo = mUnderlyingNetworkCapabilities.getTransportInfo();
+ if (transportInfo instanceof VcnTransportInfo) {
+ final int nattKeepaliveSec =
+ ((VcnTransportInfo) transportInfo).getMinUdpPort4500NatTimeoutSeconds();
+ Log.d(TAG, "Running over VCN, keepalive timer : " + nattKeepaliveSec + "s");
+ if (VcnGatewayConnectionConfig.MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET
+ != nattKeepaliveSec) {
+ return nattKeepaliveSec;
+ }
+ // else fall back to carrier config, if any
+ }
+ final CarrierConfigInfo carrierconfig = getCarrierConfigForUnderlyingNetwork();
+ final int nattKeepaliveSec = (carrierconfig != null)
+ ? carrierconfig.keepaliveDelaySec : AUTOMATIC_KEEPALIVE_DELAY_SECONDS;
if (carrierconfig != null) {
- Log.d(TAG, "Get customized keepalive(" + natKeepalive + ") on SIM("
+ Log.d(TAG, "Get customized keepalive (" + nattKeepaliveSec + "s) on SIM (mccmnc="
+ carrierconfig.mccMnc + ")");
}
- return natKeepalive;
+ return nattKeepaliveSec;
}
- private CarrierConfigInfo getCarrierConfig() {
+ /**
+ * Returns the carrier config for the underlying network, or null if not a cell network.
+ */
+ @Nullable
+ private CarrierConfigInfo getCarrierConfigForUnderlyingNetwork() {
final int subId = getCellSubIdForNetworkCapabilities(mUnderlyingNetworkCapabilities);
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
Log.d(TAG, "Underlying network is not a cellular network");
diff --git a/services/core/java/com/android/server/display/color/CctEvaluator.java b/services/core/java/com/android/server/display/color/CctEvaluator.java
new file mode 100644
index 0000000..878f7e5
--- /dev/null
+++ b/services/core/java/com/android/server/display/color/CctEvaluator.java
@@ -0,0 +1,108 @@
+/*
+ * 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.color;
+
+import android.animation.TypeEvaluator;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+
+/**
+ * Interpolates between CCT values by a given step.
+ */
+class CctEvaluator implements TypeEvaluator<Integer> {
+
+ private static final String TAG = "CctEvaluator";
+
+ /**
+ * The minimum input value, which will represent index 0 in the mValues array. Each
+ * subsequent input value is offset by this amount.
+ */
+ private final int mIndexOffset;
+ /**
+ * Cached step values at each CCT value (offset by the {@link #mIndexOffset} above). For
+ * example, if the minimum CCT is 2000K (which is set to mIndexOffset), then the 0th index of
+ * this array is equivalent to the step value at 2000K, 1st index corresponds to 2001K, and so
+ * on.
+ */
+ @VisibleForTesting
+ final int[] mStepsAtOffsetCcts;
+ /**
+ * Pre-computed stepped CCTs. These will be accessed frequently; the memory cost of caching them
+ * is well-spent.
+ */
+ @VisibleForTesting
+ final int[] mSteppedCctsAtOffsetCcts;
+
+ CctEvaluator(int min, int max, int[] cctRangeMinimums, int[] steps) {
+ final int delta = max - min + 1;
+ mStepsAtOffsetCcts = new int[delta];
+ mSteppedCctsAtOffsetCcts = new int[delta];
+ mIndexOffset = min;
+
+ final int parallelArraysLength = cctRangeMinimums.length;
+ if (cctRangeMinimums.length != steps.length) {
+ Slog.e(TAG,
+ "Parallel arrays cctRangeMinimums and steps are different lengths; setting "
+ + "step of 1");
+ setStepOfOne();
+ } else if (parallelArraysLength == 0) {
+ Slog.e(TAG, "No cctRangeMinimums or steps are set; setting step of 1");
+ setStepOfOne();
+ } else {
+ int parallelArraysIndex = 0;
+ int index = 0;
+ int lastSteppedCct = Integer.MIN_VALUE;
+ while (index < delta) {
+ final int cct = index + mIndexOffset;
+ int nextParallelArraysIndex = parallelArraysIndex + 1;
+ while (nextParallelArraysIndex < parallelArraysLength
+ && cct >= cctRangeMinimums[nextParallelArraysIndex]) {
+ parallelArraysIndex = nextParallelArraysIndex;
+ nextParallelArraysIndex++;
+ }
+ mStepsAtOffsetCcts[index] = steps[parallelArraysIndex];
+ if (lastSteppedCct == Integer.MIN_VALUE
+ || Math.abs(lastSteppedCct - cct) >= steps[parallelArraysIndex]) {
+ lastSteppedCct = cct;
+ }
+ mSteppedCctsAtOffsetCcts[index] = lastSteppedCct;
+ index++;
+ }
+ }
+ }
+
+ @Override
+ public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
+ final int cct = (int) (startValue + fraction * (endValue - startValue));
+ final int index = cct - mIndexOffset;
+ if (index < 0 || index >= mSteppedCctsAtOffsetCcts.length) {
+ Slog.e(TAG, "steppedCctValueAt: returning same since invalid requested index=" + index);
+ return cct;
+ }
+ return mSteppedCctsAtOffsetCcts[index];
+ }
+
+ private void setStepOfOne() {
+ Arrays.fill(mStepsAtOffsetCcts, 1);
+ for (int i = 0; i < mSteppedCctsAtOffsetCcts.length; i++) {
+ mSteppedCctsAtOffsetCcts[i] = mIndexOffset + i;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 0284d9c..c0ea5fea 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -180,6 +180,8 @@
*/
private SparseIntArray mColorModeCompositionColorSpaces = null;
+ private final Object mCctTintApplierLock = new Object();
+
public ColorDisplayService(Context context) {
super(context);
mHandler = new TintHandler(DisplayThread.get().getLooper());
@@ -698,6 +700,79 @@
}
}
+ private void applyTintByCct(ColorTemperatureTintController tintController, boolean immediate) {
+ synchronized (mCctTintApplierLock) {
+ tintController.cancelAnimator();
+
+ final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
+ final int from = tintController.getAppliedCct();
+ final int to = tintController.isActivated() ? tintController.getTargetCct()
+ : tintController.getDisabledCct();
+
+ if (immediate) {
+ Slog.d(TAG, tintController.getClass().getSimpleName()
+ + " applied immediately: toCct=" + to + " fromCct=" + from);
+ dtm.setColorMatrix(tintController.getLevel(),
+ tintController.computeMatrixForCct(to));
+ tintController.setAppliedCct(to);
+ } else {
+ Slog.d(TAG, tintController.getClass().getSimpleName() + " animation started: toCct="
+ + to + " fromCct=" + from);
+ ValueAnimator valueAnimator = ValueAnimator.ofInt(from, to);
+ tintController.setAnimator(valueAnimator);
+ final CctEvaluator evaluator = tintController.getEvaluator();
+ if (evaluator != null) {
+ valueAnimator.setEvaluator(evaluator);
+ }
+ valueAnimator.setDuration(tintController.getTransitionDurationMilliseconds());
+ valueAnimator.setInterpolator(AnimationUtils.loadInterpolator(
+ getContext(), android.R.interpolator.linear));
+ valueAnimator.addUpdateListener((ValueAnimator animator) -> {
+ synchronized (mCctTintApplierLock) {
+ final int value = (int) animator.getAnimatedValue();
+ if (value != tintController.getAppliedCct()) {
+ dtm.setColorMatrix(tintController.getLevel(),
+ tintController.computeMatrixForCct(value));
+ tintController.setAppliedCct(value);
+ }
+ }
+ });
+ valueAnimator.addListener(new AnimatorListenerAdapter() {
+
+ private boolean mIsCancelled;
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ Slog.d(TAG, tintController.getClass().getSimpleName()
+ + " animation cancelled");
+ mIsCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ synchronized (mCctTintApplierLock) {
+ Slog.d(TAG, tintController.getClass().getSimpleName()
+ + " animation ended: wasCancelled=" + mIsCancelled
+ + " toCct=" + to
+ + " fromCct=" + from);
+ if (!mIsCancelled) {
+ // Ensure final color matrix is set at the end of the animation.
+ // If the animation is cancelled then don't set the final color
+ // matrix so the new animator can pick up from where this one left
+ // off.
+ dtm.setColorMatrix(tintController.getLevel(),
+ tintController.computeMatrixForCct(to));
+ tintController.setAppliedCct(to);
+ }
+ tintController.setAnimator(null);
+ }
+ }
+ });
+ valueAnimator.start();
+ }
+ }
+ }
+
/**
* Returns the first date time corresponding to the local time that occurs before the provided
* date time.
@@ -747,7 +822,7 @@
// If disabled, clear the tint. If enabled, do nothing more here and let the next
// temperature update set the correct tint.
- if (!activated) {
+ if (oldActivated && !activated) {
mHandler.sendEmptyMessage(MSG_APPLY_DISPLAY_WHITE_BALANCE);
}
}
@@ -1452,7 +1527,7 @@
public class ColorDisplayServiceInternal {
/** Sets whether DWB should be allowed in the current state. */
- public void setDisplayWhiteBalanceAllowed(boolean allowed) {
+ public void setDisplayWhiteBalanceAllowed(boolean allowed) {
mDisplayWhiteBalanceTintController.setAllowed(allowed);
updateDisplayWhiteBalanceStatus();
}
@@ -1464,8 +1539,8 @@
* @param cct the color temperature in Kelvin.
*/
public boolean setDisplayWhiteBalanceColorTemperature(int cct) {
- // Update the transform matrix even if it can't be applied.
- mDisplayWhiteBalanceTintController.setMatrix(cct);
+ // Update the transform target CCT even if it can't be applied.
+ mDisplayWhiteBalanceTintController.setTargetCct(cct);
if (mDisplayWhiteBalanceTintController.isActivated()) {
mHandler.sendEmptyMessage(MSG_APPLY_DISPLAY_WHITE_BALANCE);
@@ -1601,7 +1676,7 @@
applyTint(mNightDisplayTintController, false);
break;
case MSG_APPLY_DISPLAY_WHITE_BALANCE:
- applyTint(mDisplayWhiteBalanceTintController, false);
+ applyTintByCct(mDisplayWhiteBalanceTintController, false);
break;
}
}
diff --git a/services/core/java/com/android/server/display/color/ColorTemperatureTintController.java b/services/core/java/com/android/server/display/color/ColorTemperatureTintController.java
new file mode 100644
index 0000000..0fbd9d4
--- /dev/null
+++ b/services/core/java/com/android/server/display/color/ColorTemperatureTintController.java
@@ -0,0 +1,38 @@
+/*
+ * 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.color;
+
+abstract class ColorTemperatureTintController extends TintController {
+
+ abstract int getAppliedCct();
+
+ abstract void setAppliedCct(int cct);
+
+ abstract int getTargetCct();
+
+ abstract void setTargetCct(int cct);
+
+ /**
+ * Returns the CCT value most closely associated with the "disabled" (identity) matrix for
+ * this device, to use as the target when deactivating this transform.
+ */
+ abstract int getDisabledCct();
+
+ abstract float[] computeMatrixForCct(int cct);
+
+ abstract CctEvaluator getEvaluator();
+}
diff --git a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
index 106ac0c..bf0139f 100644
--- a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
+++ b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
@@ -21,6 +21,7 @@
import static com.android.server.display.color.DisplayTransformManager.LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.Size;
import android.content.Context;
import android.content.res.Resources;
@@ -36,7 +37,7 @@
import java.io.PrintWriter;
-final class DisplayWhiteBalanceTintController extends TintController {
+final class DisplayWhiteBalanceTintController extends ColorTemperatureTintController {
// Three chromaticity coordinates per color: X, Y, and Z
private static final int NUM_VALUES_PER_PRIMARY = 3;
@@ -52,9 +53,11 @@
private int mTemperatureDefault;
@VisibleForTesting
float[] mDisplayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
+ private int mDisplayNominalWhiteCct;
@VisibleForTesting
ColorSpace.Rgb mDisplayColorSpaceRGB;
private float[] mChromaticAdaptationMatrix;
+ // The temperature currently represented in the matrix.
@VisibleForTesting
int mCurrentColorTemperature;
private float[] mCurrentColorTemperatureXYZ;
@@ -65,6 +68,9 @@
private Boolean mIsAvailable;
// This feature becomes disallowed if the device is in an unsupported strong/light state.
private boolean mIsAllowed = true;
+ private int mTargetCct;
+ private int mAppliedCct;
+ private CctEvaluator mCctEvaluator;
private final DisplayManagerInternal mDisplayManagerInternal;
@@ -108,6 +114,9 @@
displayNominalWhiteXYZ[i] = Float.parseFloat(nominalWhiteValues[i]);
}
+ final int displayNominalWhiteCct = res.getInteger(
+ R.integer.config_displayWhiteBalanceDisplayNominalWhiteCct);
+
final int colorTemperatureMin = res.getInteger(
R.integer.config_displayWhiteBalanceColorTemperatureMin);
if (colorTemperatureMin <= 0) {
@@ -124,19 +133,28 @@
return;
}
- final int colorTemperature = res.getInteger(
+ final int defaultTemperature = res.getInteger(
R.integer.config_displayWhiteBalanceColorTemperatureDefault);
mTransitionDuration = res.getInteger(
R.integer.config_displayWhiteBalanceTransitionTime);
+ int[] cctRangeMinimums = res.getIntArray(
+ R.array.config_displayWhiteBalanceDisplayRangeMinimums);
+ int[] steps = res.getIntArray(R.array.config_displayWhiteBalanceDisplaySteps);
+
synchronized (mLock) {
mDisplayColorSpaceRGB = displayColorSpaceRGB;
mDisplayNominalWhiteXYZ = displayNominalWhiteXYZ;
+ mDisplayNominalWhiteCct = displayNominalWhiteCct;
+ mTargetCct = mDisplayNominalWhiteCct;
+ mAppliedCct = mDisplayNominalWhiteCct;
mTemperatureMin = colorTemperatureMin;
mTemperatureMax = colorTemperatureMax;
- mTemperatureDefault = colorTemperature;
+ mTemperatureDefault = defaultTemperature;
mSetUp = true;
+ mCctEvaluator = new CctEvaluator(mTemperatureMin, mTemperatureMax,
+ cctRangeMinimums, steps);
}
setMatrix(mTemperatureDefault);
@@ -144,8 +162,16 @@
@Override
public float[] getMatrix() {
- return mSetUp && isActivated() ? mMatrixDisplayWhiteBalance
- : ColorDisplayService.MATRIX_IDENTITY;
+ if (!mSetUp || !isActivated()) {
+ return ColorDisplayService.MATRIX_IDENTITY;
+ }
+ computeMatrixForCct(mAppliedCct);
+ return mMatrixDisplayWhiteBalance;
+ }
+
+ @Override
+ public int getTargetCct() {
+ return mTargetCct;
}
/**
@@ -174,6 +200,12 @@
@Override
public void setMatrix(int cct) {
+ setTargetCct(cct);
+ computeMatrixForCct(mTargetCct);
+ }
+
+ @Override
+ public void setTargetCct(int cct) {
if (!mSetUp) {
Slog.w(ColorDisplayService.TAG,
"Can't set display white balance temperature: uninitialized");
@@ -183,50 +215,93 @@
if (cct < mTemperatureMin) {
Slog.w(ColorDisplayService.TAG,
"Requested display color temperature is below allowed minimum");
- cct = mTemperatureMin;
+ mTargetCct = mTemperatureMin;
} else if (cct > mTemperatureMax) {
Slog.w(ColorDisplayService.TAG,
"Requested display color temperature is above allowed maximum");
- cct = mTemperatureMax;
+ mTargetCct = mTemperatureMax;
+ } else {
+ mTargetCct = cct;
+ }
+ }
+
+ @Override
+ public int getDisabledCct() {
+ return mDisplayNominalWhiteCct;
+ }
+
+ @Override
+ public float[] computeMatrixForCct(int cct) {
+ if (!mSetUp || cct == 0) {
+ Slog.w(ColorDisplayService.TAG, "Couldn't compute matrix for cct=" + cct);
+ return ColorDisplayService.MATRIX_IDENTITY;
}
synchronized (mLock) {
mCurrentColorTemperature = cct;
- // Adapt the display's nominal white point to match the requested CCT value
- mCurrentColorTemperatureXYZ = ColorSpace.cctToXyz(cct);
-
- mChromaticAdaptationMatrix =
- ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD,
- mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ);
-
- // Convert the adaptation matrix to RGB space
- float[] result = mul3x3(mChromaticAdaptationMatrix,
- mDisplayColorSpaceRGB.getTransform());
- result = mul3x3(mDisplayColorSpaceRGB.getInverseTransform(), result);
-
- // Normalize the transform matrix to peak white value in RGB space
- final float adaptedMaxR = result[0] + result[3] + result[6];
- final float adaptedMaxG = result[1] + result[4] + result[7];
- final float adaptedMaxB = result[2] + result[5] + result[8];
- final float denum = Math.max(Math.max(adaptedMaxR, adaptedMaxG), adaptedMaxB);
-
- Matrix.setIdentityM(mMatrixDisplayWhiteBalance, 0);
- for (int i = 0; i < result.length; i++) {
- result[i] /= denum;
- if (!isColorMatrixCoeffValid(result[i])) {
- Slog.e(ColorDisplayService.TAG, "Invalid DWB color matrix");
- return;
- }
+ if (cct == mDisplayNominalWhiteCct && !isActivated()) {
+ // DWB is finished turning off. Clear the matrix.
+ Matrix.setIdentityM(mMatrixDisplayWhiteBalance, 0);
+ } else {
+ computeMatrixForCctLocked(cct);
}
- java.lang.System.arraycopy(result, 0, mMatrixDisplayWhiteBalance, 0, 3);
- java.lang.System.arraycopy(result, 3, mMatrixDisplayWhiteBalance, 4, 3);
- java.lang.System.arraycopy(result, 6, mMatrixDisplayWhiteBalance, 8, 3);
+ Slog.d(ColorDisplayService.TAG, "computeDisplayWhiteBalanceMatrix: cct =" + cct
+ + " matrix =" + matrixToString(mMatrixDisplayWhiteBalance, 16));
+
+ return mMatrixDisplayWhiteBalance;
+ }
+ }
+
+ private void computeMatrixForCctLocked(int cct) {
+ // Adapt the display's nominal white point to match the requested CCT value
+ mCurrentColorTemperatureXYZ = ColorSpace.cctToXyz(cct);
+
+ mChromaticAdaptationMatrix =
+ ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD,
+ mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ);
+
+ // Convert the adaptation matrix to RGB space
+ float[] result = mul3x3(mChromaticAdaptationMatrix,
+ mDisplayColorSpaceRGB.getTransform());
+ result = mul3x3(mDisplayColorSpaceRGB.getInverseTransform(), result);
+
+ // Normalize the transform matrix to peak white value in RGB space
+ final float adaptedMaxR = result[0] + result[3] + result[6];
+ final float adaptedMaxG = result[1] + result[4] + result[7];
+ final float adaptedMaxB = result[2] + result[5] + result[8];
+ final float denum = Math.max(Math.max(adaptedMaxR, adaptedMaxG), adaptedMaxB);
+
+ Matrix.setIdentityM(mMatrixDisplayWhiteBalance, 0);
+
+ for (int i = 0; i < result.length; i++) {
+ result[i] /= denum;
+ if (!isColorMatrixCoeffValid(result[i])) {
+ Slog.e(ColorDisplayService.TAG, "Invalid DWB color matrix");
+ return;
+ }
}
- Slog.d(ColorDisplayService.TAG, "setDisplayWhiteBalanceTemperatureMatrix: cct = " + cct
- + " matrix = " + matrixToString(mMatrixDisplayWhiteBalance, 16));
+ java.lang.System.arraycopy(result, 0, mMatrixDisplayWhiteBalance, 0, 3);
+ java.lang.System.arraycopy(result, 3, mMatrixDisplayWhiteBalance, 4, 3);
+ java.lang.System.arraycopy(result, 6, mMatrixDisplayWhiteBalance, 8, 3);
+ }
+
+ @Override
+ int getAppliedCct() {
+ return mAppliedCct;
+ }
+
+ @Override
+ void setAppliedCct(int cct) {
+ mAppliedCct = cct;
+ }
+
+ @Override
+ @Nullable
+ CctEvaluator getEvaluator() {
+ return mCctEvaluator;
}
@Override
@@ -258,7 +333,10 @@
pw.println(" mTemperatureMin = " + mTemperatureMin);
pw.println(" mTemperatureMax = " + mTemperatureMax);
pw.println(" mTemperatureDefault = " + mTemperatureDefault);
+ pw.println(" mDisplayNominalWhiteCct = " + mDisplayNominalWhiteCct);
pw.println(" mCurrentColorTemperature = " + mCurrentColorTemperature);
+ pw.println(" mTargetCct = " + mTargetCct);
+ pw.println(" mAppliedCct = " + mAppliedCct);
pw.println(" mCurrentColorTemperatureXYZ = "
+ matrixToString(mCurrentColorTemperatureXYZ, 3));
pw.println(" mDisplayColorSpaceRGB RGB-to-XYZ = "
@@ -340,11 +418,7 @@
}
private boolean isColorMatrixCoeffValid(float coeff) {
- if (Float.isNaN(coeff) || Float.isInfinite(coeff)) {
- return false;
- }
-
- return true;
+ return !Float.isNaN(coeff) && !Float.isInfinite(coeff);
}
private boolean isColorMatrixValid(float[] matrix) {
@@ -352,8 +426,8 @@
return false;
}
- for (int i = 0; i < matrix.length; i++) {
- if (!isColorMatrixCoeffValid(matrix[i])) {
+ for (float value : matrix) {
+ if (!isColorMatrixCoeffValid(value)) {
return false;
}
}
diff --git a/services/core/java/com/android/server/display/color/TintController.java b/services/core/java/com/android/server/display/color/TintController.java
index c53ac06..384333a 100644
--- a/services/core/java/com/android/server/display/color/TintController.java
+++ b/services/core/java/com/android/server/display/color/TintController.java
@@ -16,6 +16,7 @@
package com.android.server.display.color;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.util.Slog;
@@ -28,14 +29,14 @@
*/
private static final long TRANSITION_DURATION = 3000L;
- private ColorDisplayService.TintValueAnimator mAnimator;
+ private ValueAnimator mAnimator;
private Boolean mIsActivated;
- public ColorDisplayService.TintValueAnimator getAnimator() {
+ public ValueAnimator getAnimator() {
return mAnimator;
}
- public void setAnimator(ColorDisplayService.TintValueAnimator animator) {
+ public void setAnimator(ValueAnimator animator) {
mAnimator = animator;
}
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 fd94be9..17f928a 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.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 @@
@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 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 @@
@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 @@
}
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 @@
}
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 @@
}
@Override
- public void registerSmoothDisplayObserver(@NonNull ContentResolver cr,
+ public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
@NonNull ContentObserver observer) {
- cr.registerContentObserver(SMOOTH_DISPLAY_URI, false /*notifyDescendants*/,
- observer, UserHandle.USER_SYSTEM);
- }
-
- @Override
- public void registerForcePeakRefreshRateObserver(@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 220a438..b82129b 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 @@
ERROR_DESTINATION,
ERROR_PARAMETER,
ERROR_PARAMETER_SHORT,
+ ERROR_PARAMETER_LONG,
})
public @interface ValidationResult {};
@@ -43,6 +44,7 @@
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 @@
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 @@
// 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 @@
// 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 @@
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,14 @@
}
}
- /** 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;
- OneByteRangeValidator(int minValue, int maxValue) {
+ MinimumOneByteRangeValidator(int minValue, int maxValue) {
mMinValue = minValue;
mMaxValue = maxValue;
}
@@ -918,6 +927,26 @@
}
}
+ /** Check if the given parameters are exactly one byte parameters and within range. */
+ private static class SingleByteRangeValidator implements ParameterValidator {
+ private final int mMinValue, mMaxValue;
+
+ SingleByteRangeValidator(int minValue, int maxValue) {
+ mMinValue = minValue;
+ mMaxValue = maxValue;
+ }
+
+ @Override
+ 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));
+ }
+ }
+
/**
* Check if the given Analogue Timer message parameters are valid. Valid parameters should
* adhere to message description of Analogue Timer defined in CEC 1.4 Specification : Message
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 75fe63a..9cd5272 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 @@
@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/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index d8716b3..6ec4022 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -562,7 +562,7 @@
key.append(",languageTag:").append(inputDevice.getKeyboardLanguageTag());
}
if (!TextUtils.isEmpty(inputDevice.getKeyboardLayoutType())) {
- key.append(",layoutType:").append(inputDevice.getKeyboardLanguageTag());
+ key.append(",layoutType:").append(inputDevice.getKeyboardLayoutType());
}
}
return key.toString();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index c70d555..57f8d14 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -856,13 +856,15 @@
@GuardedBy("ImfLock.class")
private final WeakHashMap<IBinder, IBinder> mImeTargetWindowMap = new WeakHashMap<>();
- private static final class SoftInputShowHideHistory {
+ @VisibleForTesting
+ static final class SoftInputShowHideHistory {
private final Entry[] mEntries = new Entry[16];
private int mNextIndex = 0;
private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
- private static final class Entry {
+ static final class Entry {
final int mSequenceNumber = sSequenceNumber.getAndIncrement();
+ @Nullable
final ClientState mClientState;
@SoftInputModeFlags
final int mFocusedWindowSoftInputMode;
@@ -874,7 +876,7 @@
final boolean mInFullscreenMode;
@NonNull
final String mFocusedWindowName;
- @NonNull
+ @Nullable
final EditorInfo mEditorInfo;
@NonNull
final String mRequestWindowName;
@@ -953,9 +955,13 @@
pw.print(prefix);
pw.print(" editorInfo: ");
- pw.print(" inputType=" + entry.mEditorInfo.inputType);
- pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
- pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
+ if (entry.mEditorInfo != null) {
+ pw.print(" inputType=" + entry.mEditorInfo.inputType);
+ pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
+ pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
+ } else {
+ pw.println("null");
+ }
pw.print(prefix);
pw.println(" focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString(
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 464a256..02b7053 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -16,6 +16,12 @@
package com.android.server.media;
+import static android.media.VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
+import static android.media.VolumeProvider.VOLUME_CONTROL_FIXED;
+import static android.media.VolumeProvider.VOLUME_CONTROL_RELATIVE;
+import static android.media.session.MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+import static android.media.session.MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -173,8 +179,8 @@
// Volume handling fields
private AudioAttributes mAudioAttrs;
private AudioManager mAudioManager;
- private int mVolumeType = PlaybackInfo.PLAYBACK_TYPE_LOCAL;
- private int mVolumeControlType = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
+ private int mVolumeType = PLAYBACK_TYPE_LOCAL;
+ private int mVolumeControlType = VOLUME_CONTROL_ABSOLUTE;
private int mMaxVolume = 0;
private int mCurrentVolume = 0;
private int mOptimisticVolume = -1;
@@ -309,13 +315,13 @@
if (checkPlaybackActiveState(true) || isSystemPriority()) {
flags &= ~AudioManager.FLAG_PLAY_SOUND;
}
- if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
+ if (mVolumeType == PLAYBACK_TYPE_LOCAL) {
// Adjust the volume with a handler not to be blocked by other system service.
int stream = getVolumeStream(mAudioAttrs);
postAdjustLocalVolume(stream, direction, flags, opPackageName, pid, uid,
asSystemService, useSuggested, previousFlagPlaySound);
} else {
- if (mVolumeControlType == VolumeProvider.VOLUME_CONTROL_FIXED) {
+ if (mVolumeControlType == VOLUME_CONTROL_FIXED) {
if (DEBUG) {
Log.d(TAG, "Session does not support volume adjustment");
}
@@ -354,7 +360,7 @@
private void setVolumeTo(String packageName, String opPackageName, int pid, int uid, int value,
int flags) {
- if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
+ if (mVolumeType == PLAYBACK_TYPE_LOCAL) {
int stream = getVolumeStream(mAudioAttrs);
final int volumeValue = value;
mHandler.post(new Runnable() {
@@ -371,7 +377,7 @@
}
});
} else {
- if (mVolumeControlType != VolumeProvider.VOLUME_CONTROL_ABSOLUTE) {
+ if (mVolumeControlType != VOLUME_CONTROL_ABSOLUTE) {
if (DEBUG) {
Log.d(TAG, "Session does not support setting volume");
}
@@ -433,7 +439,7 @@
*/
@Override
public boolean isPlaybackTypeLocal() {
- return mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+ return mVolumeType == PLAYBACK_TYPE_LOCAL;
}
@Override
@@ -495,7 +501,7 @@
@Override
public boolean canHandleVolumeKey() {
- return mVolumeControlType != VolumeProvider.VOLUME_CONTROL_FIXED;
+ return mVolumeControlType != VOLUME_CONTROL_FIXED;
}
@Override
@@ -528,13 +534,48 @@
pw.println(indent + "controllers: " + mControllerCallbackHolders.size());
pw.println(indent + "state=" + (mPlaybackState == null ? null : mPlaybackState.toString()));
pw.println(indent + "audioAttrs=" + mAudioAttrs);
- pw.println(indent + "volumeType=" + mVolumeType + ", controlType=" + mVolumeControlType
- + ", max=" + mMaxVolume + ", current=" + mCurrentVolume);
+ pw.append(indent)
+ .append("volumeType=")
+ .append(toVolumeTypeString(mVolumeType))
+ .append(", controlType=")
+ .append(toVolumeControlTypeString(mVolumeControlType))
+ .append(", max=")
+ .append(Integer.toString(mMaxVolume))
+ .append(", current=")
+ .append(Integer.toString(mCurrentVolume))
+ .append(", volumeControlId=")
+ .append(mVolumeControlId)
+ .println();
pw.println(indent + "metadata: " + mMetadataDescription);
pw.println(indent + "queueTitle=" + mQueueTitle + ", size="
+ (mQueue == null ? 0 : mQueue.size()));
}
+ private static String toVolumeControlTypeString(
+ @VolumeProvider.ControlType int volumeControlType) {
+ switch (volumeControlType) {
+ case VOLUME_CONTROL_FIXED:
+ return "FIXED";
+ case VOLUME_CONTROL_RELATIVE:
+ return "RELATIVE";
+ case VOLUME_CONTROL_ABSOLUTE:
+ return "ABSOLUTE";
+ default:
+ return TextUtils.formatSimple("unknown(%d)", volumeControlType);
+ }
+ }
+
+ private static String toVolumeTypeString(@PlaybackInfo.PlaybackType int volumeType) {
+ switch (volumeType) {
+ case PLAYBACK_TYPE_LOCAL:
+ return "LOCAL";
+ case PLAYBACK_TYPE_REMOTE:
+ return "REMOTE";
+ default:
+ return TextUtils.formatSimple("unknown(%d)", volumeType);
+ }
+ }
+
@Override
public String toString() {
return mPackageName + "/" + mTag + " (userId=" + mUserId + ")";
@@ -877,8 +918,8 @@
int stream = getVolumeStream(attributes);
int max = mAudioManager.getStreamMaxVolume(stream);
int current = mAudioManager.getStreamVolume(stream);
- return new PlaybackInfo(volumeType, VolumeProvider.VOLUME_CONTROL_ABSOLUTE, max,
- current, attributes, null);
+ return new PlaybackInfo(
+ volumeType, VOLUME_CONTROL_ABSOLUTE, max, current, attributes, null);
}
private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
@@ -1124,7 +1165,7 @@
boolean typeChanged;
synchronized (mLock) {
typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
- mVolumeType = PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+ mVolumeType = PLAYBACK_TYPE_LOCAL;
mVolumeControlId = null;
if (attributes != null) {
mAudioAttrs = attributes;
@@ -1148,7 +1189,7 @@
throws RemoteException {
boolean typeChanged;
synchronized (mLock) {
- typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+ typeChanged = mVolumeType == PLAYBACK_TYPE_LOCAL;
mVolumeType = PlaybackInfo.PLAYBACK_TYPE_REMOTE;
mVolumeControlType = control;
mMaxVolume = max;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 33e6a8f1..f0ab815 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -316,7 +316,6 @@
import com.android.server.notification.toast.ToastRecord;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.UserManagerInternal;
-import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.powerstats.StatsPullAtomCallbackImpl;
import com.android.server.statusbar.StatusBarManagerInternal;
@@ -2559,8 +2558,8 @@
Context.STATS_MANAGER),
getContext().getSystemService(TelephonyManager.class),
LocalServices.getService(ActivityManagerInternal.class),
- createToastRateLimiter(), new PermissionHelper(LocalServices.getService(
- PermissionManagerServiceInternal.class), AppGlobals.getPackageManager(),
+ createToastRateLimiter(), new PermissionHelper(getContext(),
+ AppGlobals.getPackageManager(),
AppGlobals.getPermissionManager()),
LocalServices.getService(UsageStatsManagerInternal.class),
getContext().getSystemService(TelecomManager.class),
@@ -11599,6 +11598,8 @@
StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
try {
listener.onNotificationPosted(sbnHolder, rankingUpdate);
+ } catch (android.os.DeadObjectException ex) {
+ Slog.wtf(TAG, "unable to notify listener (posted): " + info, ex);
} catch (RemoteException ex) {
Slog.e(TAG, "unable to notify listener (posted): " + info, ex);
}
@@ -11620,6 +11621,8 @@
reason = REASON_LISTENER_CANCEL;
}
listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason);
+ } catch (android.os.DeadObjectException ex) {
+ Slog.wtf(TAG, "unable to notify listener (removed): " + info, ex);
} catch (RemoteException ex) {
Slog.e(TAG, "unable to notify listener (removed): " + info, ex);
}
@@ -11630,6 +11633,8 @@
final INotificationListener listener = (INotificationListener) info.service;
try {
listener.onNotificationRankingUpdate(rankingUpdate);
+ } catch (android.os.DeadObjectException ex) {
+ Slog.wtf(TAG, "unable to notify listener (ranking update): " + info, ex);
} catch (RemoteException ex) {
Slog.e(TAG, "unable to notify listener (ranking update): " + info, ex);
}
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index b6fd822..93c83e1 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -25,6 +25,7 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.content.Context;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -37,7 +38,6 @@
import android.util.Slog;
import com.android.internal.util.ArrayUtils;
-import com.android.server.pm.permission.PermissionManagerServiceInternal;
import java.util.Collections;
import java.util.HashSet;
@@ -53,13 +53,13 @@
private static final String NOTIFICATION_PERMISSION = Manifest.permission.POST_NOTIFICATIONS;
- private final PermissionManagerServiceInternal mPmi;
+ private final Context mContext;
private final IPackageManager mPackageManager;
private final IPermissionManager mPermManager;
- public PermissionHelper(PermissionManagerServiceInternal pmi, IPackageManager packageManager,
+ public PermissionHelper(Context context, IPackageManager packageManager,
IPermissionManager permManager) {
- mPmi = pmi;
+ mContext = context;
mPackageManager = packageManager;
mPermManager = permManager;
}
@@ -71,7 +71,7 @@
public boolean hasPermission(int uid) {
final long callingId = Binder.clearCallingIdentity();
try {
- return mPmi.checkUidPermission(uid, NOTIFICATION_PERMISSION) == PERMISSION_GRANTED;
+ return mContext.checkPermission(NOTIFICATION_PERMISSION, -1, uid) == PERMISSION_GRANTED;
} finally {
Binder.restoreCallingIdentity(callingId);
}
@@ -193,8 +193,8 @@
return;
}
- boolean currentlyGranted = mPmi.checkPermission(packageName, NOTIFICATION_PERMISSION,
- userId) != PackageManager.PERMISSION_DENIED;
+ int uid = mPackageManager.getPackageUid(packageName, 0, userId);
+ boolean currentlyGranted = hasPermission(uid);
if (grant && !currentlyGranted) {
mPermManager.grantRuntimePermission(packageName, NOTIFICATION_PERMISSION, userId);
} else if (!grant && currentlyGranted) {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index a267e8a..5932929 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -4360,12 +4360,22 @@
// A new application appeared on /system, and we are seeing it for the first time.
// Its also not updated as we don't have a copy of it on /data. So, scan it in a
- // STOPPED state. Ignore if it's an APEX package since stopped state does not affect them.
+ // STOPPED state.
+ // We'll skip this step under the following conditions:
+ // - It's "android"
+ // - It's an APEX or overlay package since stopped state does not affect them.
+ // - It is enumerated with a <initial-package-state> tag having the stopped attribute
+ // set to false
final boolean isApexPkg = (scanFlags & SCAN_AS_APEX) != 0;
- if (mPm.mShouldStopSystemPackagesByDefault && scanSystemPartition
- && !pkgAlreadyExists && !isApexPkg) {
+ if (mPm.mShouldStopSystemPackagesByDefault
+ && scanSystemPartition
+ && !pkgAlreadyExists
+ && !isApexPkg
+ && !parsedPackage.isOverlayIsStatic()
+ ) {
String packageName = parsedPackage.getPackageName();
- if (!mPm.mInitialNonStoppedSystemPackages.contains(packageName)) {
+ if (!mPm.mInitialNonStoppedSystemPackages.contains(packageName)
+ && !"android".contentEquals(packageName)) {
scanFlags |= SCAN_AS_STOPPED_SYSTEM_APP;
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 29c5ada..97e7f6f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1148,12 +1148,21 @@
info.userId = userId;
info.installerPackageName = mInstallSource.mInstallerPackageName;
info.installerAttributionTag = mInstallSource.mInstallerAttributionTag;
+ info.resolvedBaseCodePath = null;
if (mContext.checkCallingOrSelfPermission(
Manifest.permission.READ_INSTALLED_SESSION_PATHS)
- == PackageManager.PERMISSION_GRANTED && mResolvedBaseFile != null) {
- info.resolvedBaseCodePath = mResolvedBaseFile.getAbsolutePath();
- } else {
- info.resolvedBaseCodePath = null;
+ == PackageManager.PERMISSION_GRANTED) {
+ File file = mResolvedBaseFile;
+ if (file == null) {
+ // Try to guess mResolvedBaseFile file.
+ final List<File> addedFiles = getAddedApksLocked();
+ if (addedFiles.size() > 0) {
+ file = addedFiles.get(0);
+ }
+ }
+ if (file != null) {
+ info.resolvedBaseCodePath = file.getAbsolutePath();
+ }
}
info.progress = progress;
info.sealed = mSealed;
@@ -1355,9 +1364,12 @@
@GuardedBy("mLock")
private String[] getStageDirContentsLocked() {
+ if (stageDir == null) {
+ return EmptyArray.STRING;
+ }
String[] result = stageDir.list();
if (result == null) {
- result = EmptyArray.STRING;
+ return EmptyArray.STRING;
}
return result;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b5108af..f482046 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5258,6 +5258,12 @@
}
@Override
+ public @NonNull List<String> getInitialNonStoppedSystemPackages() {
+ return mInitialNonStoppedSystemPackages != null
+ ? new ArrayList<>(mInitialNonStoppedSystemPackages) : new ArrayList<>();
+ }
+
+ @Override
public String[] getUnsuspendablePackagesForUser(String[] packageNames, int userId) {
Objects.requireNonNull(packageNames, "packageNames cannot be null");
mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS,
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index f78b611..58183f0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -782,6 +782,8 @@
getInFileDescriptor(), getOutFileDescriptor(), getErrFileDescriptor(),
new String[] { "list" }, getShellCallback(), adoptResultReceiver());
return 0;
+ case "initial-non-stopped-system-packages":
+ return runListInitialNonStoppedSystemPackages();
}
pw.println("Error: unknown list type '" + type + "'");
return -1;
@@ -794,6 +796,21 @@
return 0;
}
+ private int runListInitialNonStoppedSystemPackages() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final List<String> list = mInterface.getInitialNonStoppedSystemPackages();
+
+ Collections.sort(list);
+
+ for (String pkgName : list) {
+ pw.print("package:");
+ pw.print(pkgName);
+ pw.println();
+ }
+
+ return 0;
+ }
+
private int runListFeatures() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
final List<FeatureInfo> list = mInterface.getSystemAvailableFeatures().getList();
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 84a9888..2f0cea3 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -93,7 +93,6 @@
import android.text.TextUtils;
import android.text.format.TimeMigrationUtils;
import android.util.ArraySet;
-import android.util.AtomicFile;
import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Slog;
@@ -149,6 +148,7 @@
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
@@ -321,8 +321,7 @@
private final ArrayList<LauncherApps.ShortcutChangeCallback> mShortcutChangeCallbacks =
new ArrayList<>(1);
- @GuardedBy("mLock")
- private long mRawLastResetTime;
+ private final AtomicLong mRawLastResetTime = new AtomicLong(0);
/**
* User ID -> UserShortcuts
@@ -756,10 +755,15 @@
}
/** Return the base state file name */
- private AtomicFile getBaseStateFile() {
- final File path = new File(injectSystemDataPath(), FILENAME_BASE_STATE);
- path.mkdirs();
- return new AtomicFile(path);
+ final ResilientAtomicFile getBaseStateFile() {
+ File mainFile = new File(injectSystemDataPath(), FILENAME_BASE_STATE);
+ File temporaryBackup = new File(injectSystemDataPath(),
+ FILENAME_BASE_STATE + ".backup");
+ File reserveCopy = new File(injectSystemDataPath(),
+ FILENAME_BASE_STATE + ".reservecopy");
+ int fileMode = FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH;
+ return new ResilientAtomicFile(mainFile, temporaryBackup, reserveCopy, fileMode,
+ "base shortcut", null);
}
/**
@@ -976,80 +980,91 @@
writeAttr(out, name, intent.toUri(/* flags =*/ 0));
}
- @GuardedBy("mLock")
@VisibleForTesting
- void saveBaseStateLocked() {
- final AtomicFile file = getBaseStateFile();
- if (DEBUG || DEBUG_REBOOT) {
- Slog.d(TAG, "Saving to " + file.getBaseFile());
- }
+ void saveBaseState() {
+ try (ResilientAtomicFile file = getBaseStateFile()) {
+ if (DEBUG || DEBUG_REBOOT) {
+ Slog.d(TAG, "Saving to " + file.getBaseFile());
+ }
- FileOutputStream outs = null;
- try {
- outs = file.startWrite();
+ FileOutputStream outs = null;
+ try {
+ synchronized (mLock) {
+ outs = file.startWrite();
+ }
- // Write to XML
- TypedXmlSerializer out = Xml.resolveSerializer(outs);
- out.startDocument(null, true);
- out.startTag(null, TAG_ROOT);
+ // Write to XML
+ TypedXmlSerializer out = Xml.resolveSerializer(outs);
+ out.startDocument(null, true);
+ out.startTag(null, TAG_ROOT);
- // Body.
- writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime);
+ // Body.
+ // No locking required. Ok to add lock later if we save more data.
+ writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime.get());
- // Epilogue.
- out.endTag(null, TAG_ROOT);
- out.endDocument();
+ // Epilogue.
+ out.endTag(null, TAG_ROOT);
+ out.endDocument();
- // Close.
- file.finishWrite(outs);
- } catch (IOException e) {
- Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
- file.failWrite(outs);
+ // Close.
+ file.finishWrite(outs);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
+ file.failWrite(outs);
+ }
}
}
@GuardedBy("mLock")
private void loadBaseStateLocked() {
- mRawLastResetTime = 0;
+ mRawLastResetTime.set(0);
- final AtomicFile file = getBaseStateFile();
- if (DEBUG || DEBUG_REBOOT) {
- Slog.d(TAG, "Loading from " + file.getBaseFile());
- }
- try (FileInputStream in = file.openRead()) {
- TypedXmlPullParser parser = Xml.resolvePullParser(in);
-
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
- if (type != XmlPullParser.START_TAG) {
- continue;
- }
- final int depth = parser.getDepth();
- // Check the root tag
- final String tag = parser.getName();
- if (depth == 1) {
- if (!TAG_ROOT.equals(tag)) {
- Slog.e(TAG, "Invalid root tag: " + tag);
- return;
- }
- continue;
- }
- // Assume depth == 2
- switch (tag) {
- case TAG_LAST_RESET_TIME:
- mRawLastResetTime = parseLongAttribute(parser, ATTR_VALUE);
- break;
- default:
- Slog.e(TAG, "Invalid tag: " + tag);
- break;
- }
+ try (ResilientAtomicFile file = getBaseStateFile()) {
+ if (DEBUG || DEBUG_REBOOT) {
+ Slog.d(TAG, "Loading from " + file.getBaseFile());
}
- } catch (FileNotFoundException e) {
- // Use the default
- } catch (IOException | XmlPullParserException e) {
- Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
+ FileInputStream in = null;
+ try {
+ in = file.openRead();
+ if (in == null) {
+ throw new FileNotFoundException(file.getBaseFile().getAbsolutePath());
+ }
- mRawLastResetTime = 0;
+ TypedXmlPullParser parser = Xml.resolvePullParser(in);
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ final int depth = parser.getDepth();
+ // Check the root tag
+ final String tag = parser.getName();
+ if (depth == 1) {
+ if (!TAG_ROOT.equals(tag)) {
+ Slog.e(TAG, "Invalid root tag: " + tag);
+ return;
+ }
+ continue;
+ }
+ // Assume depth == 2
+ switch (tag) {
+ case TAG_LAST_RESET_TIME:
+ mRawLastResetTime.set(parseLongAttribute(parser, ATTR_VALUE));
+ break;
+ default:
+ Slog.e(TAG, "Invalid tag: " + tag);
+ break;
+ }
+ }
+ } catch (FileNotFoundException e) {
+ // Use the default
+ } catch (IOException | XmlPullParserException e) {
+ // Remove corrupted file and retry.
+ file.failRead(in, e);
+ loadBaseStateLocked();
+ return;
+ }
}
// Adjust the last reset time.
getLastResetTimeLocked();
@@ -1067,8 +1082,7 @@
"user shortcut", null);
}
- @GuardedBy("mLock")
- private void saveUserLocked(@UserIdInt int userId) {
+ private void saveUser(@UserIdInt int userId) {
try (ResilientAtomicFile file = getUserFile(userId)) {
FileOutputStream os = null;
try {
@@ -1076,9 +1090,10 @@
Slog.d(TAG, "Saving to " + file);
}
- os = file.startWrite();
-
- saveUserInternalLocked(userId, os, /* forBackup= */ false);
+ synchronized (mLock) {
+ os = file.startWrite();
+ saveUserInternalLocked(userId, os, /* forBackup= */ false);
+ }
file.finishWrite(os);
@@ -1215,16 +1230,19 @@
}
try {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "shortcutSaveDirtyInfo");
+ List<Integer> dirtyUserIds = new ArrayList<>();
synchronized (mLock) {
- for (int i = mDirtyUserIds.size() - 1; i >= 0; i--) {
- final int userId = mDirtyUserIds.get(i);
- if (userId == UserHandle.USER_NULL) { // USER_NULL for base state.
- saveBaseStateLocked();
- } else {
- saveUserLocked(userId);
- }
+ List<Integer> tmp = mDirtyUserIds;
+ mDirtyUserIds = dirtyUserIds;
+ dirtyUserIds = tmp;
+ }
+ for (int i = dirtyUserIds.size() - 1; i >= 0; i--) {
+ final int userId = dirtyUserIds.get(i);
+ if (userId == UserHandle.USER_NULL) { // USER_NULL for base state.
+ saveBaseState();
+ } else {
+ saveUser(userId);
}
- mDirtyUserIds.clear();
}
} catch (Exception e) {
wtf("Exception in saveDirtyInfo", e);
@@ -1237,14 +1255,14 @@
@GuardedBy("mLock")
long getLastResetTimeLocked() {
updateTimesLocked();
- return mRawLastResetTime;
+ return mRawLastResetTime.get();
}
/** Return the next reset time. */
@GuardedBy("mLock")
long getNextResetTimeLocked() {
updateTimesLocked();
- return mRawLastResetTime + mResetInterval;
+ return mRawLastResetTime.get() + mResetInterval;
}
static boolean isClockValid(long time) {
@@ -1259,25 +1277,26 @@
final long now = injectCurrentTimeMillis();
- final long prevLastResetTime = mRawLastResetTime;
+ final long prevLastResetTime = mRawLastResetTime.get();
+ long newLastResetTime = prevLastResetTime;
- if (mRawLastResetTime == 0) { // first launch.
+ if (newLastResetTime == 0) { // first launch.
// TODO Randomize??
- mRawLastResetTime = now;
- } else if (now < mRawLastResetTime) {
+ newLastResetTime = now;
+ } else if (now < newLastResetTime) {
// Clock rewound.
if (isClockValid(now)) {
Slog.w(TAG, "Clock rewound");
// TODO Randomize??
- mRawLastResetTime = now;
+ newLastResetTime = now;
}
- } else {
- if ((mRawLastResetTime + mResetInterval) <= now) {
- final long offset = mRawLastResetTime % mResetInterval;
- mRawLastResetTime = ((now / mResetInterval) * mResetInterval) + offset;
- }
+ } else if ((newLastResetTime + mResetInterval) <= now) {
+ final long offset = newLastResetTime % mResetInterval;
+ newLastResetTime = ((now / mResetInterval) * mResetInterval) + offset;
}
- if (prevLastResetTime != mRawLastResetTime) {
+
+ mRawLastResetTime.set(newLastResetTime);
+ if (prevLastResetTime != newLastResetTime) {
scheduleSaveBaseState();
}
}
@@ -2705,9 +2724,7 @@
}
void resetAllThrottlingInner() {
- synchronized (mLock) {
- mRawLastResetTime = injectCurrentTimeMillis();
- }
+ mRawLastResetTime.set(injectCurrentTimeMillis());
scheduleSaveBaseState();
Slog.i(TAG, "ShortcutManager: throttling counter reset for all users");
}
@@ -2725,8 +2742,8 @@
}
getPackageShortcutsLocked(packageName, userId)
.resetRateLimitingForCommandLineNoSaving();
- saveUserLocked(userId);
}
+ saveUser(userId);
}
// We override this method in unit tests to do a simpler check.
@@ -4505,8 +4522,8 @@
dumpCurrentTime(pw);
pw.println();
});
- saveUserLocked(userId);
}
+ saveUser(userId);
}
// === Dump ===
@@ -4717,9 +4734,9 @@
pw.print(formatTime(now));
pw.print(" Raw last reset: [");
- pw.print(mRawLastResetTime);
+ pw.print(mRawLastResetTime.get());
pw.print("] ");
- pw.print(formatTime(mRawLastResetTime));
+ pw.print(formatTime(mRawLastResetTime.get()));
final long last = getLastResetTimeLocked();
pw.print(" Last reset: [");
diff --git a/services/core/java/com/android/server/pm/UserJourneyLogger.java b/services/core/java/com/android/server/pm/UserJourneyLogger.java
new file mode 100644
index 0000000..f48a166
--- /dev/null
+++ b/services/core/java/com/android/server/pm/UserJourneyLogger.java
@@ -0,0 +1,552 @@
+/*
+ * 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.pm;
+
+import static android.os.UserManager.USER_TYPE_FULL_DEMO;
+import static android.os.UserManager.USER_TYPE_FULL_GUEST;
+import static android.os.UserManager.USER_TYPE_FULL_RESTRICTED;
+import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
+import static android.os.UserManager.USER_TYPE_FULL_SYSTEM;
+import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
+import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
+import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS;
+
+import static com.android.internal.util.FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNKNOWN;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.UserInfo;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * This class is logging User Lifecycle statsd events and synchronise User Lifecycle Journeys
+ * by making sure all events are called in correct order and errors are reported in case of
+ * unexpected journeys. This class also makes sure that all user sub-journeys are logged so
+ * for example User Switch also log User Start Journey.
+ */
+public class UserJourneyLogger {
+
+ public static final int ERROR_CODE_INVALID_SESSION_ID = 0;
+ public static final int ERROR_CODE_UNSPECIFIED = -1;
+ /*
+ * Possible reasons for ERROR_CODE_INCOMPLETE_OR_TIMEOUT to occur:
+ * - A user switch journey is received while another user switch journey is in
+ * process for the same user.
+ * - A user switch journey is received while user start journey is in process for
+ * the same user.
+ * - A user start journey is received while another user start journey is in process
+ * for the same user.
+ * In all cases potentially an incomplete, timed-out session or multiple
+ * simultaneous requests. It is not possible to keep track of multiple sessions for
+ * the same user, so previous session is abandoned.
+ */
+ public static final int ERROR_CODE_INCOMPLETE_OR_TIMEOUT = 2;
+ public static final int ERROR_CODE_ABORTED = 3;
+ public static final int ERROR_CODE_NULL_USER_INFO = 4;
+ public static final int ERROR_CODE_USER_ALREADY_AN_ADMIN = 5;
+ public static final int ERROR_CODE_USER_IS_NOT_AN_ADMIN = 6;
+
+ @IntDef(prefix = {"ERROR_CODE"}, value = {
+ ERROR_CODE_UNSPECIFIED,
+ ERROR_CODE_INCOMPLETE_OR_TIMEOUT,
+ ERROR_CODE_ABORTED,
+ ERROR_CODE_NULL_USER_INFO,
+ ERROR_CODE_USER_ALREADY_AN_ADMIN,
+ ERROR_CODE_USER_IS_NOT_AN_ADMIN,
+ ERROR_CODE_INVALID_SESSION_ID
+ })
+ public @interface UserJourneyErrorCode {
+ }
+
+ // The various user journeys, defined in the UserLifecycleJourneyReported atom for statsd
+ public static final int USER_JOURNEY_UNKNOWN =
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__UNKNOWN;
+ public static final int USER_JOURNEY_USER_SWITCH_FG =
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_SWITCH_FG;
+ public static final int USER_JOURNEY_USER_SWITCH_UI =
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_SWITCH_UI;
+ public static final int USER_JOURNEY_USER_START =
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_START;
+ public static final int USER_JOURNEY_USER_CREATE =
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE;
+ public static final int USER_JOURNEY_USER_STOP =
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_STOP;
+ public static final int USER_JOURNEY_USER_REMOVE =
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_REMOVE;
+ public static final int USER_JOURNEY_GRANT_ADMIN =
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN;
+ public static final int USER_JOURNEY_REVOKE_ADMIN =
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN;
+
+ @IntDef(prefix = {"USER_JOURNEY"}, value = {
+ USER_JOURNEY_UNKNOWN,
+ USER_JOURNEY_USER_SWITCH_FG,
+ USER_JOURNEY_USER_SWITCH_UI,
+ USER_JOURNEY_USER_START,
+ USER_JOURNEY_USER_STOP,
+ USER_JOURNEY_USER_CREATE,
+ USER_JOURNEY_USER_REMOVE,
+ USER_JOURNEY_GRANT_ADMIN,
+ USER_JOURNEY_REVOKE_ADMIN
+ })
+ public @interface UserJourney {
+ }
+
+
+ // The various user lifecycle events, defined in the UserLifecycleEventOccurred atom for statsd
+ public static final int USER_LIFECYCLE_EVENT_UNKNOWN =
+ USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNKNOWN;
+ public static final int USER_LIFECYCLE_EVENT_SWITCH_USER =
+ FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__SWITCH_USER;
+ public static final int USER_LIFECYCLE_EVENT_START_USER =
+ FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__START_USER;
+ public static final int USER_LIFECYCLE_EVENT_CREATE_USER =
+ FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__CREATE_USER;
+ public static final int USER_LIFECYCLE_EVENT_REMOVE_USER =
+ FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REMOVE_USER;
+ public static final int USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED =
+ FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__USER_RUNNING_LOCKED;
+ public static final int USER_LIFECYCLE_EVENT_UNLOCKING_USER =
+ FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNLOCKING_USER;
+ public static final int USER_LIFECYCLE_EVENT_UNLOCKED_USER =
+ FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNLOCKED_USER;
+ public static final int USER_LIFECYCLE_EVENT_STOP_USER =
+ FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__STOP_USER;
+ public static final int USER_LIFECYCLE_EVENT_GRANT_ADMIN =
+ FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__GRANT_ADMIN;
+ public static final int USER_LIFECYCLE_EVENT_REVOKE_ADMIN =
+ FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REVOKE_ADMIN;
+
+ @IntDef(prefix = {"USER_LIFECYCLE_EVENT"}, value = {
+ USER_LIFECYCLE_EVENT_UNKNOWN,
+ USER_LIFECYCLE_EVENT_SWITCH_USER,
+ USER_LIFECYCLE_EVENT_START_USER,
+ USER_LIFECYCLE_EVENT_CREATE_USER,
+ USER_LIFECYCLE_EVENT_REMOVE_USER,
+ USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
+ USER_LIFECYCLE_EVENT_UNLOCKING_USER,
+ USER_LIFECYCLE_EVENT_UNLOCKED_USER,
+ USER_LIFECYCLE_EVENT_STOP_USER,
+ USER_LIFECYCLE_EVENT_GRANT_ADMIN,
+ USER_LIFECYCLE_EVENT_REVOKE_ADMIN
+ })
+ public @interface UserLifecycleEvent {
+ }
+
+ // User lifecycle event state, defined in the UserLifecycleEventOccurred atom for statsd
+ public static final int EVENT_STATE_BEGIN =
+ FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__BEGIN;
+ public static final int EVENT_STATE_FINISH =
+ FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__FINISH;
+ public static final int EVENT_STATE_NONE =
+ FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__NONE;
+ public static final int EVENT_STATE_CANCEL =
+ FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__CANCEL;
+ public static final int EVENT_STATE_ERROR =
+ FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__ERROR;
+
+ @IntDef(prefix = {"EVENT_STATE"}, value = {
+ EVENT_STATE_BEGIN,
+ EVENT_STATE_FINISH,
+ EVENT_STATE_NONE,
+ EVENT_STATE_CANCEL,
+ EVENT_STATE_ERROR,
+ })
+ public @interface UserLifecycleEventState {
+ }
+
+ private static final int USER_ID_KEY_MULTIPLICATION = 100;
+
+ private final Object mLock = new Object();
+
+ /**
+ * {@link UserIdInt} and {@link UserJourney} to {@link UserJourneySession} mapping used for
+ * statsd logging for the UserLifecycleJourneyReported and UserLifecycleEventOccurred atoms.
+ */
+ @GuardedBy("mLock")
+ private final SparseArray<UserJourneySession> mUserIdToUserJourneyMap = new SparseArray<>();
+
+ /**
+ * Returns event equivalent of given journey
+ */
+ @UserLifecycleEvent
+ private static int journeyToEvent(@UserJourney int journey) {
+ switch (journey) {
+ case USER_JOURNEY_USER_SWITCH_UI:
+ case USER_JOURNEY_USER_SWITCH_FG:
+ return USER_LIFECYCLE_EVENT_SWITCH_USER;
+ case USER_JOURNEY_USER_START:
+ return USER_LIFECYCLE_EVENT_START_USER;
+ case USER_JOURNEY_USER_CREATE:
+ return USER_LIFECYCLE_EVENT_CREATE_USER;
+ case USER_JOURNEY_USER_STOP:
+ return USER_LIFECYCLE_EVENT_STOP_USER;
+ case USER_JOURNEY_USER_REMOVE:
+ return USER_LIFECYCLE_EVENT_REMOVE_USER;
+ case USER_JOURNEY_GRANT_ADMIN:
+ return USER_LIFECYCLE_EVENT_GRANT_ADMIN;
+ case USER_JOURNEY_REVOKE_ADMIN:
+ return USER_LIFECYCLE_EVENT_REVOKE_ADMIN;
+ default:
+ return USER_LIFECYCLE_EVENT_UNKNOWN;
+ }
+ }
+
+ /**
+ * Returns the enum defined in the statsd UserLifecycleJourneyReported atom corresponding to
+ * the user type.
+ * Changes to this method require changes in CTS file
+ * com.android.cts.packagemanager.stats.device.UserInfoUtil
+ * which is duplicate for CTS tests purposes.
+ */
+ public static int getUserTypeForStatsd(@NonNull String userType) {
+ switch (userType) {
+ case USER_TYPE_FULL_SYSTEM:
+ return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SYSTEM;
+ case USER_TYPE_FULL_SECONDARY:
+ return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY;
+ case USER_TYPE_FULL_GUEST:
+ return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_GUEST;
+ case USER_TYPE_FULL_DEMO:
+ return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_DEMO;
+ case USER_TYPE_FULL_RESTRICTED:
+ return FrameworkStatsLog
+ .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_RESTRICTED;
+ case USER_TYPE_PROFILE_MANAGED:
+ return FrameworkStatsLog
+ .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__PROFILE_MANAGED;
+ case USER_TYPE_SYSTEM_HEADLESS:
+ return FrameworkStatsLog
+ .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__SYSTEM_HEADLESS;
+ case USER_TYPE_PROFILE_CLONE:
+ return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__PROFILE_CLONE;
+ default:
+ return FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
+ }
+ }
+
+ /**
+ * Map error code to the event finish state.
+ */
+ @UserLifecycleEventState
+ private static int errorToFinishState(@UserJourneyErrorCode int errorCode) {
+ switch (errorCode) {
+ case ERROR_CODE_ABORTED:
+ return EVENT_STATE_CANCEL;
+ case ERROR_CODE_UNSPECIFIED:
+ return EVENT_STATE_FINISH;
+ default:
+ return EVENT_STATE_ERROR;
+ }
+ }
+
+ /**
+ * Simply logging USER_LIFECYCLE_JOURNEY_REPORTED if session exists.
+ * If session does not exist then it logs ERROR_CODE_INVALID_SESSION_ID
+ */
+ @VisibleForTesting
+ public void logUserLifecycleJourneyReported(@Nullable UserJourneySession session,
+ @UserJourney int journey, @UserIdInt int originalUserId, @UserIdInt int targetUserId,
+ int userType, int userFlags, @UserJourneyErrorCode int errorCode) {
+ if (session == null) {
+ writeUserLifecycleJourneyReported(-1, journey, originalUserId, targetUserId,
+ userType, userFlags, ERROR_CODE_INVALID_SESSION_ID);
+ } else {
+ writeUserLifecycleJourneyReported(
+ session.mSessionId, journey, originalUserId, targetUserId, userType, userFlags,
+ errorCode);
+ }
+ }
+
+ /**
+ * Helper method for spy testing
+ */
+ @VisibleForTesting
+ public void writeUserLifecycleJourneyReported(long sessionId, int journey, int originalUserId,
+ int targetUserId, int userType, int userFlags, int errorCode) {
+ FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED,
+ sessionId, journey, originalUserId, targetUserId, userType, userFlags,
+ errorCode);
+ }
+
+ /**
+ * Simply logging USER_LIFECYCLE_EVENT_OCCURRED if session exists.
+ * If session does not exist then it logs ERROR_CODE_INVALID_SESSION_ID
+ * and EVENT_STATE_ERROR
+ */
+ @VisibleForTesting
+ public void logUserLifecycleEventOccurred(UserJourneySession session,
+ @UserIdInt int targetUserId, @UserLifecycleEvent int event,
+ @UserLifecycleEventState int state, @UserJourneyErrorCode int errorCode) {
+ if (session == null) {
+ writeUserLifecycleEventOccurred(-1, targetUserId, event,
+ EVENT_STATE_ERROR, ERROR_CODE_INVALID_SESSION_ID);
+ } else {
+ writeUserLifecycleEventOccurred(session.mSessionId, targetUserId, event, state,
+ errorCode);
+ }
+ }
+
+ /**
+ * Helper method for spy testing
+ */
+ @VisibleForTesting
+ public void writeUserLifecycleEventOccurred(long sessionId, int userId, int event, int state,
+ int errorCode) {
+ FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED,
+ sessionId, userId, event, state, errorCode);
+ }
+
+ /**
+ * statsd helper method for logging the given event for the UserLifecycleEventOccurred statsd
+ * atom. It finds the user journey session for target user id and logs it as that journey.
+ */
+ public void logUserLifecycleEvent(@UserIdInt int userId, @UserLifecycleEvent int event,
+ @UserLifecycleEventState int eventState) {
+ final UserJourneySession userJourneySession = findUserJourneySession(userId);
+ logUserLifecycleEventOccurred(userJourneySession, userId,
+ event, eventState, UserJourneyLogger.ERROR_CODE_UNSPECIFIED);
+ }
+
+ /**
+ * Returns first user session from mUserIdToUserJourneyMap for given user id,
+ * or null if user id was not found in mUserIdToUserJourneyMap.
+ */
+ private @Nullable UserJourneySession findUserJourneySession(@UserIdInt int userId) {
+ synchronized (mLock) {
+ final int keyMapSize = mUserIdToUserJourneyMap.size();
+ for (int i = 0; i < keyMapSize; i++) {
+ int key = mUserIdToUserJourneyMap.keyAt(i);
+ if (key / USER_ID_KEY_MULTIPLICATION == userId) {
+ return mUserIdToUserJourneyMap.get(key);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns unique id for user and journey. For example if user id = 11 and journey = 7
+ * then unique key = 11 * 100 + 7 = 1107
+ */
+ private int getUserJourneyKey(@UserIdInt int targetUserId, @UserJourney int journey) {
+ // We leave 99 for user journeys ids.
+ return (targetUserId * USER_ID_KEY_MULTIPLICATION) + journey;
+ }
+
+ /**
+ * Special use case when user journey incomplete or timeout and current user is unclear
+ */
+ @VisibleForTesting
+ public UserJourneySession finishAndClearIncompleteUserJourney(@UserIdInt int targetUserId,
+ @UserJourney int journey) {
+ synchronized (mLock) {
+ final int key = getUserJourneyKey(targetUserId, journey);
+ final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(key);
+ if (userJourneySession != null) {
+ logUserLifecycleEventOccurred(
+ userJourneySession,
+ targetUserId,
+ journeyToEvent(userJourneySession.mJourney),
+ EVENT_STATE_ERROR,
+ UserJourneyLogger.ERROR_CODE_INCOMPLETE_OR_TIMEOUT);
+
+ logUserLifecycleJourneyReported(
+ userJourneySession,
+ journey,
+ /* originalUserId= */ -1,
+ targetUserId,
+ getUserTypeForStatsd(""), -1,
+ ERROR_CODE_INCOMPLETE_OR_TIMEOUT);
+ mUserIdToUserJourneyMap.remove(key);
+
+ return userJourneySession;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Log user journey event and report finishing without error
+ */
+ public UserJourneySession logUserJourneyFinish(@UserIdInt int originalUserId,
+ UserInfo targetUser, @UserJourney int journey) {
+ return logUserJourneyFinishWithError(originalUserId, targetUser, journey,
+ ERROR_CODE_UNSPECIFIED);
+ }
+
+ /**
+ * Special case when it is unknown which user switch journey was used and checking both
+ */
+ @VisibleForTesting
+ public UserJourneySession logUserSwitchJourneyFinish(@UserIdInt int originalUserId,
+ UserInfo targetUser) {
+ synchronized (mLock) {
+ final int key_fg = getUserJourneyKey(targetUser.id, USER_JOURNEY_USER_SWITCH_FG);
+ final int key_ui = getUserJourneyKey(targetUser.id, USER_JOURNEY_USER_SWITCH_UI);
+
+ if (mUserIdToUserJourneyMap.contains(key_fg)) {
+ return logUserJourneyFinish(originalUserId, targetUser,
+ USER_JOURNEY_USER_SWITCH_FG);
+ }
+
+ if (mUserIdToUserJourneyMap.contains(key_ui)) {
+ return logUserJourneyFinish(originalUserId, targetUser,
+ USER_JOURNEY_USER_SWITCH_UI);
+ }
+
+ return null;
+ }
+ }
+
+ /**
+ * Log user journey event and report finishing with error
+ */
+ public UserJourneySession logUserJourneyFinishWithError(@UserIdInt int originalUserId,
+ UserInfo targetUser, @UserJourney int journey, @UserJourneyErrorCode int errorCode) {
+ synchronized (mLock) {
+ final int state = errorToFinishState(errorCode);
+ final int key = getUserJourneyKey(targetUser.id, journey);
+ final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(key);
+ if (userJourneySession != null) {
+ logUserLifecycleEventOccurred(
+ userJourneySession, targetUser.id,
+ journeyToEvent(userJourneySession.mJourney),
+ state,
+ errorCode);
+
+ logUserLifecycleJourneyReported(
+ userJourneySession,
+ journey, originalUserId, targetUser.id,
+ getUserTypeForStatsd(targetUser.userType),
+ targetUser.flags,
+ errorCode);
+ mUserIdToUserJourneyMap.remove(key);
+
+ return userJourneySession;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Log event and report finish when user is null. This is edge case when UserInfo
+ * can not be passed because it is null, therefore all information are passed as arguments.
+ */
+ public UserJourneySession logNullUserJourneyError(@UserJourney int journey,
+ @UserIdInt int currentUserId, @UserIdInt int targetUserId, String targetUserType,
+ int targetUserFlags) {
+ synchronized (mLock) {
+ final int key = getUserJourneyKey(targetUserId, journey);
+ final UserJourneySession session = mUserIdToUserJourneyMap.get(key);
+
+ logUserLifecycleEventOccurred(
+ session, targetUserId, journeyToEvent(journey),
+ EVENT_STATE_ERROR,
+ ERROR_CODE_NULL_USER_INFO);
+
+ logUserLifecycleJourneyReported(
+ session, journey, currentUserId, targetUserId,
+ getUserTypeForStatsd(targetUserType), targetUserFlags,
+ ERROR_CODE_NULL_USER_INFO);
+
+ mUserIdToUserJourneyMap.remove(key);
+ return session;
+ }
+ }
+
+ /**
+ * Log for user creation finish event and report. This is edge case when target user id is
+ * different in begin event and finish event as it is unknown what is user id
+ * until it has been created.
+ */
+ public UserJourneySession logUserCreateJourneyFinish(@UserIdInt int originalUserId,
+ UserInfo targetUser) {
+ synchronized (mLock) {
+ // we do not know user id until we create new user which is why we use -1
+ // as user id to create and find session, but we log correct id.
+ final int key = getUserJourneyKey(-1, USER_JOURNEY_USER_CREATE);
+ final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(key);
+ if (userJourneySession != null) {
+ logUserLifecycleEventOccurred(
+ userJourneySession, targetUser.id,
+ USER_LIFECYCLE_EVENT_CREATE_USER,
+ EVENT_STATE_FINISH,
+ ERROR_CODE_UNSPECIFIED);
+
+ logUserLifecycleJourneyReported(
+ userJourneySession,
+ USER_JOURNEY_USER_CREATE, originalUserId, targetUser.id,
+ getUserTypeForStatsd(targetUser.userType),
+ targetUser.flags,
+ ERROR_CODE_UNSPECIFIED);
+ mUserIdToUserJourneyMap.remove(key);
+
+ return userJourneySession;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Adds new UserJourneySession to mUserIdToUserJourneyMap and log UserJourneyEvent Begin state
+ */
+ public UserJourneySession logUserJourneyBegin(@UserIdInt int targetId,
+ @UserJourney int journey) {
+ final long newSessionId = ThreadLocalRandom.current().nextLong(1, Long.MAX_VALUE);
+ synchronized (mLock) {
+ final int key = getUserJourneyKey(targetId, journey);
+ final UserJourneySession userJourneySession =
+ new UserJourneySession(newSessionId, journey);
+ mUserIdToUserJourneyMap.append(key, userJourneySession);
+
+ logUserLifecycleEventOccurred(
+ userJourneySession, targetId,
+ journeyToEvent(userJourneySession.mJourney),
+ EVENT_STATE_BEGIN,
+ ERROR_CODE_UNSPECIFIED);
+
+ return userJourneySession;
+ }
+ }
+
+ /**
+ * Helper class to store user journey and session id.
+ *
+ * <p> User journey tracks a chain of user lifecycle events occurring during different user
+ * activities such as user start, user switch, and user creation.
+ */
+ public static class UserJourneySession {
+ public final long mSessionId;
+ @UserJourney
+ public final int mJourney;
+
+ @VisibleForTesting
+ public UserJourneySession(long sessionId, @UserJourney int journey) {
+ mJourney = journey;
+ mSessionId = sessionId;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 5f8efe2..b92cdde 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -23,6 +23,15 @@
import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_ABORTED;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_UNSPECIFIED;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_USER_ALREADY_AN_ADMIN;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_USER_IS_NOT_AN_ADMIN;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_GRANT_ADMIN;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_REVOKE_ADMIN;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_CREATE;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_REMOVE;
+
import android.Manifest;
import android.accounts.Account;
import android.accounts.AccountManager;
@@ -162,7 +171,6 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@@ -519,6 +527,8 @@
@GuardedBy("mUserLifecycleListeners")
private final ArrayList<UserLifecycleListener> mUserLifecycleListeners = new ArrayList<>();
+ private final UserJourneyLogger mUserJourneyLogger = new UserJourneyLogger();
+
private final LockPatternUtils mLockPatternUtils;
private final String ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK =
@@ -1580,45 +1590,56 @@
@Override
public void setUserAdmin(@UserIdInt int userId) {
checkManageUserAndAcrossUsersFullPermission("set user admin");
- final long sessionId = logGrantAdminJourneyBegin(userId);
+ mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_GRANT_ADMIN);
UserInfo info;
synchronized (mPackagesLock) {
synchronized (mUsersLock) {
info = getUserInfoLU(userId);
}
- if (info == null || info.isAdmin()) {
- // Exit if no user found with that id, or the user is already an Admin.
- logUserJourneyError(sessionId,
- FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN,
- userId);
+ if (info == null) {
+ // Exit if no user found with that id,
+ mUserJourneyLogger.logNullUserJourneyError(USER_JOURNEY_GRANT_ADMIN,
+ getCurrentUserId(), userId, /* userType */ "", /* userFlags */ -1);
+ return;
+ } else if (info.isAdmin()) {
+ // Exit if the user is already an Admin.
+ mUserJourneyLogger.logUserJourneyFinishWithError(getCurrentUserId(), info,
+ USER_JOURNEY_GRANT_ADMIN, ERROR_CODE_USER_ALREADY_AN_ADMIN);
return;
}
info.flags ^= UserInfo.FLAG_ADMIN;
writeUserLP(getUserDataLU(info.id));
}
- logGrantAdminJourneyFinish(sessionId, userId, info.userType, info.flags);
+ mUserJourneyLogger.logUserJourneyFinishWithError(getCurrentUserId(), info,
+ USER_JOURNEY_GRANT_ADMIN, ERROR_CODE_UNSPECIFIED);
}
@Override
public void revokeUserAdmin(@UserIdInt int userId) {
checkManageUserAndAcrossUsersFullPermission("revoke admin privileges");
- final long sessionId = logRevokeAdminJourneyBegin(userId);
+ mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_REVOKE_ADMIN);
UserData user;
synchronized (mPackagesLock) {
synchronized (mUsersLock) {
user = getUserDataLU(userId);
- if (user == null || !user.info.isAdmin()) {
- // Exit if no user found with that id, or the user is not an Admin.
- logUserJourneyError(sessionId, FrameworkStatsLog
- .USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN,
- userId);
+ if (user == null) {
+ // Exit if no user found with that id
+ mUserJourneyLogger.logNullUserJourneyError(
+ USER_JOURNEY_REVOKE_ADMIN,
+ getCurrentUserId(), userId, "", -1);
+ return;
+ } else if (!user.info.isAdmin()) {
+ // Exit if no user is not an Admin.
+ mUserJourneyLogger.logUserJourneyFinishWithError(getCurrentUserId(), user.info,
+ USER_JOURNEY_REVOKE_ADMIN, ERROR_CODE_USER_IS_NOT_AN_ADMIN);
return;
}
user.info.flags ^= UserInfo.FLAG_ADMIN;
writeUserLP(user);
}
}
- logRevokeAdminJourneyFinish(sessionId, userId, user.info.userType, user.info.flags);
+ mUserJourneyLogger.logUserJourneyFinishWithError(getCurrentUserId(), user.info,
+ USER_JOURNEY_REVOKE_ADMIN, ERROR_CODE_UNSPECIFIED);
}
/**
@@ -4700,16 +4721,20 @@
final int noneUserId = -1;
final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
t.traceBegin("createUser-" + flags);
- final long sessionId = logUserCreateJourneyBegin(noneUserId);
+ mUserJourneyLogger.logUserJourneyBegin(noneUserId, USER_JOURNEY_USER_CREATE);
UserInfo newUser = null;
try {
newUser = createUserInternalUncheckedNoTracing(name, userType, flags, parentId,
preCreate, disallowedPackages, t, token);
return newUser;
} finally {
- logUserCreateJourneyFinish(sessionId,
- newUser != null ? newUser.id : noneUserId, userType, flags,
- newUser != null);
+ if (newUser != null) {
+ mUserJourneyLogger.logUserCreateJourneyFinish(getCurrentUserId(), newUser);
+ } else {
+ mUserJourneyLogger.logNullUserJourneyError(
+ USER_JOURNEY_USER_CREATE,
+ getCurrentUserId(), noneUserId, userType, flags);
+ }
t.traceEnd();
}
}
@@ -5198,137 +5223,6 @@
&& !userTypeDetails.getName().equals(UserManager.USER_TYPE_FULL_RESTRICTED);
}
- private long logUserCreateJourneyBegin(@UserIdInt int userId) {
- return logUserJourneyBegin(
- FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE,
- userId);
- }
-
- private void logUserCreateJourneyFinish(long sessionId, @UserIdInt int userId, String userType,
- @UserInfoFlag int flags, boolean finish) {
- logUserJourneyFinish(sessionId,
- FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE,
- userId, userType, flags, finish);
- }
-
- private long logUserRemoveJourneyBegin(@UserIdInt int userId) {
- return logUserJourneyBegin(
- FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_REMOVE,
- userId);
- }
-
- private void logUserRemoveJourneyFinish(long sessionId, @UserIdInt int userId, String userType,
- @UserInfoFlag int flags, boolean finish) {
- logUserJourneyFinish(sessionId,
- FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_REMOVE,
- userId, userType, flags, finish);
- }
-
- private long logGrantAdminJourneyBegin(@UserIdInt int userId) {
- return logUserJourneyBegin(
- FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN,
- userId);
- }
-
- private void logGrantAdminJourneyFinish(long sessionId, @UserIdInt int userId, String userType,
- @UserInfoFlag int flags) {
- logUserJourneyFinish(sessionId,
- FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN,
- userId, userType, flags, true);
- }
-
- private long logRevokeAdminJourneyBegin(@UserIdInt int userId) {
- return logUserJourneyBegin(
- FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN,
- userId);
- }
-
- private void logRevokeAdminJourneyFinish(long sessionId, @UserIdInt int userId, String userType,
- @UserInfoFlag int flags) {
- logUserJourneyFinish(sessionId,
- FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN,
- userId, userType, flags, true);
- }
-
- private void logUserJourneyFinish(long sessionId, int journey, @UserIdInt int userId,
- String userType, @UserInfoFlag int flags, boolean finish) {
-
- // log the journey atom with the user metadata
- FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED, sessionId,
- journey, /* origin_user= */ getCurrentUserId(), userId,
- UserManager.getUserTypeForStatsd(userType), flags);
-
- int event;
- switch (journey) {
- case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE:
- event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__CREATE_USER;
- break;
- case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_REMOVE:
- event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REMOVE_USER;
- break;
- case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN:
- event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__GRANT_ADMIN;
- break;
- case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN:
- event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REVOKE_ADMIN;
- break;
- default:
- throw new IllegalArgumentException("Journey " + journey + " not expected.");
- }
- FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED, sessionId, userId,
- event,
- finish ? FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__FINISH
- : FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__NONE);
- }
-
- private long logUserJourneyBegin(int journey, @UserIdInt int userId) {
- final long sessionId = ThreadLocalRandom.current().nextLong(1, Long.MAX_VALUE);
-
- // log the event atom to indicate the event start
- int event;
- switch (journey) {
- case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_CREATE:
- event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__CREATE_USER;
- break;
- case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_REMOVE:
- event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REMOVE_USER;
- break;
- case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN:
- event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__GRANT_ADMIN;
- break;
- case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN:
- event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REVOKE_ADMIN;
- break;
- default:
- throw new IllegalArgumentException("Journey " + journey + " not expected.");
- }
-
- FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED, sessionId, userId,
- event, FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__BEGIN);
- return sessionId;
- }
-
- private void logUserJourneyError(long sessionId, int journey, @UserIdInt int userId) {
-
- // log the journey atom with the user metadata
- FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED, sessionId,
- journey, /* origin_user= */ getCurrentUserId(), userId);
-
- int event;
- switch (journey) {
- case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN:
- event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__GRANT_ADMIN;
- break;
- case FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN:
- event = FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__REVOKE_ADMIN;
- break;
- default:
- throw new IllegalArgumentException("Journey " + journey + " not expected.");
- }
- FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED, sessionId, userId,
- event, FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__STATE__ERROR);
- }
-
/** Register callbacks for statsd pulled atoms. */
private void registerStatsCallbacks() {
final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
@@ -5352,7 +5246,8 @@
if (size > 1) {
for (int idx = 0; idx < size; idx++) {
final UserInfo user = users.get(idx);
- final int userTypeStandard = UserManager.getUserTypeForStatsd(user.userType);
+ final int userTypeStandard = mUserJourneyLogger
+ .getUserTypeForStatsd(user.userType);
final String userTypeCustom = (userTypeStandard == FrameworkStatsLog
.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN)
?
@@ -5635,7 +5530,7 @@
writeUserLP(userData);
}
- final long sessionId = logUserRemoveJourneyBegin(userId);
+ mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_USER_REMOVE);
try {
mAppOpsService.removeUser(userId);
@@ -5657,13 +5552,17 @@
@Override
public void userStopped(int userIdParam) {
finishRemoveUser(userIdParam);
- logUserRemoveJourneyFinish(sessionId, userIdParam,
- userData.info.userType, userData.info.flags, true);
+ int originUserId = UserManagerService.this.getCurrentUserId();
+ mUserJourneyLogger.logUserJourneyFinishWithError(originUserId,
+ userData.info, USER_JOURNEY_USER_REMOVE,
+ ERROR_CODE_UNSPECIFIED);
}
@Override
public void userStopAborted(int userIdParam) {
- logUserRemoveJourneyFinish(sessionId, userIdParam,
- userData.info.userType, userData.info.flags, false);
+ int originUserId = UserManagerService.this.getCurrentUserId();
+ mUserJourneyLogger.logUserJourneyFinishWithError(originUserId,
+ userData.info, USER_JOURNEY_USER_REMOVE,
+ ERROR_CODE_ABORTED);
}
});
} catch (RemoteException e) {
@@ -7297,9 +7196,9 @@
final UserInfo userInfo = getUserInfo(userIds[i]);
if (userInfo == null) {
// Not possible because the input user ids should all be valid
- userTypes[i] = UserManager.getUserTypeForStatsd("");
+ userTypes[i] = mUserJourneyLogger.getUserTypeForStatsd("");
} else {
- userTypes[i] = UserManager.getUserTypeForStatsd(userInfo.userType);
+ userTypes[i] = mUserJourneyLogger.getUserTypeForStatsd(userInfo.userType);
}
}
return userTypes;
@@ -7536,4 +7435,11 @@
.getBoolean(R.bool.config_canSwitchToHeadlessSystemUser);
}
+ /**
+ * Returns instance of {@link com.android.server.pm.UserJourneyLogger}.
+ */
+ public UserJourneyLogger getUserJourneyLogger() {
+ return mUserJourneyLogger;
+ }
+
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 9ff98be..f8954b7 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -5690,8 +5690,14 @@
}
if (eventTime > now) {
- Slog.e(TAG, "Event time " + eventTime + " cannot be newer than " + now);
- throw new IllegalArgumentException("event time must not be in the future");
+ Slog.wtf(TAG, "Event cannot be newer than the current time ("
+ + "now=" + now
+ + ", eventTime=" + eventTime
+ + ", displayId=" + displayId
+ + ", event=" + PowerManager.userActivityEventToString(event)
+ + ", flags=" + flags
+ + ")");
+ return;
}
final int uid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index f971db9..e796275 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -161,7 +161,13 @@
mDisplayTrustGrantedMessage = (flags & FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0;
if ((flags & FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0) {
mWaitingForTrustableDowngrade = true;
- setSecurityWindowTimer();
+ resultCallback.thenAccept(result -> {
+ if (result.getStatus() == GrantTrustResult.STATUS_UNLOCKED_BY_GRANT) {
+ // if we are not unlocked by grantTrust, then we don't need to
+ // have the timer for the security window
+ setSecurityWindowTimer();
+ }
+ });
} else {
mWaitingForTrustableDowngrade = false;
}
@@ -562,6 +568,7 @@
* @see android.service.trust.TrustAgentService#onDeviceLocked()
*/
public void onDeviceLocked() {
+ mWithinSecurityLockdownWindow = false;
try {
if (mTrustAgentService != null) mTrustAgentService.onDeviceLocked();
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 8786005..1ab9823 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -393,6 +393,23 @@
true /* overrideHardTimeout */);
}
+ private void cancelBothTrustableAlarms(int userId) {
+ TrustableTimeoutAlarmListener idleTimeout =
+ mIdleTrustableTimeoutAlarmListenerForUser.get(
+ userId);
+ TrustableTimeoutAlarmListener trustableTimeout =
+ mTrustableTimeoutAlarmListenerForUser.get(
+ userId);
+ if (idleTimeout != null && idleTimeout.isQueued()) {
+ idleTimeout.setQueued(false);
+ mAlarmManager.cancel(idleTimeout);
+ }
+ if (trustableTimeout != null && trustableTimeout.isQueued()) {
+ trustableTimeout.setQueued(false);
+ mAlarmManager.cancel(trustableTimeout);
+ }
+ }
+
private void handleScheduleTrustedTimeout(int userId, boolean shouldOverride) {
long when = SystemClock.elapsedRealtime() + TRUST_TIMEOUT_IN_MILLIS;
TrustedTimeoutAlarmListener alarm = mTrustTimeoutAlarmListenerForUser.get(userId);
@@ -657,6 +674,11 @@
resultCallback.complete(new GrantTrustResult(STATUS_UNLOCKED_BY_GRANT));
}
}
+
+ if ((wasTrusted || wasTrustable) && pendingTrustState == TrustState.UNTRUSTED) {
+ if (DEBUG) Slog.d(TAG, "Trust was revoked, destroy trustable alarms");
+ cancelBothTrustableAlarms(userId);
+ }
}
private void updateTrustUsuallyManaged(int userId, boolean managed) {
@@ -1908,7 +1930,11 @@
handleScheduleTrustTimeout(shouldOverride, timeoutType);
break;
case MSG_REFRESH_TRUSTABLE_TIMERS_AFTER_AUTH:
- refreshTrustableTimers(msg.arg1);
+ TrustableTimeoutAlarmListener trustableAlarm =
+ mTrustableTimeoutAlarmListenerForUser.get(msg.arg1);
+ if (trustableAlarm != null && trustableAlarm.isQueued()) {
+ refreshTrustableTimers(msg.arg1);
+ }
break;
}
}
@@ -2160,7 +2186,7 @@
TrustedTimeoutAlarmListener otherAlarm;
boolean otherAlarmPresent;
if (ENABLE_ACTIVE_UNLOCK_FLAG) {
- cancelBothTrustableAlarms();
+ cancelBothTrustableAlarms(mUserId);
otherAlarm = mTrustTimeoutAlarmListenerForUser.get(mUserId);
otherAlarmPresent = (otherAlarm != null) && otherAlarm.isQueued();
if (otherAlarmPresent) {
@@ -2172,23 +2198,6 @@
}
}
- private void cancelBothTrustableAlarms() {
- TrustableTimeoutAlarmListener idleTimeout =
- mIdleTrustableTimeoutAlarmListenerForUser.get(
- mUserId);
- TrustableTimeoutAlarmListener trustableTimeout =
- mTrustableTimeoutAlarmListenerForUser.get(
- mUserId);
- if (idleTimeout != null && idleTimeout.isQueued()) {
- idleTimeout.setQueued(false);
- mAlarmManager.cancel(idleTimeout);
- }
- if (trustableTimeout != null && trustableTimeout.isQueued()) {
- trustableTimeout.setQueued(false);
- mAlarmManager.cancel(trustableTimeout);
- }
- }
-
private void disableRenewableTrustWhileNonrenewableTrustIsPresent() {
// if non-renewable trust is running, we need to temporarily prevent
// renewable trust from being used
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 98d2d3d..45c7c9a 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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
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 {
@@ -2465,12 +2445,36 @@
/**
* TODO(multi-display) Extends this method with specific display.
- * Propagate ambient state to wallpaper engine.
+ * Propagate ambient state to wallpaper engine(s).
*
* @param inAmbientMode {@code true} when in ambient mode, {@code false} otherwise.
* @param animationDuration Duration of the animation, or 0 when immediate.
*/
public void setInAmbientMode(boolean inAmbientMode, long animationDuration) {
+ if (mIsLockscreenLiveWallpaperEnabled) {
+ List<IWallpaperEngine> engines = new ArrayList<>();
+ synchronized (mLock) {
+ mInAmbientMode = inAmbientMode;
+ for (WallpaperData data : getActiveWallpapers()) {
+ if (data.connection.mInfo == null
+ || data.connection.mInfo.supportsAmbientMode()) {
+ // TODO(multi-display) Extends this method with specific display.
+ IWallpaperEngine engine = data.connection
+ .getDisplayConnectorOrCreate(DEFAULT_DISPLAY).mEngine;
+ if (engine != null) engines.add(engine);
+ }
+ }
+ }
+ for (IWallpaperEngine engine : engines) {
+ try {
+ engine.setInAmbientMode(inAmbientMode, animationDuration);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to set ambient mode", e);
+ }
+ }
+ return;
+ }
+
final IWallpaperEngine engine;
synchronized (mLock) {
mInAmbientMode = inAmbientMode;
@@ -2495,10 +2499,25 @@
}
/**
- * Propagate a wake event to the wallpaper engine.
+ * Propagate a wake event to the wallpaper engine(s).
*/
public void notifyWakingUp(int x, int y, @NonNull Bundle extras) {
synchronized (mLock) {
+ if (mIsLockscreenLiveWallpaperEnabled) {
+ for (WallpaperData data : getActiveWallpapers()) {
+ data.connection.forEachDisplayConnector(displayConnector -> {
+ if (displayConnector.mEngine != null) {
+ try {
+ displayConnector.mEngine.dispatchWallpaperCommand(
+ WallpaperManager.COMMAND_WAKING_UP, x, y, -1, extras);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to dispatch COMMAND_WAKING_UP", e);
+ }
+ }
+ });
+ }
+ return;
+ }
final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
if (data != null && data.connection != null) {
data.connection.forEachDisplayConnector(
@@ -2517,10 +2536,26 @@
}
/**
- * Propagate a sleep event to the wallpaper engine.
+ * Propagate a sleep event to the wallpaper engine(s).
*/
public void notifyGoingToSleep(int x, int y, @NonNull Bundle extras) {
synchronized (mLock) {
+ if (mIsLockscreenLiveWallpaperEnabled) {
+ for (WallpaperData data : getActiveWallpapers()) {
+ data.connection.forEachDisplayConnector(displayConnector -> {
+ if (displayConnector.mEngine != null) {
+ try {
+ displayConnector.mEngine.dispatchWallpaperCommand(
+ WallpaperManager.COMMAND_GOING_TO_SLEEP, x, y, -1,
+ extras);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to dispatch COMMAND_GOING_TO_SLEEP", e);
+ }
+ }
+ });
+ }
+ return;
+ }
final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
if (data != null && data.connection != null) {
data.connection.forEachDisplayConnector(
@@ -2540,11 +2575,27 @@
}
/**
- * Propagates screen turned on event to wallpaper engine.
+ * Propagates screen turned on event to wallpaper engine(s).
*/
@Override
public void notifyScreenTurnedOn(int displayId) {
synchronized (mLock) {
+ if (mIsLockscreenLiveWallpaperEnabled) {
+ for (WallpaperData data : getActiveWallpapers()) {
+ if (data.connection.containsDisplay(displayId)) {
+ final IWallpaperEngine engine = data.connection
+ .getDisplayConnectorOrCreate(displayId).mEngine;
+ if (engine != null) {
+ try {
+ engine.onScreenTurnedOn();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to notify that the screen turned on", e);
+ }
+ }
+ }
+ }
+ return;
+ }
final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
if (data != null
&& data.connection != null
@@ -2565,11 +2616,27 @@
/**
- * Propagate screen turning on event to wallpaper engine.
+ * Propagate screen turning on event to wallpaper engine(s).
*/
@Override
public void notifyScreenTurningOn(int displayId) {
synchronized (mLock) {
+ if (mIsLockscreenLiveWallpaperEnabled) {
+ for (WallpaperData data : getActiveWallpapers()) {
+ if (data.connection.containsDisplay(displayId)) {
+ final IWallpaperEngine engine = data.connection
+ .getDisplayConnectorOrCreate(displayId).mEngine;
+ if (engine != null) {
+ try {
+ engine.onScreenTurningOn();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to notify that the screen is turning on", e);
+ }
+ }
+ }
+ }
+ return;
+ }
final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
if (data != null
&& data.connection != null
@@ -2596,6 +2663,17 @@
return true;
}
+ private WallpaperData[] getActiveWallpapers() {
+ WallpaperData systemWallpaper = mWallpaperMap.get(mCurrentUserId);
+ WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId);
+ boolean systemValid = systemWallpaper != null && systemWallpaper.connection != null;
+ boolean lockValid = lockWallpaper != null && lockWallpaper.connection != null;
+ return systemValid && lockValid ? new WallpaperData[]{systemWallpaper, lockWallpaper}
+ : systemValid ? new WallpaperData[]{systemWallpaper}
+ : lockValid ? new WallpaperData[]{lockWallpaper}
+ : new WallpaperData[0];
+ }
+
private IWallpaperEngine getEngine(int which, int userId, int displayId) {
WallpaperData wallpaperData = findWallpaperAtDisplay(userId, displayId);
if (wallpaperData == null) return null;
@@ -3095,6 +3173,9 @@
newWallpaper.userId);
if (lockedWallpaper != null) {
detachWallpaperLocked(lockedWallpaper);
+ if (same) {
+ updateEngineFlags(newWallpaper);
+ }
}
mLockWallpaperMap.remove(newWallpaper.userId);
}
@@ -3430,6 +3511,27 @@
}
}
+ // 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 78c066b..26b40b4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -582,6 +582,9 @@
boolean mPauseSchedulePendingForPip = false;
+ // Gets set to indicate that the activity is currently being auto-pipped.
+ boolean mAutoEnteringPip = false;
+
private void updateEnterpriseThumbnailDrawable(Context context) {
DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
mEnterpriseThumbnailDrawable = dpm.getResources().getDrawable(
@@ -4908,9 +4911,14 @@
mTransitionController.setStatusBarTransitionDelay(
mPendingRemoteAnimation.getStatusBarTransitionDelay());
} else {
- if (mPendingOptions == null
- || mPendingOptions.getAnimationType() == ANIM_SCENE_TRANSITION) {
- // Scene transition will run on the client side.
+ if (mPendingOptions == null) {
+ return;
+ } else if (mPendingOptions.getAnimationType() == ANIM_SCENE_TRANSITION) {
+ // Scene transition will run on the client side, so just notify transition
+ // controller but don't clear the animation information from the options since they
+ // need to be sent to the animating activity.
+ mTransitionController.setOverrideAnimation(
+ AnimationOptions.makeSceneTransitionAnimOptions(), null, null);
return;
}
applyOptionsAnimation(mPendingOptions, intent);
@@ -5220,6 +5228,11 @@
}
logAppCompatState();
if (!visible) {
+ final InputTarget imeInputTarget = mDisplayContent.getImeInputTarget();
+ mLastImeShown = imeInputTarget != null && imeInputTarget.getWindowState() != null
+ && imeInputTarget.getWindowState().mActivityRecord == this
+ && mDisplayContent.mInputMethodWindow != null
+ && mDisplayContent.mInputMethodWindow.isVisible();
finishOrAbortReplacingWindow();
}
return true;
@@ -5400,7 +5413,9 @@
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
@@ -5607,11 +5622,6 @@
}
if (!visible) {
- final InputTarget imeInputTarget = mDisplayContent.getImeInputTarget();
- mLastImeShown = imeInputTarget != null && imeInputTarget.getWindowState() != null
- && imeInputTarget.getWindowState().mActivityRecord == this
- && mDisplayContent.mInputMethodWindow != null
- && mDisplayContent.mInputMethodWindow.isVisible();
mImeInsetsFrozenUntilStartInput = true;
}
@@ -6092,8 +6102,7 @@
try {
mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
PauseActivityItem.obtain(finishing, false /* userLeaving */,
- configChangeFlags, false /* dontReport */,
- false /* autoEnteringPip */));
+ configChangeFlags, false /* dontReport */, mAutoEnteringPip));
} catch (Exception e) {
Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e);
}
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index bfe2986..a6e5040 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -45,6 +45,7 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
@@ -554,7 +555,25 @@
.execute();
}
+ /**
+ * A quick path (skip general intent/task resolving) to start recents animation if the recents
+ * (or home) activity is available in background.
+ * @return {@code true} if the recents activity is moved to front.
+ */
boolean startExistingRecentsIfPossible(Intent intent, ActivityOptions options) {
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startExistingRecents");
+ if (startExistingRecents(intent, options)) {
+ return true;
+ }
+ // Else follow the standard launch procedure.
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ return false;
+ }
+
+ private boolean startExistingRecents(Intent intent, ActivityOptions options) {
final int activityType = mService.getRecentTasks().getRecentsComponent()
.equals(intent.getComponent()) ? ACTIVITY_TYPE_RECENTS : ACTIVITY_TYPE_HOME;
final Task rootTask = mService.mRootWindowContainer.getDefaultTaskDisplayArea()
@@ -563,6 +582,7 @@
final ActivityRecord r = rootTask.topRunningActivity();
if (r == null || r.isVisibleRequested() || !r.attachedToProcess()
|| !r.mActivityComponent.equals(intent.getComponent())
+ || !mService.isCallerRecents(r.getUid())
// Recents keeps invisible while device is locked.
|| r.mDisplayContent.isKeyguardLocked()) {
return false;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index a27f3e4..19a12f2 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1468,9 +1468,8 @@
// transition based on a sub-action.
// Only do the create here (and defer requestStart) since startActivityInner might abort.
final TransitionController transitionController = r.mTransitionController;
- Transition newTransition = (!transitionController.isCollecting()
- && transitionController.getTransitionPlayer() != null)
- ? transitionController.createTransition(TRANSIT_OPEN) : null;
+ Transition newTransition = transitionController.isShellTransitionsEnabled()
+ ? transitionController.createAndStartCollecting(TRANSIT_OPEN) : null;
RemoteTransition remoteTransition = r.takeRemoteTransition();
try {
mService.deferWindowLayout();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 1f4606b..b816dad 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3592,15 +3592,21 @@
}
}
+ boolean enterPictureInPictureMode(@NonNull ActivityRecord r,
+ @NonNull PictureInPictureParams params, boolean fromClient) {
+ return enterPictureInPictureMode(r, params, fromClient, false /* isAutoEnter */);
+ }
+
/**
* Puts the given activity in picture in picture mode if possible.
*
* @param fromClient true if this comes from a client call (eg. Activity.enterPip).
+ * @param isAutoEnter true if this comes from an automatic pip-enter.
* @return true if the activity is now in picture-in-picture mode, or false if it could not
* enter picture-in-picture mode.
*/
boolean enterPictureInPictureMode(@NonNull ActivityRecord r,
- @NonNull PictureInPictureParams params, boolean fromClient) {
+ @NonNull PictureInPictureParams params, boolean fromClient, boolean isAutoEnter) {
// If the activity is already in picture in picture mode, then just return early
if (r.inPinnedWindowingMode()) {
return true;
@@ -3635,6 +3641,7 @@
return;
}
r.setPictureInPictureParams(params);
+ r.mAutoEnteringPip = isAutoEnter;
mRootWindowContainer.moveActivityToPinnedRootTask(r,
null /* launchIntoPipHostActivity */, "enterPictureInPictureMode",
transition);
@@ -3643,6 +3650,7 @@
r.getTask().schedulePauseActivity(r, false /* userLeaving */,
false /* pauseImmediately */, true /* autoEnteringPip */, "auto-pip");
}
+ r.mAutoEnteringPip = false;
}
};
@@ -5753,23 +5761,6 @@
boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent,
BackgroundStartPrivileges backgroundStartPrivileges) {
assertPackageMatchesCallingUid(callingPackage);
- // A quick path (skip general intent/task resolving) to start recents animation if the
- // recents (or home) activity is available in background.
- if (options != null && options.getOriginalOptions() != null
- && options.getOriginalOptions().getTransientLaunch() && isCallerRecents(uid)) {
- try {
- synchronized (mGlobalLock) {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "startExistingRecents");
- if (mActivityStartController.startExistingRecentsIfPossible(
- intent, options.getOriginalOptions())) {
- return ActivityManager.START_TASK_TO_FRONT;
- }
- // Else follow the standard launch procedure.
- }
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
- }
return getActivityStartController().startActivityInPackage(uid, realCallingPid,
realCallingUid, callingPackage, callingFeatureId, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags, options, userId, inTask,
@@ -6345,6 +6336,8 @@
public void cleanupDisabledPackageComponents(
String packageName, Set<String> disabledClasses, int userId, boolean booted) {
synchronized (mGlobalLock) {
+ // In case if setWindowManager hasn't been called yet when booting.
+ if (mRootWindowContainer == null) return;
// Clean-up disabled activities.
if (mRootWindowContainer.finishDisabledPackageActivities(
packageName, disabledClasses, true /* doit */, false /* evenPersistent */,
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 7a11120..7e78393 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -267,7 +267,12 @@
op.mDrawTransaction = null;
if (DEBUG) Slog.d(TAG, "finishOp merge transaction " + windowToken.getTopChild());
}
- if (op.mAction == Operation.ACTION_FADE) {
+ if (op.mAction == Operation.ACTION_TOGGLE_IME) {
+ if (DEBUG) Slog.d(TAG, "finishOp fade-in IME " + windowToken.getTopChild());
+ fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_TOKEN_TRANSFORM,
+ (type, anim) -> mDisplayContent.getInsetsStateController()
+ .getImeSourceProvider().reportImeDrawnForOrganizer());
+ } else if (op.mAction == Operation.ACTION_FADE) {
if (DEBUG) Slog.d(TAG, "finishOp fade-in " + windowToken.getTopChild());
// The previous animation leash will be dropped when preparing fade-in animation, so
// simply apply new animation without restoring the transformation.
@@ -344,7 +349,7 @@
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
final WindowToken windowToken = mTargetWindowTokens.keyAt(i);
final Operation op = mTargetWindowTokens.valueAt(i);
- if (op.mAction == Operation.ACTION_FADE) {
+ if (op.mAction == Operation.ACTION_FADE || op.mAction == Operation.ACTION_TOGGLE_IME) {
fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_TOKEN_TRANSFORM);
op.mLeash = windowToken.getAnimationLeash();
if (DEBUG) Slog.d(TAG, "Start fade-out " + windowToken.getTopChild());
@@ -374,17 +379,19 @@
WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION);
}
- /** Hides the window immediately until it is drawn in new rotation. */
- void hideImmediately(WindowToken windowToken) {
- if (isTargetToken(windowToken)) return;
+ /** Hides the IME window immediately until it is drawn in new rotation. */
+ void hideImeImmediately() {
+ if (mDisplayContent.mInputMethodWindow == null) return;
+ final WindowToken imeWindowToken = mDisplayContent.mInputMethodWindow.mToken;
+ if (isTargetToken(imeWindowToken)) return;
final boolean original = mHideImmediately;
mHideImmediately = true;
- final Operation op = new Operation(Operation.ACTION_FADE);
- mTargetWindowTokens.put(windowToken, op);
- fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_TOKEN_TRANSFORM);
- op.mLeash = windowToken.getAnimationLeash();
+ final Operation op = new Operation(Operation.ACTION_TOGGLE_IME);
+ mTargetWindowTokens.put(imeWindowToken, op);
+ fadeWindowToken(false /* show */, imeWindowToken, ANIMATION_TYPE_TOKEN_TRANSFORM);
+ op.mLeash = imeWindowToken.getAnimationLeash();
mHideImmediately = original;
- if (DEBUG) Slog.d(TAG, "hideImmediately " + windowToken.getTopChild());
+ if (DEBUG) Slog.d(TAG, "hideImeImmediately " + imeWindowToken.getTopChild());
}
/** Returns {@code true} if the window will rotate independently. */
@@ -586,11 +593,13 @@
/** The operation to control the rotation appearance associated with window token. */
private static class Operation {
@Retention(RetentionPolicy.SOURCE)
- @IntDef(value = { ACTION_SEAMLESS, ACTION_FADE })
+ @IntDef(value = { ACTION_SEAMLESS, ACTION_FADE, ACTION_TOGGLE_IME })
@interface Action {}
static final int ACTION_SEAMLESS = 1;
static final int ACTION_FADE = 2;
+ /** The action to toggle the IME window appearance */
+ static final int ACTION_TOGGLE_IME = 3;
final @Action int mAction;
/** The leash of window token. It can be animation leash or the token itself. */
SurfaceControl mLeash;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8bca106..57812c1 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1896,7 +1896,12 @@
case SOFT_INPUT_STATE_HIDDEN:
return false;
}
- return r.mLastImeShown;
+ final boolean useIme = r.getWindow(
+ w -> WindowManager.LayoutParams.mayUseInputMethod(w.mAttrs.flags)) != null;
+ if (!useIme) {
+ return false;
+ }
+ return r.mLastImeShown || (r.mStartingData != null && r.mStartingData.hasImeSurface());
}
/** Returns {@code true} if the top activity is transformed with the new rotation of display. */
@@ -4219,7 +4224,7 @@
// Hide the window until the rotation is done to avoid intermediate artifacts if the
// parent surface of IME container is changed.
if (mAsyncRotationController != null) {
- mAsyncRotationController.hideImmediately(mInputMethodWindow.mToken);
+ mAsyncRotationController.hideImeImmediately();
}
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index ce43628..be52e5a 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1095,11 +1095,9 @@
} else {
overrideProviders = null;
}
- final @InsetsType int type = provider.getType();
- final int id = InsetsSource.createId(
- provider.getOwner(), provider.getIndex(), type);
- mDisplayContent.getInsetsStateController().getOrCreateSourceProvider(id, type)
- .setWindowContainer(win, frameProvider, overrideProviders);
+ mDisplayContent.getInsetsStateController().getOrCreateSourceProvider(
+ provider.getId(), provider.getType()).setWindowContainer(
+ win, frameProvider, overrideProviders);
mInsetsSourceWindowsExceptIme.add(win);
}
}
diff --git a/services/core/java/com/android/server/wm/FadeAnimationController.java b/services/core/java/com/android/server/wm/FadeAnimationController.java
index 561a070..7af67e6 100644
--- a/services/core/java/com/android/server/wm/FadeAnimationController.java
+++ b/services/core/java/com/android/server/wm/FadeAnimationController.java
@@ -57,14 +57,21 @@
return AnimationUtils.loadAnimation(mContext, R.anim.fade_out);
}
+ /** Run the fade in/out animation for a window token. */
+ public void fadeWindowToken(boolean show, WindowToken windowToken, int animationType) {
+ fadeWindowToken(show, windowToken, animationType, null);
+ }
+
/**
* Run the fade in/out animation for a window token.
*
* @param show true for fade-in, otherwise for fade-out.
* @param windowToken the window token to run the animation.
* @param animationType the animation type defined in SurfaceAnimator.
+ * @param finishedCallback the callback after the animation finished.
*/
- public void fadeWindowToken(boolean show, WindowToken windowToken, int animationType) {
+ public void fadeWindowToken(boolean show, WindowToken windowToken, int animationType,
+ SurfaceAnimator.OnAnimationFinishedCallback finishedCallback) {
if (windowToken == null || windowToken.getParent() == null) {
return;
}
@@ -75,9 +82,8 @@
if (animationAdapter == null) {
return;
}
-
windowToken.startAnimation(windowToken.getPendingTransaction(), animationAdapter,
- show /* hidden */, animationType, null /* finishedCallback */);
+ show /* hidden */, animationType, finishedCallback);
}
protected FadeAnimationAdapter createAdapter(LocalAnimationAdapter.AnimationSpec animationSpec,
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index b4dffdc..ff2985c 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -145,18 +145,44 @@
}
boolean changed = super.updateClientVisibility(caller);
if (changed && caller.isRequestedVisible(mSource.getType())) {
- reportImeDrawnForOrganizer(caller);
+ reportImeDrawnForOrganizerIfNeeded(caller);
}
changed |= mDisplayContent.onImeInsetsClientVisibilityUpdate();
return changed;
}
- private void reportImeDrawnForOrganizer(InsetsControlTarget caller) {
- if (caller.getWindow() != null && caller.getWindow().getTask() != null) {
- if (caller.getWindow().getTask().isOrganized()) {
- mWindowContainer.mWmService.mAtmService.mTaskOrganizerController
- .reportImeDrawnOnTask(caller.getWindow().getTask());
- }
+ private void reportImeDrawnForOrganizerIfNeeded(@NonNull InsetsControlTarget caller) {
+ final WindowState callerWindow = caller.getWindow();
+ if (callerWindow == null) {
+ return;
+ }
+ WindowToken imeToken = mWindowContainer.asWindowState() != null
+ ? mWindowContainer.asWindowState().mToken : null;
+ if (mDisplayContent.getAsyncRotationController() != null
+ && mDisplayContent.getAsyncRotationController().isTargetToken(imeToken)) {
+ // Skip reporting IME drawn state when the control target is in fixed
+ // rotation, AsyncRotationController will report after the animation finished.
+ return;
+ }
+ reportImeDrawnForOrganizer(caller);
+ }
+
+ private void reportImeDrawnForOrganizer(@NonNull InsetsControlTarget caller) {
+ final WindowState callerWindow = caller.getWindow();
+ if (callerWindow == null || callerWindow.getTask() == null) {
+ return;
+ }
+ if (callerWindow.getTask().isOrganized()) {
+ mWindowContainer.mWmService.mAtmService.mTaskOrganizerController
+ .reportImeDrawnOnTask(caller.getWindow().getTask());
+ }
+ }
+
+ /** Report the IME has drawn on the current IME control target for its task organizer */
+ void reportImeDrawnForOrganizer() {
+ final InsetsControlTarget imeControlTarget = getControlTarget();
+ if (imeControlTarget != null) {
+ reportImeDrawnForOrganizer(imeControlTarget);
}
}
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index fe13b87..ddf96c5 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -311,16 +311,13 @@
state.removeSource(ID_IME);
} else if (attrs.providedInsets != null) {
for (InsetsFrameProvider provider : attrs.providedInsets) {
- final int id = InsetsSource.createId(
- provider.getOwner(), provider.getIndex(), provider.getType());
- final @InsetsType int type = provider.getType();
- if ((type & WindowInsets.Type.systemBars()) == 0) {
+ if ((provider.getType() & WindowInsets.Type.systemBars()) == 0) {
continue;
}
if (state == originalState) {
state = new InsetsState(state);
}
- state.removeSource(id);
+ state.removeSource(provider.getId());
}
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 08a6358..5f6d660 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -269,6 +269,8 @@
TRANSIT_TO_BACK, transitFlags, null /* trigger */, dc);
updateKeyguardSleepToken();
+ // Make the home wallpaper visible
+ dc.mWallpaperController.showHomeWallpaperInTransition();
// Some stack visibility might change (e.g. docked stack)
mRootWindowContainer.resumeFocusedTasksTopActivities();
mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
diff --git a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
index 2edb082..7852249 100644
--- a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
+++ b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
@@ -20,41 +20,73 @@
import static com.android.internal.R.bool.config_unfoldTransitionEnabled;
import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
+import static com.android.server.wm.DeviceStateController.DeviceState.FOLDED;
+import static com.android.server.wm.DeviceStateController.DeviceState.HALF_FOLDED;
+import static com.android.server.wm.DeviceStateController.DeviceState.OPEN;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
import android.graphics.Rect;
import android.window.DisplayAreaInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.DeviceStateController.DeviceState;
+
public class PhysicalDisplaySwitchTransitionLauncher {
private final DisplayContent mDisplayContent;
- private final WindowManagerService mService;
+ private final ActivityTaskManagerService mAtmService;
+ private final Context mContext;
private final TransitionController mTransitionController;
/**
- * If on a foldable device represents whether the device is folded or not
+ * If on a foldable device represents whether we need to show unfold animation when receiving
+ * a physical display switch event
*/
- private boolean mIsFolded;
+ private boolean mShouldRequestTransitionOnDisplaySwitch = false;
+ /**
+ * Current device state from {@link android.hardware.devicestate.DeviceStateManager}
+ */
+ private DeviceState mDeviceState = DeviceState.UNKNOWN;
private Transition mTransition;
public PhysicalDisplaySwitchTransitionLauncher(DisplayContent displayContent,
TransitionController transitionController) {
+ this(displayContent, displayContent.mWmService.mAtmService,
+ displayContent.mWmService.mContext, transitionController);
+ }
+
+ @VisibleForTesting
+ public PhysicalDisplaySwitchTransitionLauncher(DisplayContent displayContent,
+ ActivityTaskManagerService service, Context context,
+ TransitionController transitionController) {
mDisplayContent = displayContent;
- mService = displayContent.mWmService;
+ mAtmService = service;
+ mContext = context;
mTransitionController = transitionController;
}
/**
* Called by the DeviceStateManager callback when the state changes.
*/
- void foldStateChanged(DeviceStateController.DeviceState newDeviceState) {
- // Ignore transitions to/from half-folded.
- if (newDeviceState == DeviceStateController.DeviceState.HALF_FOLDED) return;
- mIsFolded = newDeviceState == DeviceStateController.DeviceState.FOLDED;
+ void foldStateChanged(DeviceState newDeviceState) {
+ boolean isUnfolding = mDeviceState == FOLDED
+ && (newDeviceState == HALF_FOLDED || newDeviceState == OPEN);
+
+ if (isUnfolding) {
+ // Request transition only if we are unfolding the device
+ mShouldRequestTransitionOnDisplaySwitch = true;
+ } else if (newDeviceState != HALF_FOLDED && newDeviceState != OPEN) {
+ // Cancel the transition request in case if we are folding or switching to back
+ // to the rear display before the displays got switched
+ mShouldRequestTransitionOnDisplaySwitch = false;
+ }
+
+ mDeviceState = newDeviceState;
}
/**
@@ -62,12 +94,12 @@
*/
public void requestDisplaySwitchTransitionIfNeeded(int displayId, int oldDisplayWidth,
int oldDisplayHeight, int newDisplayWidth, int newDisplayHeight) {
+ if (!mShouldRequestTransitionOnDisplaySwitch) return;
if (!mTransitionController.isShellTransitionsEnabled()) return;
if (!mDisplayContent.getLastHasContent()) return;
- boolean shouldRequestUnfoldTransition = !mIsFolded
- && mService.mContext.getResources().getBoolean(config_unfoldTransitionEnabled)
- && ValueAnimator.areAnimatorsEnabled();
+ boolean shouldRequestUnfoldTransition = mContext.getResources()
+ .getBoolean(config_unfoldTransitionEnabled) && ValueAnimator.areAnimatorsEnabled();
if (!shouldRequestUnfoldTransition) {
return;
@@ -91,6 +123,8 @@
mDisplayContent.mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
mTransition = t;
}
+
+ mShouldRequestTransitionOnDisplaySwitch = false;
}
/**
@@ -118,7 +152,7 @@
if (mTransition == null) return;
if (transaction != null) {
- mService.mAtmService.mWindowOrganizerController.applyTransaction(transaction);
+ mAtmService.mWindowOrganizerController.applyTransaction(transaction);
}
markTransitionAsReady();
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index f5079d3..e47787e 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_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 @@
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 3f4296a..d3edeae 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 @@
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/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index b5df3e0..bbb8563 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -193,6 +193,7 @@
.setSourceCrop(new Rect(0, 0, width, height))
.setAllowProtected(true)
.setCaptureSecureLayers(true)
+ .setHintForSeamlessTransition(true)
.build();
screenshotBuffer = ScreenCapture.captureDisplay(captureArgs);
} else {
@@ -202,6 +203,7 @@
.setCaptureSecureLayers(true)
.setAllowProtected(true)
.setSourceCrop(new Rect(0, 0, width, height))
+ .setHintForSeamlessTransition(true)
.build();
screenshotBuffer = ScreenCapture.captureLayers(captureArgs);
}
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index 49d064f..9324e29 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 @@
};
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 @@
* 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/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 5626aa7..cdb4ad6 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -18,6 +18,9 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.TaskInfo.cameraCompatControlStateToString;
+import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NONE;
+import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NORMAL;
+import static android.window.StartingWindowRemovalInfo.DEFER_MODE_ROTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
@@ -686,8 +689,19 @@
final boolean playShiftUpAnimation = !task.inMultiWindowMode();
final ActivityRecord topActivity = task.topActivityContainsStartingWindow();
if (topActivity != null) {
- removalInfo.deferRemoveForIme = topActivity.mDisplayContent
- .mayImeShowOnLaunchingActivity(topActivity);
+ // Set defer remove mode for IME
+ final DisplayContent dc = topActivity.getDisplayContent();
+ final WindowState imeWindow = dc.mInputMethodWindow;
+ if (topActivity.isVisibleRequested() && imeWindow != null
+ && dc.mayImeShowOnLaunchingActivity(topActivity)
+ && dc.isFixedRotationLaunchingApp(topActivity)) {
+ removalInfo.deferRemoveForImeMode = DEFER_MODE_ROTATION;
+ } else if (dc.mayImeShowOnLaunchingActivity(topActivity)) {
+ removalInfo.deferRemoveForImeMode = DEFER_MODE_NORMAL;
+ } else {
+ removalInfo.deferRemoveForImeMode = DEFER_MODE_NONE;
+ }
+
final WindowState mainWindow =
topActivity.findMainWindow(false/* includeStartingApp */);
// No app window for this activity, app might be crashed.
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index d531ad1..50bf38b 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_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.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.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 @@
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 @@
/** 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 @@
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 @@
return false;
}
+ boolean hasTransientLaunch() {
+ return mTransientLaunches != null && !mTransientLaunches.isEmpty();
+ }
+
boolean isTransientLaunch(@NonNull ActivityRecord activity) {
return mTransientLaunches != null && mTransientLaunches.containsKey(activity);
}
@@ -579,9 +607,10 @@
recordDisplay(wc.getDisplayContent());
if (info.mShowWallpaper) {
// Collect the wallpaper token (for isWallpaper(wc)) so it is part of the sync set.
- final WindowState wallpaper =
- wc.getDisplayContent().mWallpaperController.getTopVisibleWallpaper();
- if (wallpaper != null) {
+ final List<WindowState> wallpapers =
+ wc.getDisplayContent().mWallpaperController.getAllTopWallpapers();
+ for (int i = wallpapers.size() - 1; i >= 0; i--) {
+ WindowState wallpaper = wallpapers.get(i);
collect(wallpaper.mToken);
}
}
@@ -926,8 +955,15 @@
// to the transient activity.
ar.supportsEnterPipOnTaskSwitch = true;
}
+ // Make sure this activity can enter pip under the current circumstances.
+ // `enterPictureInPicture` internally checks, but with beforeStopping=false which
+ // is specifically for non-auto-enter.
+ if (!ar.checkEnterPictureInPictureState("enterPictureInPictureMode",
+ true /* beforeStopping */)) {
+ return false;
+ }
return mController.mAtm.enterPictureInPictureMode(ar, ar.pictureInPictureArgs,
- false /* fromClient */);
+ false /* fromClient */, true /* isAutoEnter */);
}
// Legacy pip-entry (not via isAutoEnterEnabled).
@@ -984,13 +1020,20 @@
// 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);
});
}
@@ -1282,6 +1325,7 @@
// ActivityRecord#canShowWindows() may reject to show its window. The visibility also
// needs to be updated for STATE_ABORT.
commitVisibleActivities(transaction);
+ commitVisibleWallpapers();
// Fall-back to the default display if there isn't one participating.
final DisplayContent primaryDisplay = !mTargetDisplays.isEmpty() ? mTargetDisplays.get(0)
@@ -1314,6 +1358,7 @@
}
// Resolve the animating targets from the participants.
mTargets = calculateTargets(mParticipants, mChanges);
+
// Check whether the participants were animated from back navigation.
mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets);
final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
@@ -1590,6 +1635,30 @@
}
}
+ /**
+ * Reset waitingToshow for all wallpapers, and commit the visibility of the visible ones
+ */
+ private void commitVisibleWallpapers() {
+ boolean showWallpaper = shouldWallpaperBeVisible();
+ for (int i = mParticipants.size() - 1; i >= 0; --i) {
+ final WallpaperWindowToken wallpaper = mParticipants.valueAt(i).asWallpaperToken();
+ if (wallpaper != null) {
+ wallpaper.waitingToShow = false;
+ if (!wallpaper.isVisible() && wallpaper.isVisibleRequested()) {
+ wallpaper.commitVisibility(showWallpaper);
+ }
+ }
+ }
+ }
+
+ private boolean shouldWallpaperBeVisible() {
+ for (int i = mParticipants.size() - 1; i >= 0; --i) {
+ WindowContainer participant = mParticipants.valueAt(i);
+ if (participant.showWallpaper()) return true;
+ }
+ return false;
+ }
+
// TODO(b/188595497): Remove after migrating to shell.
/** @see RecentsAnimationController#attachNavigationBarToApp */
private void handleLegacyRecentsStartBehavior(DisplayContent dc, TransitionInfo info) {
@@ -2198,7 +2267,7 @@
// Display won't be rotated for multi window Task, so the fixed rotation
// won't be applied. This can happen when the windowing mode is changed
// before the previous fixed rotation is applied.
- && !task.inMultiWindowMode()) {
+ && (!task.inMultiWindowMode() || !topRunningActivity.inMultiWindowMode())) {
// If Activity is in fixed rotation, its will be applied with the next rotation,
// when the Task is still in the previous rotation.
final int taskRotation = task.getWindowConfiguration().getDisplayRotation();
@@ -2964,11 +3033,14 @@
Rect cropBounds = new Rect(bounds);
cropBounds.offsetTo(0, 0);
+ final boolean isDisplayRotation = wc.asDisplayContent() != null
+ && wc.asDisplayContent().isRotationChanging();
ScreenCapture.LayerCaptureArgs captureArgs =
new ScreenCapture.LayerCaptureArgs.Builder(wc.getSurfaceControl())
.setSourceCrop(cropBounds)
.setCaptureSecureLayers(true)
.setAllowProtected(true)
+ .setHintForSeamlessTransition(isDisplayRotation)
.build();
ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
ScreenCapture.captureLayers(captureArgs);
@@ -2979,8 +3051,6 @@
Slog.w(TAG, "Failed to capture screenshot for " + wc);
return false;
}
- final boolean isDisplayRotation = wc.asDisplayContent() != null
- && wc.asDisplayContent().isRotationChanging();
// Some tests may check the name "RotationLayer" to detect display rotation.
final String name = isDisplayRotation ? "RotationLayer" : "transition snapshot: " + wc;
SurfaceControl snapshotSurface = wc.makeAnimationLeash()
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 7950eda..c9316bf 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 @@
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.
*/
@@ -806,6 +813,10 @@
}
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record);
mPlayingTransitions.remove(record);
+ if (!inTransition()) {
+ // reset track-count now since shell-side is idle.
+ mTrackCount = 0;
+ }
updateRunningRemoteAnimation(record, false /* isPlaying */);
record.finishTransition();
for (int i = mAnimatingExitWindows.size() - 1; i >= 0; i--) {
@@ -818,10 +829,9 @@
}
}
mRunningLock.doNotifyLocked();
- // Run state-validation checks when no transitions are active anymore.
+ // Run state-validation checks when no transitions are active anymore (Note: sometimes
+ // finish can start a transition, so check afterwards -- eg. pip).
if (!inTransition()) {
- // Can reset track-count now that everything is idle.
- mTrackCount = 0;
validateStates();
mAtm.mWindowManager.onAnimationFinished();
}
@@ -848,6 +858,15 @@
}
}
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();
}
/**
@@ -858,7 +877,7 @@
tryStartCollectFromQueue();
}
- private boolean canStartCollectingNow(Transition queued) {
+ private boolean canStartCollectingNow(@Nullable Transition queued) {
if (mCollectingTransition == null) return true;
// Population (collect until ready) is still serialized, so always wait for that.
if (!mCollectingTransition.isPopulated()) return false;
@@ -879,6 +898,8 @@
// 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()) {
@@ -929,11 +950,38 @@
* `collecting` transition. It may still ultimately block in sync-engine or become dependent
* in {@link #getIsIndependent} later.
*/
- boolean getCanBeIndependent(Transition collecting, Transition queued) {
- if (queued.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL
+ boolean getCanBeIndependent(Transition collecting, @Nullable Transition queued) {
+ // For tests
+ if (queued != null && queued.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL
&& collecting.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL) {
return true;
}
+ // For recents
+ if (queued != null && 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 +990,47 @@
* `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 +1054,15 @@
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 +1235,8 @@
// 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);
@@ -1162,6 +1254,44 @@
return true;
}
+ /**
+ * This will create and start collecting for a transition if possible. If there's no way to
+ * start collecting for `parallelType` now, then this returns null.
+ *
+ * WARNING: ONLY use this if the transition absolutely cannot be deferred!
+ */
+ @NonNull
+ Transition createAndStartCollecting(int type) {
+ if (mTransitionPlayer == null) {
+ return null;
+ }
+ if (!mQueuedTransitions.isEmpty()) {
+ // There is a queue, so it's not possible to start immediately
+ return null;
+ }
+ if (mSyncEngine.hasActiveSync()) {
+ if (isCollecting()) {
+ // Check if we can run in parallel here.
+ if (canStartCollectingNow(null /* transit */)) {
+ // create and collect in parallel.
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Moving #%d from"
+ + " collecting to waiting.", mCollectingTransition.getSyncId());
+ mWaitingTransitions.add(mCollectingTransition);
+ mCollectingTransition = null;
+ Transition transit = new Transition(type, 0 /* flags */, this, mSyncEngine);
+ moveToCollecting(transit);
+ return transit;
+ }
+ } else {
+ Slog.w(TAG, "Ongoing Sync outside of transition.");
+ }
+ return null;
+ }
+ Transition transit = new Transition(type, 0 /* flags */, this, mSyncEngine);
+ moveToCollecting(transit);
+ return transit;
+ }
+
/** Returns {@code true} if it started collecting, {@code false} if it was queued. */
boolean startLegacySyncOrQueue(BLASTSyncEngine.SyncGroup syncGroup, Runnable applySync) {
if (!mQueuedTransitions.isEmpty() || mSyncEngine.hasActiveSync()) {
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 7ceac4f..edafe06 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -124,24 +124,19 @@
final boolean mIsLockscreenLiveWallpaperEnabled;
- private final ToBooleanFunction<WindowState> mFindWallpaperTargetFunction = w -> {
- if ((w.mAttrs.type == TYPE_WALLPAPER)) {
- if (mFindResults.topWallpaper == null || mFindResults.resetTopWallpaper) {
- WallpaperWindowToken token = w.mToken.asWallpaperToken();
- if (token == null) {
- Slog.w(TAG, "Window " + w + " has wallpaper type but not wallpaper token");
- return false;
- }
- if (!token.canShowWhenLocked() && mDisplayContent.isKeyguardLocked()) {
- return false;
- }
- mFindResults.setTopWallpaper(w);
- mFindResults.resetTopWallpaper = false;
+ private final Consumer<WindowState> mFindWallpapers = w -> {
+ if (w.mAttrs.type == TYPE_WALLPAPER) {
+ WallpaperWindowToken token = w.mToken.asWallpaperToken();
+ if (token.canShowWhenLocked() && !mFindResults.hasTopShowWhenLockedWallpaper()) {
+ mFindResults.setTopShowWhenLockedWallpaper(w);
+ } else if (!token.canShowWhenLocked()
+ && !mFindResults.hasTopHideWhenLockedWallpaper()) {
+ mFindResults.setTopHideWhenLockedWallpaper(w);
}
- return false;
}
+ };
- mFindResults.resetTopWallpaper = true;
+ private final ToBooleanFunction<WindowState> mFindWallpaperTargetFunction = w -> {
if (!w.mTransitionController.isShellTransitionsEnabled()) {
if (w.mActivityRecord != null && !w.mActivityRecord.isVisible()
&& !w.mActivityRecord.isAnimating(TRANSITION | PARENTS)) {
@@ -344,6 +339,31 @@
}
}
+ /**
+ * Change the visibility if wallpaper is home screen only.
+ * This is called during the keyguard unlocking transition
+ * (see {@link KeyguardController#keyguardGoingAway(int, int)}) and thus assumes that if the
+ * system wallpaper is shared with lock, then it needs no animation.
+ */
+ public void showHomeWallpaperInTransition() {
+ updateWallpaperWindowsTarget(mFindResults);
+
+ if (!mFindResults.hasTopShowWhenLockedWallpaper()) {
+ Slog.w(TAG, "There is no wallpaper for the lock screen");
+ return;
+ }
+ WindowState hideWhenLocked = mFindResults.mTopWallpaper.mTopHideWhenLockedWallpaper;
+ WindowState showWhenLocked = mFindResults.mTopWallpaper.mTopShowWhenLockedWallpaper;
+ if (!mFindResults.hasTopHideWhenLockedWallpaper()) {
+ // Shared wallpaper, ensure its visibility
+ showWhenLocked.mToken.asWallpaperToken().updateWallpaperWindows(true);
+ } else {
+ // Separate lock and home wallpapers: show home wallpaper and hide lock
+ hideWhenLocked.mToken.asWallpaperToken().updateWallpaperWindowsInTransition(true);
+ showWhenLocked.mToken.asWallpaperToken().updateWallpaperWindowsInTransition(false);
+ }
+ }
+
void hideDeferredWallpapersIfNeededLegacy() {
for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
final WallpaperWindowToken token = mWallpaperTokens.get(i);
@@ -668,13 +688,26 @@
mFindResults.setUseTopWallpaperAsTarget(true);
}
+ mDisplayContent.forAllWindows(mFindWallpapers, true /* traverseTopToBottom */);
mDisplayContent.forAllWindows(mFindWallpaperTargetFunction, true /* traverseTopToBottom */);
if (mFindResults.wallpaperTarget == null && mFindResults.useTopWallpaperAsTarget) {
- mFindResults.setWallpaperTarget(mFindResults.topWallpaper);
+ mFindResults.setWallpaperTarget(
+ mFindResults.getTopWallpaper(mDisplayContent.isKeyguardLocked()));
}
}
+ List<WindowState> getAllTopWallpapers() {
+ ArrayList<WindowState> wallpapers = new ArrayList<>(2);
+ if (mFindResults.hasTopShowWhenLockedWallpaper()) {
+ wallpapers.add(mFindResults.mTopWallpaper.mTopShowWhenLockedWallpaper);
+ }
+ if (mFindResults.hasTopHideWhenLockedWallpaper()) {
+ wallpapers.add(mFindResults.mTopWallpaper.mTopHideWhenLockedWallpaper);
+ }
+ return wallpapers;
+ }
+
private boolean isFullscreen(WindowManager.LayoutParams attrs) {
return attrs.x == 0 && attrs.y == 0
&& attrs.width == MATCH_PARENT && attrs.height == MATCH_PARENT;
@@ -760,10 +793,16 @@
result.setWallpaperTarget(wallpaperTarget);
}
+ /**
+ * Change the visibility of the top wallpaper to {@param visibility} and hide all the others.
+ */
private void updateWallpaperTokens(boolean visibility, boolean locked) {
+ WindowState topWallpaper = mFindResults.getTopWallpaper(locked);
+ WallpaperWindowToken topWallpaperToken =
+ topWallpaper == null ? null : topWallpaper.mToken.asWallpaperToken();
for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
- token.updateWallpaperWindows(visibility && (!locked || token.canShowWhenLocked()));
+ token.updateWallpaperWindows(visibility && (token == topWallpaperToken));
}
}
@@ -801,8 +840,10 @@
}
}
- // Keep both wallpapers visible unless the keyguard is locked (then hide private wp)
- updateWallpaperTokens(visible, mDisplayContent.isKeyguardLocked());
+ if (!mDisplayContent.isKeyguardGoingAway() || !mIsLockscreenLiveWallpaperEnabled) {
+ // When keyguard goes away, KeyguardController handles the visibility
+ updateWallpaperTokens(visible, mDisplayContent.isKeyguardLocked());
+ }
if (DEBUG_WALLPAPER) {
Slog.v(TAG, "adjustWallpaperWindows: wallpaper visibility " + visible
@@ -1019,14 +1060,52 @@
/** Helper class for storing the results of a wallpaper target find operation. */
final private static class FindWallpaperTargetResult {
- WindowState topWallpaper = null;
+
+ static final class TopWallpaper {
+ // A wp that can be visible on home screen only
+ WindowState mTopHideWhenLockedWallpaper = null;
+ // A wallpaper that has permission to be visible on lock screen (lock or shared wp)
+ WindowState mTopShowWhenLockedWallpaper = null;
+
+ void reset() {
+ mTopHideWhenLockedWallpaper = null;
+ mTopShowWhenLockedWallpaper = null;
+ }
+ }
+
+ TopWallpaper mTopWallpaper = new TopWallpaper();
boolean useTopWallpaperAsTarget = false;
WindowState wallpaperTarget = null;
- boolean resetTopWallpaper = false;
boolean isWallpaperTargetForLetterbox = false;
- void setTopWallpaper(WindowState win) {
- topWallpaper = win;
+ void setTopHideWhenLockedWallpaper(WindowState win) {
+ if (DEBUG_WALLPAPER) {
+ Slog.v(TAG, "setTopHideWhenLockedWallpaper " + win);
+ }
+ mTopWallpaper.mTopHideWhenLockedWallpaper = win;
+ }
+
+ void setTopShowWhenLockedWallpaper(WindowState win) {
+ if (DEBUG_WALLPAPER) {
+ Slog.v(TAG, "setTopShowWhenLockedWallpaper " + win);
+ }
+ mTopWallpaper.mTopShowWhenLockedWallpaper = win;
+ }
+
+ boolean hasTopHideWhenLockedWallpaper() {
+ return mTopWallpaper.mTopHideWhenLockedWallpaper != null;
+ }
+
+ boolean hasTopShowWhenLockedWallpaper() {
+ return mTopWallpaper.mTopShowWhenLockedWallpaper != null;
+ }
+
+ WindowState getTopWallpaper(boolean isKeyguardLocked) {
+ if (!isKeyguardLocked && hasTopHideWhenLockedWallpaper()) {
+ return mTopWallpaper.mTopHideWhenLockedWallpaper;
+ } else {
+ return mTopWallpaper.mTopShowWhenLockedWallpaper;
+ }
}
void setWallpaperTarget(WindowState win) {
@@ -1042,10 +1121,9 @@
}
void reset() {
- topWallpaper = null;
+ mTopWallpaper.reset();
wallpaperTarget = null;
useTopWallpaperAsTarget = false;
- resetTopWallpaper = false;
isWallpaperTargetForLetterbox = false;
}
}
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 1ffee05..5ea8f65 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -128,6 +128,20 @@
}
}
+ /**
+ * Update the visibility of the token to {@param visible}. If a transition will collect the
+ * wallpaper, then the visibility will be committed during the execution of the transition.
+ *
+ * waitingToShow is reset at the beginning of the transition:
+ * {@link Transition#onTransactionReady(int, SurfaceControl.Transaction)}
+ */
+ void updateWallpaperWindowsInTransition(boolean visible) {
+ if (mTransitionController.isCollecting() && mVisibleRequested != visible) {
+ waitingToShow = true;
+ }
+ updateWallpaperWindows(visible);
+ }
+
void updateWallpaperWindows(boolean visible) {
if (mVisibleRequested != visible) {
ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper token %s visible=%b",
@@ -199,11 +213,11 @@
}
/**
- * Commits the visibility of this token. This will directly update the visibility without
- * regard for other state (like being in a transition).
+ * Commits the visibility of this token. This will directly update the visibility unless the
+ * wallpaper is in a transition.
*/
void commitVisibility(boolean visible) {
- if (visible == isVisible()) return;
+ if (visible == isVisible() || waitingToShow) return;
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"commitVisibility: %s: visible=%b mVisibleRequested=%b", this,
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index a5cdd0b..3ccf183 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -435,8 +435,7 @@
if (mLocalInsetsSources == null) {
mLocalInsetsSources = new SparseArray<>();
}
- final int id = InsetsSource.createId(
- provider.getOwner(), provider.getIndex(), provider.getType());
+ final int id = provider.getId();
if (mLocalInsetsSources.get(id) != null) {
if (DEBUG) {
Slog.d(TAG, "The local insets source for this " + provider
@@ -457,8 +456,7 @@
return;
}
- final int id = InsetsSource.createId(
- provider.getOwner(), provider.getIndex(), provider.getType());
+ final int id = provider.getId();
if (mLocalInsetsSources.get(id) == null) {
if (DEBUG) {
Slog.d(TAG, "Given " + provider + " doesn't have a local insets source.");
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f9b6fc1..40c6c46 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3915,8 +3915,9 @@
synchronized (mGlobalLock) {
final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent == null) {
- throw new IllegalStateException("No touch mode is defined for displayId {"
- + displayId + "}");
+ throw new IllegalStateException("Failed to retrieve the touch mode state for"
+ + "display {" + displayId + "}: display is not registered in "
+ + "WindowRootContainer");
}
return displayContent.isInTouchMode();
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index cd42528..09312ba 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 @@
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();
@@ -971,19 +972,30 @@
switch (type) {
case HIERARCHY_OP_TYPE_PENDING_INTENT: {
+ final Bundle launchOpts = hop.getLaunchOptions();
+ ActivityOptions activityOptions = launchOpts != null
+ ? new ActivityOptions(launchOpts) : null;
+ if (activityOptions != null && activityOptions.getTransientLaunch()
+ && mService.isCallerRecents(hop.getPendingIntent().getCreatorUid())) {
+ if (mService.getActivityStartController().startExistingRecentsIfPossible(
+ hop.getActivityIntent(), activityOptions)) {
+ // Start recents successfully.
+ break;
+ }
+ }
+
String resolvedType = hop.getActivityIntent() != null
? hop.getActivityIntent().resolveTypeIfNeeded(
mService.mContext.getContentResolver())
: null;
- ActivityOptions activityOptions = null;
if (hop.getPendingIntent().isActivity()) {
// Set the context display id as preferred for this activity launches, so that
// it can land on caller's display. Or just brought the task to front at the
// display where it was on since it has higher preference.
- activityOptions = hop.getLaunchOptions() != null
- ? new ActivityOptions(hop.getLaunchOptions())
- : ActivityOptions.makeBasic();
+ if (activityOptions == null) {
+ activityOptions = ActivityOptions.makeBasic();
+ }
activityOptions.setCallerDisplayId(DEFAULT_DISPLAY);
}
final Bundle options = activityOptions != null ? activityOptions.toBundle() : null;
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 4af685e..0488247 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -561,6 +561,14 @@
return env->NewStringUTF(path.c_str());
}
+static jboolean com_android_server_am_CachedAppOptimizer_isFreezerProfileValid(JNIEnv* env) {
+ int uid = getuid();
+ int pid = getpid();
+
+ return isProfileValidForProcess("Frozen", uid, pid) &&
+ isProfileValidForProcess("Unfrozen", uid, pid);
+}
+
static const JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"cancelCompaction", "()V",
@@ -578,7 +586,9 @@
{"getBinderFreezeInfo", "(I)I",
(void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo},
{"getFreezerCheckPath", "()Ljava/lang/String;",
- (void*)com_android_server_am_CachedAppOptimizer_getFreezerCheckPath}};
+ (void*)com_android_server_am_CachedAppOptimizer_getFreezerCheckPath},
+ {"isFreezerProfileValid", "()Z",
+ (void*)com_android_server_am_CachedAppOptimizer_isFreezerProfileValid}};
int register_android_server_am_CachedAppOptimizer(JNIEnv* env)
{
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 369c974..f0d718a 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -342,15 +342,15 @@
void notifySensorAccuracy(int32_t deviceId, InputDeviceSensorType sensorType,
InputDeviceSensorAccuracy accuracy) override;
void notifyVibratorState(int32_t deviceId, bool isOn) override;
- bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) override;
- void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) override;
- void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) override;
- void interceptMotionBeforeQueueing(const int32_t displayId, nsecs_t when,
+ bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override;
+ InputDispatcherConfiguration getDispatcherConfiguration() override;
+ void interceptKeyBeforeQueueing(const KeyEvent& keyEvent, uint32_t& policyFlags) override;
+ void interceptMotionBeforeQueueing(int32_t displayId, nsecs_t when,
uint32_t& policyFlags) override;
- nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token, const KeyEvent* keyEvent,
+ nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token, const KeyEvent& keyEvent,
uint32_t policyFlags) override;
- bool dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent* keyEvent,
- uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) override;
+ std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent& keyEvent,
+ uint32_t policyFlags) override;
void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override;
void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override;
void setPointerCapture(const PointerCaptureRequest& request) override;
@@ -450,7 +450,7 @@
mServiceObj = env->NewGlobalRef(serviceObj);
- InputManager* im = new InputManager(this, this);
+ InputManager* im = new InputManager(this, *this);
mInputManager = im;
defaultServiceManager()->addService(String16("inputflinger"), im);
}
@@ -1007,21 +1007,22 @@
checkAndClearExceptionFromCallback(env, "notifyVibratorState");
}
-void NativeInputManager::getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) {
+InputDispatcherConfiguration NativeInputManager::getDispatcherConfiguration() {
ATRACE_CALL();
+ InputDispatcherConfiguration config;
JNIEnv* env = jniEnv();
- jint keyRepeatTimeout = env->CallIntMethod(mServiceObj,
- gServiceClassInfo.getKeyRepeatTimeout);
+ jint keyRepeatTimeout = env->CallIntMethod(mServiceObj, gServiceClassInfo.getKeyRepeatTimeout);
if (!checkAndClearExceptionFromCallback(env, "getKeyRepeatTimeout")) {
- outConfig->keyRepeatTimeout = milliseconds_to_nanoseconds(keyRepeatTimeout);
+ config.keyRepeatTimeout = milliseconds_to_nanoseconds(keyRepeatTimeout);
}
- jint keyRepeatDelay = env->CallIntMethod(mServiceObj,
- gServiceClassInfo.getKeyRepeatDelay);
+ jint keyRepeatDelay = env->CallIntMethod(mServiceObj, gServiceClassInfo.getKeyRepeatDelay);
if (!checkAndClearExceptionFromCallback(env, "getKeyRepeatDelay")) {
- outConfig->keyRepeatDelay = milliseconds_to_nanoseconds(keyRepeatDelay);
+ config.keyRepeatDelay = milliseconds_to_nanoseconds(keyRepeatDelay);
}
+
+ return config;
}
void NativeInputManager::displayRemoved(JNIEnv* env, int32_t displayId) {
@@ -1294,110 +1295,112 @@
checkAndClearExceptionFromCallback(env, "notifyStylusGestureStarted");
}
-bool NativeInputManager::filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) {
+bool NativeInputManager::filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) {
ATRACE_CALL();
- jobject inputEventObj;
-
JNIEnv* env = jniEnv();
- switch (inputEvent->getType()) {
+
+ ScopedLocalRef<jobject> inputEventObj(env);
+ switch (inputEvent.getType()) {
case InputEventType::KEY:
- inputEventObj =
- android_view_KeyEvent_fromNative(env, static_cast<const KeyEvent*>(inputEvent));
+ inputEventObj.reset(
+ android_view_KeyEvent_fromNative(env,
+ static_cast<const KeyEvent&>(inputEvent)));
break;
case InputEventType::MOTION:
- inputEventObj = android_view_MotionEvent_obtainAsCopy(env,
- static_cast<const MotionEvent&>(
- *inputEvent));
+ inputEventObj.reset(
+ android_view_MotionEvent_obtainAsCopy(env,
+ static_cast<const MotionEvent&>(
+ inputEvent)));
break;
default:
return true; // dispatch the event normally
}
- if (!inputEventObj) {
+ if (!inputEventObj.get()) {
ALOGE("Failed to obtain input event object for filterInputEvent.");
return true; // dispatch the event normally
}
// The callee is responsible for recycling the event.
- jboolean pass = env->CallBooleanMethod(mServiceObj, gServiceClassInfo.filterInputEvent,
- inputEventObj, policyFlags);
+ const jboolean continueEventDispatch =
+ env->CallBooleanMethod(mServiceObj, gServiceClassInfo.filterInputEvent,
+ inputEventObj.get(), policyFlags);
if (checkAndClearExceptionFromCallback(env, "filterInputEvent")) {
- pass = true;
+ return true; // dispatch the event normally
}
- env->DeleteLocalRef(inputEventObj);
- return pass;
+ return continueEventDispatch;
}
-void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent,
- uint32_t& policyFlags) {
+void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent& keyEvent,
+ uint32_t& policyFlags) {
ATRACE_CALL();
// Policy:
// - Ignore untrusted events and pass them along.
// - Ask the window manager what to do with normal events and trusted injected events.
// - For normal events wake and brighten the screen if currently off or dim.
- bool interactive = mInteractive.load();
+ const bool interactive = mInteractive.load();
if (interactive) {
policyFlags |= POLICY_FLAG_INTERACTIVE;
}
- if ((policyFlags & POLICY_FLAG_TRUSTED)) {
- nsecs_t when = keyEvent->getEventTime();
- JNIEnv* env = jniEnv();
- jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
- jint wmActions;
- if (keyEventObj) {
- wmActions = env->CallIntMethod(mServiceObj,
- gServiceClassInfo.interceptKeyBeforeQueueing,
- keyEventObj, policyFlags);
- if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeQueueing")) {
- wmActions = 0;
- }
- android_view_KeyEvent_recycle(env, keyEventObj);
- env->DeleteLocalRef(keyEventObj);
- } else {
- ALOGE("Failed to obtain key event object for interceptKeyBeforeQueueing.");
- wmActions = 0;
- }
- handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
- } else {
+ if ((policyFlags & POLICY_FLAG_TRUSTED) == 0) {
if (interactive) {
policyFlags |= POLICY_FLAG_PASS_TO_USER;
}
+ return;
}
+
+ const nsecs_t when = keyEvent.getEventTime();
+ JNIEnv* env = jniEnv();
+ ScopedLocalRef<jobject> keyEventObj(env, android_view_KeyEvent_fromNative(env, keyEvent));
+ if (!keyEventObj.get()) {
+ ALOGE("Failed to obtain key event object for interceptKeyBeforeQueueing.");
+ return;
+ }
+
+ jint wmActions = env->CallIntMethod(mServiceObj, gServiceClassInfo.interceptKeyBeforeQueueing,
+ keyEventObj.get(), policyFlags);
+ if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeQueueing")) {
+ wmActions = 0;
+ }
+ android_view_KeyEvent_recycle(env, keyEventObj.get());
+ handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
}
-void NativeInputManager::interceptMotionBeforeQueueing(const int32_t displayId, nsecs_t when,
- uint32_t& policyFlags) {
+void NativeInputManager::interceptMotionBeforeQueueing(int32_t displayId, nsecs_t when,
+ uint32_t& policyFlags) {
ATRACE_CALL();
// Policy:
// - Ignore untrusted events and pass them along.
// - No special filtering for injected events required at this time.
// - Filter normal events based on screen state.
// - For normal events brighten (but do not wake) the screen if currently dim.
- bool interactive = mInteractive.load();
+ const bool interactive = mInteractive.load();
if (interactive) {
policyFlags |= POLICY_FLAG_INTERACTIVE;
}
- if ((policyFlags & POLICY_FLAG_TRUSTED) && !(policyFlags & POLICY_FLAG_INJECTED)) {
- if (policyFlags & POLICY_FLAG_INTERACTIVE) {
- policyFlags |= POLICY_FLAG_PASS_TO_USER;
- } else {
- JNIEnv* env = jniEnv();
- jint wmActions = env->CallIntMethod(mServiceObj,
- gServiceClassInfo.interceptMotionBeforeQueueingNonInteractive,
- displayId, when, policyFlags);
- if (checkAndClearExceptionFromCallback(env,
- "interceptMotionBeforeQueueingNonInteractive")) {
- wmActions = 0;
- }
- handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
- }
- } else {
+ if ((policyFlags & POLICY_FLAG_TRUSTED) == 0 || (policyFlags & POLICY_FLAG_INJECTED)) {
if (interactive) {
policyFlags |= POLICY_FLAG_PASS_TO_USER;
}
+ return;
}
+
+ if (policyFlags & POLICY_FLAG_INTERACTIVE) {
+ policyFlags |= POLICY_FLAG_PASS_TO_USER;
+ return;
+ }
+
+ JNIEnv* env = jniEnv();
+ const jint wmActions =
+ env->CallIntMethod(mServiceObj,
+ gServiceClassInfo.interceptMotionBeforeQueueingNonInteractive,
+ displayId, when, policyFlags);
+ if (checkAndClearExceptionFromCallback(env, "interceptMotionBeforeQueueingNonInteractive")) {
+ return;
+ }
+ handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
}
void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when,
@@ -1411,81 +1414,76 @@
}
}
-nsecs_t NativeInputManager::interceptKeyBeforeDispatching(
- const sp<IBinder>& token,
- const KeyEvent* keyEvent, uint32_t policyFlags) {
+nsecs_t NativeInputManager::interceptKeyBeforeDispatching(const sp<IBinder>& token,
+ const KeyEvent& keyEvent,
+ uint32_t policyFlags) {
ATRACE_CALL();
// Policy:
// - Ignore untrusted events and pass them along.
// - Filter normal events and trusted injected events through the window manager policy to
// handle the HOME key and the like.
- nsecs_t result = 0;
- if (policyFlags & POLICY_FLAG_TRUSTED) {
- JNIEnv* env = jniEnv();
- ScopedLocalFrame localFrame(env);
-
- // Token may be null
- jobject tokenObj = javaObjectForIBinder(env, token);
-
- jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
- if (keyEventObj) {
- jlong delayMillis = env->CallLongMethod(mServiceObj,
- gServiceClassInfo.interceptKeyBeforeDispatching,
- tokenObj, keyEventObj, policyFlags);
- bool error = checkAndClearExceptionFromCallback(env, "interceptKeyBeforeDispatching");
- android_view_KeyEvent_recycle(env, keyEventObj);
- env->DeleteLocalRef(keyEventObj);
- if (!error) {
- if (delayMillis < 0) {
- result = -1;
- } else if (delayMillis > 0) {
- result = milliseconds_to_nanoseconds(delayMillis);
- }
- }
- } else {
- ALOGE("Failed to obtain key event object for interceptKeyBeforeDispatching.");
- }
+ if ((policyFlags & POLICY_FLAG_TRUSTED) == 0) {
+ return 0;
}
- return result;
+
+ JNIEnv* env = jniEnv();
+ ScopedLocalFrame localFrame(env);
+
+ // Token may be null
+ ScopedLocalRef<jobject> tokenObj(env, javaObjectForIBinder(env, token));
+ ScopedLocalRef<jobject> keyEventObj(env, android_view_KeyEvent_fromNative(env, keyEvent));
+ if (!keyEventObj.get()) {
+ ALOGE("Failed to obtain key event object for interceptKeyBeforeDispatching.");
+ return 0;
+ }
+
+ const jlong delayMillis =
+ env->CallLongMethod(mServiceObj, gServiceClassInfo.interceptKeyBeforeDispatching,
+ tokenObj.get(), keyEventObj.get(), policyFlags);
+ android_view_KeyEvent_recycle(env, keyEventObj.get());
+ if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeDispatching")) {
+ return 0;
+ }
+ return delayMillis < 0 ? -1 : milliseconds_to_nanoseconds(delayMillis);
}
-bool NativeInputManager::dispatchUnhandledKey(const sp<IBinder>& token,
- const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) {
+std::optional<KeyEvent> NativeInputManager::dispatchUnhandledKey(const sp<IBinder>& token,
+ const KeyEvent& keyEvent,
+ uint32_t policyFlags) {
ATRACE_CALL();
// Policy:
// - Ignore untrusted events and do not perform default handling.
- bool result = false;
- if (policyFlags & POLICY_FLAG_TRUSTED) {
- JNIEnv* env = jniEnv();
- ScopedLocalFrame localFrame(env);
-
- // Note: tokenObj may be null.
- jobject tokenObj = javaObjectForIBinder(env, token);
- jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
- if (keyEventObj) {
- jobject fallbackKeyEventObj = env->CallObjectMethod(mServiceObj,
- gServiceClassInfo.dispatchUnhandledKey,
- tokenObj, keyEventObj, policyFlags);
- if (checkAndClearExceptionFromCallback(env, "dispatchUnhandledKey")) {
- fallbackKeyEventObj = nullptr;
- }
- android_view_KeyEvent_recycle(env, keyEventObj);
- env->DeleteLocalRef(keyEventObj);
-
- if (fallbackKeyEventObj) {
- // Note: outFallbackKeyEvent may be the same object as keyEvent.
- if (!android_view_KeyEvent_toNative(env, fallbackKeyEventObj,
- outFallbackKeyEvent)) {
- result = true;
- }
- android_view_KeyEvent_recycle(env, fallbackKeyEventObj);
- env->DeleteLocalRef(fallbackKeyEventObj);
- }
- } else {
- ALOGE("Failed to obtain key event object for dispatchUnhandledKey.");
- }
+ if ((policyFlags & POLICY_FLAG_TRUSTED) == 0) {
+ return {};
}
- return result;
+
+ JNIEnv* env = jniEnv();
+ ScopedLocalFrame localFrame(env);
+
+ // Note: tokenObj may be null.
+ ScopedLocalRef<jobject> tokenObj(env, javaObjectForIBinder(env, token));
+ ScopedLocalRef<jobject> keyEventObj(env, android_view_KeyEvent_fromNative(env, keyEvent));
+ if (!keyEventObj.get()) {
+ ALOGE("Failed to obtain key event object for dispatchUnhandledKey.");
+ return {};
+ }
+
+ ScopedLocalRef<jobject>
+ fallbackKeyEventObj(env,
+ env->CallObjectMethod(mServiceObj,
+ gServiceClassInfo.dispatchUnhandledKey,
+ tokenObj.get(), keyEventObj.get(),
+ policyFlags));
+
+ android_view_KeyEvent_recycle(env, keyEventObj.get());
+ if (checkAndClearExceptionFromCallback(env, "dispatchUnhandledKey") ||
+ !fallbackKeyEventObj.get()) {
+ return {};
+ }
+
+ const KeyEvent fallbackEvent = android_view_KeyEvent_toNative(env, fallbackKeyEventObj.get());
+ android_view_KeyEvent_recycle(env, fallbackKeyEventObj.get());
+ return fallbackEvent;
}
void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) {
@@ -1876,13 +1874,7 @@
InputEventInjectionSync mode = static_cast<InputEventInjectionSync>(syncMode);
if (env->IsInstanceOf(inputEventObj, gKeyEventClassInfo.clazz)) {
- KeyEvent keyEvent;
- status_t status = android_view_KeyEvent_toNative(env, inputEventObj, & keyEvent);
- if (status) {
- jniThrowRuntimeException(env, "Could not read contents of KeyEvent object.");
- return static_cast<jint>(InputEventInjectionResult::FAILED);
- }
-
+ const KeyEvent keyEvent = android_view_KeyEvent_toNative(env, inputEventObj);
const InputEventInjectionResult result =
im->getInputManager()->getDispatcher().injectInputEvent(&keyEvent, targetUid, mode,
std::chrono::milliseconds(
@@ -1913,13 +1905,7 @@
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
if (env->IsInstanceOf(inputEventObj, gKeyEventClassInfo.clazz)) {
- KeyEvent keyEvent;
- status_t status = android_view_KeyEvent_toNative(env, inputEventObj, &keyEvent);
- if (status) {
- jniThrowRuntimeException(env, "Could not read contents of KeyEvent object.");
- return nullptr;
- }
-
+ const KeyEvent keyEvent = android_view_KeyEvent_toNative(env, inputEventObj);
std::unique_ptr<VerifiedInputEvent> verifiedEvent =
im->getInputManager()->getDispatcher().verifyInputEvent(keyEvent);
if (verifiedEvent == nullptr) {
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index 19a0c5e..04ecd6e 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -67,7 +67,7 @@
.createNewSession(mContext, mUserId, providerInfo,
this, remoteCredentialService);
if (providerClearSession != null) {
- Slog.d(TAG, "In startProviderSession - provider session created "
+ Slog.i(TAG, "Provider session created "
+ "and being added for: " + providerInfo.getComponentName());
mProviders.put(providerClearSession.getComponentName().flattenToString(),
providerClearSession);
@@ -78,12 +78,12 @@
@Override // from provider session
public void onProviderStatusChanged(ProviderSession.Status status,
ComponentName componentName, ProviderSession.CredentialsSource source) {
- Slog.d(TAG, "in onStatusChanged with status: " + status + ", and source: " + source);
+ Slog.i(TAG, "Provider changed with status: " + status + ", and source: " + source);
if (ProviderSession.isTerminatingStatus(status)) {
- Slog.d(TAG, "in onProviderStatusChanged terminating status");
+ Slog.i(TAG, "Provider terminating status");
onProviderTerminated(componentName);
} else if (ProviderSession.isCompletionStatus(status)) {
- Slog.d(TAG, "in onProviderStatusChanged isCompletionStatus status");
+ Slog.i(TAG, "Provider has completion status");
onProviderResponseComplete(componentName);
}
}
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index fc7fd1a..4b32062 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -79,7 +79,7 @@
.createNewSession(mContext, mUserId, providerInfo,
this, remoteCredentialService);
if (providerCreateSession != null) {
- Slog.d(TAG, "In initiateProviderSession - provider session created and "
+ Slog.i(TAG, "Provider session created and "
+ "being added for: " + providerInfo.getComponentName());
mProviders.put(providerCreateSession.getComponentName().flattenToString(),
providerCreateSession);
@@ -98,7 +98,9 @@
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) {
@@ -125,7 +127,7 @@
@Override
public void onFinalResponseReceived(ComponentName componentName,
@Nullable CreateCredentialResponse response) {
- Slog.d(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
+ Slog.i(TAG, "Final credential received from: " + componentName.flattenToString());
mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime());
mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(mProviders.get(
componentName.flattenToString()).mProviderSessionMetric
@@ -168,13 +170,13 @@
@Override
public void onProviderStatusChanged(ProviderSession.Status status,
ComponentName componentName, ProviderSession.CredentialsSource source) {
- Slog.d(TAG, "in onStatusChanged with status: " + status + ", and source: " + source);
+ Slog.i(TAG, "Provider status changed: " + status + ", and source: " + source);
// If all provider responses have been received, we can either need the UI,
// or we need to respond with error. The only other case is the entry being
// selected after the UI has been invoked which has a separate code path.
if (!isAnyProviderPending()) {
if (isUiInvocationNeeded()) {
- Slog.d(TAG, "in onProviderStatusChanged - isUiInvocationNeeded");
+ Slog.i(TAG, "Provider status changed - ui invocation is needed");
getProviderDataAndInitiateUi();
} else {
respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 06b96eb..7f95e05 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -58,7 +58,6 @@
import android.service.credentials.CallingAppInfo;
import android.service.credentials.CredentialProviderInfoFactory;
import android.text.TextUtils;
-import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -437,7 +436,7 @@
IGetCredentialCallback callback,
final String callingPackage) {
final long timestampBegan = System.nanoTime();
- Slog.d(TAG, "starting executeGetCredential with callingPackage: "
+ Slog.i(TAG, "starting executeGetCredential with callingPackage: "
+ callingPackage);
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
@@ -472,7 +471,7 @@
GetCredentialException.TYPE_NO_CREDENTIAL,
"No credentials available on this device.");
} catch (RemoteException e) {
- Log.i(
+ Slog.e(
TAG,
"Issue invoking onError on IGetCredentialCallback "
+ "callback: "
@@ -528,7 +527,7 @@
false, null,
false, false, null));
} catch (RemoteException e) {
- Log.i(
+ Slog.e(
TAG,
"Issue invoking onError on IGetCredentialCallback "
+ "callback: "
@@ -607,7 +606,7 @@
ICreateCredentialCallback callback,
String callingPackage) {
final long timestampBegan = System.nanoTime();
- Slog.d(TAG, "starting executeCreateCredential with callingPackage: "
+ Slog.i(TAG, "starting executeCreateCredential with callingPackage: "
+ callingPackage);
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
@@ -673,7 +672,7 @@
MetricUtilities.logApiCalledInitialPhase(initMetric,
session.mRequestSessionMetric.returnIncrementSequence());
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: ", e);
+ Slog.i(TAG, "Unexpected error during metric logging: ", e);
}
}
@@ -706,7 +705,7 @@
Settings.Secure.CREDENTIAL_SERVICE,
storedValue,
userId)) {
- Log.e(TAG, "Failed to store setting containing enabled providers");
+ Slog.e(TAG, "Failed to store setting containing enabled providers");
try {
callback.onError(
"failed_setting_store",
@@ -733,7 +732,7 @@
@Override
public boolean isEnabledCredentialProviderService(
ComponentName componentName, String callingPackage) {
- Slog.d(TAG, "isEnabledCredentialProviderService with componentName: "
+ Slog.i(TAG, "isEnabledCredentialProviderService with componentName: "
+ componentName.flattenToString());
// TODO(253157366): Check additional set of services.
@@ -829,7 +828,7 @@
IClearCredentialStateCallback callback,
String callingPackage) {
final long timestampBegan = System.nanoTime();
- Slog.d(TAG, "starting clearCredentialState with callingPackage: "
+ Slog.i(TAG, "starting clearCredentialState with callingPackage: "
+ callingPackage);
final int userId = UserHandle.getCallingUserId();
int callingUid = Binder.getCallingUid();
@@ -882,7 +881,7 @@
public void registerCredentialDescription(
RegisterCredentialDescriptionRequest request, String callingPackage)
throws IllegalArgumentException, NonCredentialProviderCallerException {
- Slog.d(TAG, "registerCredentialDescription with callingPackage: " + callingPackage);
+ Slog.i(TAG, "registerCredentialDescription with callingPackage: " + callingPackage);
if (!isCredentialDescriptionApiEnabled()) {
throw new UnsupportedOperationException();
@@ -900,7 +899,7 @@
public void unregisterCredentialDescription(
UnregisterCredentialDescriptionRequest request, String callingPackage)
throws IllegalArgumentException {
- Slog.d(TAG, "unregisterCredentialDescription with callingPackage: "
+ Slog.i(TAG, "unregisterCredentialDescription with callingPackage: "
+ callingPackage);
@@ -962,7 +961,6 @@
@Override
@GuardedBy("mLock")
public void onFinishRequestSession(@UserIdInt int userId, IBinder token) {
- Log.i(TAG, "In onFinishRequestSession");
if (mRequestSessions.get(userId) != null) {
mRequestSessions.get(userId).remove(token);
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
index 91be2a7..808fdae 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
@@ -48,7 +48,7 @@
@NonNull Object lock, int userId, String serviceName)
throws PackageManager.NameNotFoundException {
super(master, lock, userId);
- Slog.d(TAG, "CredentialManagerServiceImpl constructed for: " + serviceName);
+ Slog.i(TAG, "CredentialManagerServiceImpl constructed for: " + serviceName);
synchronized (mLock) {
newServiceInfoLocked(ComponentName.unflattenFromString(serviceName));
}
@@ -63,7 +63,7 @@
@NonNull CredentialManagerService master,
@NonNull Object lock, int userId, CredentialProviderInfo providerInfo) {
super(master, lock, userId);
- Slog.d(TAG, "CredentialManagerServiceImpl constructed for: "
+ Slog.i(TAG, "CredentialManagerServiceImpl constructed for: "
+ providerInfo.getServiceInfo().getComponentName().flattenToString());
mInfo = providerInfo;
}
@@ -74,11 +74,11 @@
throws PackageManager.NameNotFoundException {
// TODO : Test update flows with multiple providers
if (mInfo != null) {
- Slog.d(TAG, "newServiceInfoLocked, mInfo not null : "
+ Slog.i(TAG, "newServiceInfoLocked, mInfo not null : "
+ mInfo.getServiceInfo().getComponentName().flattenToString() + " , "
+ serviceComponent.flattenToString());
} else {
- Slog.d(TAG, "newServiceInfoLocked, mInfo null, "
+ Slog.i(TAG, "newServiceInfoLocked, mInfo null, "
+ serviceComponent.flattenToString());
}
mInfo = CredentialProviderInfoFactory.create(
@@ -95,11 +95,11 @@
public ProviderSession initiateProviderSessionForRequestLocked(
RequestSession requestSession, List<String> requestOptions) {
if (!requestOptions.isEmpty() && !isServiceCapableLocked(requestOptions)) {
- Slog.d(TAG, "Service does not have the required capabilities");
+ Slog.i(TAG, "Service does not have the required capabilities");
return null;
}
if (mInfo == null) {
- Slog.w(TAG, "in initiateProviderSessionForRequest in CredManServiceImpl, "
+ Slog.w(TAG, "Initiating provider session for request "
+ "but mInfo is null. This shouldn't happen");
return null;
}
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 0271727..1503410 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -72,7 +72,7 @@
.createNewSession(mContext, mUserId, providerInfo,
this, remoteCredentialService);
if (providerGetSession != null) {
- Slog.d(TAG, "In startProviderSession - provider session created and "
+ Slog.i(TAG, "Provider session created and "
+ "being added for: " + providerInfo.getComponentName());
mProviders.put(providerGetSession.getComponentName().flattenToString(),
providerGetSession);
@@ -114,7 +114,7 @@
@Override
public void onFinalResponseReceived(ComponentName componentName,
@Nullable GetCredentialResponse response) {
- Slog.d(TAG, "onFinalResponseReceived from: " + componentName.flattenToString());
+ Slog.i(TAG, "onFinalResponseReceived from: " + componentName.flattenToString());
mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime());
mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(
mProviders.get(componentName.flattenToString())
@@ -158,7 +158,7 @@
@Override
public void onProviderStatusChanged(ProviderSession.Status status,
ComponentName componentName, ProviderSession.CredentialsSource source) {
- Slog.d(TAG, "in onStatusChanged for: " + componentName + ", with status: "
+ Slog.i(TAG, "Status changed for: " + componentName + ", with status: "
+ status + ", and source: " + source);
// Auth entry was selected, and it did not have any underlying credentials
@@ -172,7 +172,7 @@
// or we need to respond with error. The only other case is the entry being
// selected after the UI has been invoked which has a separate code path.
if (isUiInvocationNeeded()) {
- Slog.d(TAG, "in onProviderStatusChanged - isUiInvocationNeeded");
+ Slog.i(TAG, "Provider status changed - ui invocation is needed");
getProviderDataAndInitiateUi();
} else {
respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index 50e5163..47502c2 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -19,7 +19,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.util.Log;
+import android.util.Slog;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.credentials.metrics.ApiName;
@@ -27,6 +27,7 @@
import com.android.server.credentials.metrics.CandidateBrowsingPhaseMetric;
import com.android.server.credentials.metrics.CandidatePhaseMetric;
import com.android.server.credentials.metrics.ChosenProviderFinalPhaseMetric;
+import com.android.server.credentials.metrics.EntryEnum;
import com.android.server.credentials.metrics.InitialPhaseMetric;
import java.util.List;
@@ -35,6 +36,7 @@
/**
* 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;
@@ -68,7 +70,7 @@
componentName.getPackageName(),
PackageManager.ApplicationInfoFlags.of(0)).uid;
} catch (Throwable t) {
- Log.i(TAG, "Couldn't find required uid");
+ Slog.i(TAG, "Couldn't find required uid");
}
return sessUid;
}
@@ -146,28 +148,28 @@
.getFinalFinishTimeNanoseconds()),
/* chosen_provider_status */ finalPhaseMetric.getChosenProviderStatus(),
/* chosen_provider_has_exception */ finalPhaseMetric.isHasException(),
- /* chosen_provider_available_entries */ finalPhaseMetric.getAvailableEntries()
- .stream().mapToInt(i -> i).toArray(),
- /* chosen_provider_action_entry_count */ finalPhaseMetric.getActionEntryCount(),
- /* chosen_provider_credential_entry_count */
- finalPhaseMetric.getCredentialEntryCount(),
- /* chosen_provider_credential_entry_type_count */
- finalPhaseMetric.getCredentialEntryTypeCount(),
- /* chosen_provider_remote_entry_count */
- finalPhaseMetric.getRemoteEntryCount(),
- /* chosen_provider_authentication_entry_count */
- finalPhaseMetric.getAuthenticationEntryCount(),
+ /* chosen_provider_available_entries (deprecated) */ DEFAULT_REPEATED_INT_32,
+ /* chosen_provider_action_entry_count (deprecated) */ DEFAULT_INT_32,
+ /* chosen_provider_credential_entry_count (deprecated)*/DEFAULT_INT_32,
+ /* chosen_provider_credential_entry_type_count (deprecated) */ DEFAULT_INT_32,
+ /* chosen_provider_remote_entry_count (deprecated) */ DEFAULT_INT_32,
+ /* chosen_provider_authentication_entry_count (deprecated) */ DEFAULT_INT_32,
/* clicked_entries */ browsedClickedEntries,
/* provider_of_clicked_entry */ browsedProviderUid,
/* api_status */ apiStatus,
- DEFAULT_REPEATED_INT_32,
- DEFAULT_REPEATED_INT_32,
- DEFAULT_REPEATED_STR,
- DEFAULT_REPEATED_INT_32,
+ /* unique_entries */
+ finalPhaseMetric.getResponseCollective().getUniqueEntries(),
+ /* per_entry_counts */
+ finalPhaseMetric.getResponseCollective().getUniqueEntryCounts(),
+ /* unique_response_classtypes */
+ finalPhaseMetric.getResponseCollective().getUniqueResponseStrings(),
+ /* per_classtype_counts */
+ finalPhaseMetric.getResponseCollective().getUniqueResponseCounts(),
+ /* framework_exception_unique_classtypes */
DEFAULT_STRING
);
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.w(TAG, "Unexpected error during final provider uid emit: " + e);
}
}
@@ -222,12 +224,18 @@
metric.getQueryFinishTimeNanoseconds());
candidateStatusList[index] = metric.getProviderQueryStatus();
candidateHasExceptionList[index] = metric.isHasException();
- candidateTotalEntryCountList[index] = metric.getNumEntriesTotal();
- candidateCredentialEntryCountList[index] = metric.getCredentialEntryCount();
- candidateCredentialTypeCountList[index] = metric.getCredentialEntryTypeCount();
- candidateActionEntryCountList[index] = metric.getActionEntryCount();
- candidateAuthEntryCountList[index] = metric.getAuthenticationEntryCount();
- candidateRemoteEntryCountList[index] = metric.getRemoteEntryCount();
+ candidateTotalEntryCountList[index] = metric.getResponseCollective()
+ .getNumEntriesTotal();
+ candidateCredentialEntryCountList[index] = metric.getResponseCollective()
+ .getCountForEntry(EntryEnum.CREDENTIAL_ENTRY);
+ candidateCredentialTypeCountList[index] = metric.getResponseCollective()
+ .getUniqueResponseStrings().length;
+ candidateActionEntryCountList[index] = metric.getResponseCollective()
+ .getCountForEntry(EntryEnum.ACTION_ENTRY);
+ candidateAuthEntryCountList[index] = metric.getResponseCollective()
+ .getCountForEntry(EntryEnum.AUTHENTICATION_ENTRY);
+ candidateRemoteEntryCountList[index] = metric.getResponseCollective()
+ .getCountForEntry(EntryEnum.REMOTE_ENTRY);
frameworkExceptionList[index] = metric.getFrameworkException();
index++;
}
@@ -261,7 +269,7 @@
initialPhaseMetric.getUniqueRequestCounts()
);
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.w(TAG, "Unexpected error during candidate provider uid metric emit: " + e);
}
}
@@ -297,7 +305,7 @@
DEFAULT_INT_32,
/* chosen_provider_status */ DEFAULT_INT_32);
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.w(TAG, "Unexpected error during metric logging: " + e);
}
}
@@ -330,7 +338,7 @@
initialPhaseMetric.isOriginSpecified()
);
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.w(TAG, "Unexpected error during initial metric emit: " + e);
}
}
}
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
index 441c87b..36bc8ba 100644
--- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -66,7 +66,7 @@
@Override
public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName,
ProviderSession.CredentialsSource source) {
- Slog.d(TAG, "in onProviderStatusChanged with status: " + status + ", and "
+ Slog.i(TAG, "Provider Status changed with status: " + status + ", and "
+ "source: " + source);
switch (source) {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index 8af6b56..c1fb92d 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -80,7 +80,7 @@
@Override
public void onProviderResponseSuccess(@Nullable Void response) {
- Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
+ Slog.i(TAG, "Remote provider responded with a valid response: " + mComponentName);
mProviderResponseSet = true;
updateStatusAndInvokeCallback(Status.COMPLETE,
/*source=*/ CredentialsSource.REMOTE_PROVIDER);
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 520b937..4cdc6f4 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -75,7 +75,8 @@
CreateCredentialRequest providerCreateRequest =
createProviderRequest(providerInfo.getCapabilities(),
createRequestSession.mClientRequest,
- createRequestSession.mClientAppInfo);
+ createRequestSession.mClientAppInfo,
+ providerInfo.isSystemProvider());
if (providerCreateRequest != null) {
return new ProviderCreateSession(
context,
@@ -92,7 +93,7 @@
createRequestSession.mHybridService
);
}
- Slog.d(TAG, "Unable to create provider session for: "
+ Slog.i(TAG, "Unable to create provider session for: "
+ providerInfo.getComponentName());
return null;
}
@@ -114,9 +115,16 @@
}
@Nullable
- private static CreateCredentialRequest createProviderRequest(List<String> providerCapabilities,
+ private static CreateCredentialRequest createProviderRequest(
+ List<String> providerCapabilities,
android.credentials.CreateCredentialRequest clientRequest,
- CallingAppInfo callingAppInfo) {
+ CallingAppInfo callingAppInfo,
+ boolean isSystemProvider) {
+ if (clientRequest.isSystemProviderRequired() && !isSystemProvider) {
+ // Request requires system provider but this session does not correspond to a
+ // system service
+ return null;
+ }
String capability = clientRequest.getType();
if (providerCapabilities.contains(capability)) {
return new CreateCredentialRequest(callingAppInfo, capability,
@@ -145,7 +153,7 @@
@Override
public void onProviderResponseSuccess(
@Nullable BeginCreateCredentialResponse response) {
- Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
+ Slog.i(TAG, "Remote provider responded with a valid response: " + mComponentName);
onSetInitialRemoteResponse(response);
}
@@ -200,7 +208,7 @@
protected CreateCredentialProviderData prepareUiData()
throws IllegalArgumentException {
if (!ProviderSession.isUiInvokingStatus(getStatus())) {
- Slog.d(TAG, "No data for UI from: " + mComponentName.flattenToString());
+ Slog.i(TAG, "No data for UI from: " + mComponentName.flattenToString());
return null;
}
@@ -216,7 +224,7 @@
switch (entryType) {
case SAVE_ENTRY_KEY:
if (mProviderResponseDataHandler.getCreateEntry(entryKey) == null) {
- Slog.w(TAG, "Unexpected save entry key");
+ Slog.i(TAG, "Unexpected save entry key");
invokeCallbackOnInternalInvalidState();
return;
}
@@ -224,14 +232,14 @@
break;
case REMOTE_ENTRY_KEY:
if (mProviderResponseDataHandler.getRemoteEntry(entryKey) == null) {
- Slog.w(TAG, "Unexpected remote entry key");
+ Slog.i(TAG, "Unexpected remote entry key");
invokeCallbackOnInternalInvalidState();
return;
}
onRemoteEntrySelected(providerPendingIntentResponse);
break;
default:
- Slog.w(TAG, "Unsupported entry type selected");
+ Slog.i(TAG, "Unsupported entry type selected");
invokeCallbackOnInternalInvalidState();
}
}
@@ -266,7 +274,7 @@
if (credentialResponse != null) {
mCallbacks.onFinalResponseReceived(mComponentName, credentialResponse);
} else {
- Slog.w(TAG, "onSaveEntrySelected - no response or error found in pending "
+ Slog.i(TAG, "onSaveEntrySelected - no response or error found in pending "
+ "intent response");
invokeCallbackOnInternalInvalidState();
}
@@ -282,14 +290,14 @@
private CreateCredentialException maybeGetPendingIntentException(
ProviderPendingIntentResponse pendingIntentResponse) {
if (pendingIntentResponse == null) {
- Slog.w(TAG, "pendingIntentResponse is null");
+ Slog.i(TAG, "pendingIntentResponse is null");
return new CreateCredentialException(CreateCredentialException.TYPE_NO_CREATE_OPTIONS);
}
if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
CreateCredentialException exception = PendingIntentResultHandler
.extractCreateCredentialException(pendingIntentResponse.getResultData());
if (exception != null) {
- Slog.d(TAG, "Pending intent contains provider exception");
+ Slog.i(TAG, "Pending intent contains provider exception");
return exception;
}
} else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index a62d9e8..8070fa7 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.CredentialProviderService;
import android.service.credentials.GetCredentialRequest;
import android.service.credentials.RemoteEntry;
-import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -115,7 +114,7 @@
getRequestSession.mHybridService
);
}
- Slog.d(TAG, "Unable to create provider session for: "
+ Slog.i(TAG, "Unable to create provider session for: "
+ providerInfo.getComponentName());
return null;
}
@@ -147,13 +146,13 @@
android.credentials.GetCredentialRequest clientRequest,
CredentialProviderInfo info
) {
- Slog.d(TAG, "Filtering request options for: " + info.getComponentName());
+ Slog.i(TAG, "Filtering request options for: " + info.getComponentName());
List<CredentialOption> filteredOptions = new ArrayList<>();
for (CredentialOption option : clientRequest.getCredentialOptions()) {
if (providerCapabilities.contains(option.getType())
&& isProviderAllowed(option, info.getComponentName())
&& checkSystemProviderRequirement(option, info.isSystemProvider())) {
- Slog.d(TAG, "Option of type: " + option.getType() + " meets all filtering"
+ Slog.i(TAG, "Option of type: " + option.getType() + " meets all filtering"
+ "conditions");
filteredOptions.add(option);
}
@@ -164,14 +163,14 @@
.setCredentialOptions(
filteredOptions).build();
}
- Slog.d(TAG, "No options filtered");
+ Slog.i(TAG, "No options filtered");
return null;
}
private static boolean isProviderAllowed(CredentialOption option, ComponentName componentName) {
if (!option.getAllowedProviders().isEmpty() && !option.getAllowedProviders().contains(
componentName)) {
- Slog.d(TAG, "Provider allow list specified but does not contain this provider");
+ Slog.i(TAG, "Provider allow list specified but does not contain this provider");
return false;
}
return true;
@@ -180,7 +179,7 @@
private static boolean checkSystemProviderRequirement(CredentialOption option,
boolean isSystemProvider) {
if (option.isSystemProviderRequired() && !isSystemProvider) {
- Slog.d(TAG, "System provider required, but this service is not a system provider");
+ Slog.i(TAG, "System provider required, but this service is not a system provider");
return false;
}
return true;
@@ -208,7 +207,7 @@
/** Called when the provider response has been updated by an external source. */
@Override // Callback from the remote provider
public void onProviderResponseSuccess(@Nullable BeginGetCredentialResponse response) {
- Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
+ Slog.i(TAG, "Remote provider responded with a valid response: " + mComponentName);
onSetInitialRemoteResponse(response);
}
@@ -245,14 +244,14 @@
@Override // Selection call from the request provider
protected void onUiEntrySelected(String entryType, String entryKey,
ProviderPendingIntentResponse providerPendingIntentResponse) {
- Slog.d(TAG, "onUiEntrySelected with entryType: " + entryType + ", and entryKey: "
+ Slog.i(TAG, "onUiEntrySelected with entryType: " + entryType + ", and entryKey: "
+ entryKey);
switch (entryType) {
case CREDENTIAL_ENTRY_KEY:
CredentialEntry credentialEntry = mProviderResponseDataHandler
.getCredentialEntry(entryKey);
if (credentialEntry == null) {
- Slog.w(TAG, "Unexpected credential entry key");
+ Slog.i(TAG, "Unexpected credential entry key");
invokeCallbackOnInternalInvalidState();
return;
}
@@ -261,7 +260,7 @@
case ACTION_ENTRY_KEY:
Action actionEntry = mProviderResponseDataHandler.getActionEntry(entryKey);
if (actionEntry == null) {
- Slog.w(TAG, "Unexpected action entry key");
+ Slog.i(TAG, "Unexpected action entry key");
invokeCallbackOnInternalInvalidState();
return;
}
@@ -271,21 +270,21 @@
Action authenticationEntry = mProviderResponseDataHandler
.getAuthenticationAction(entryKey);
if (authenticationEntry == null) {
- Slog.w(TAG, "Unexpected authenticationEntry key");
+ Slog.i(TAG, "Unexpected authenticationEntry key");
invokeCallbackOnInternalInvalidState();
return;
}
boolean additionalContentReceived =
onAuthenticationEntrySelected(providerPendingIntentResponse);
if (additionalContentReceived) {
- Slog.d(TAG, "Additional content received - removing authentication entry");
+ Slog.i(TAG, "Additional content received - removing authentication entry");
mProviderResponseDataHandler.removeAuthenticationAction(entryKey);
if (!mProviderResponseDataHandler.isEmptyResponse()) {
updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED,
/*source=*/ CredentialsSource.AUTH_ENTRY);
}
} else {
- Slog.d(TAG, "Additional content not received from authentication entry");
+ Slog.i(TAG, "Additional content not received from authentication entry");
mProviderResponseDataHandler
.updateAuthEntryWithNoCredentialsReceived(entryKey);
updateStatusAndInvokeCallback(Status.NO_CREDENTIALS_FROM_AUTH_ENTRY,
@@ -296,12 +295,12 @@
if (mProviderResponseDataHandler.getRemoteEntry(entryKey) != null) {
onRemoteEntrySelected(providerPendingIntentResponse);
} else {
- Slog.d(TAG, "Unexpected remote entry key");
+ Slog.i(TAG, "Unexpected remote entry key");
invokeCallbackOnInternalInvalidState();
}
break;
default:
- Slog.w(TAG, "Unsupported entry type selected");
+ Slog.i(TAG, "Unsupported entry type selected");
invokeCallbackOnInternalInvalidState();
}
}
@@ -323,13 +322,12 @@
@Nullable
protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
if (!ProviderSession.isUiInvokingStatus(getStatus())) {
- Slog.d(TAG, "No data for UI from: " + mComponentName.flattenToString());
+ Slog.i(TAG, "No data for UI from: " + mComponentName.flattenToString());
return null;
}
if (mProviderResponse != null && !mProviderResponseDataHandler.isEmptyResponse()) {
return mProviderResponseDataHandler.toGetCredentialProviderData();
}
- Slog.d(TAG, "In prepareUiData response null");
return null;
}
@@ -382,7 +380,7 @@
getCredentialResponse);
return;
}
- Slog.d(TAG, "Pending intent response contains no credential, or error "
+ Slog.i(TAG, "Pending intent response contains no credential, or error "
+ "for a credential entry");
invokeCallbackOnInternalInvalidState();
}
@@ -413,11 +411,9 @@
*/
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;
}
@@ -462,7 +458,7 @@
/** Returns true if either an exception or a response is found. */
private void onActionEntrySelected(ProviderPendingIntentResponse
providerPendingIntentResponse) {
- Slog.d(TAG, "onActionEntrySelected");
+ Slog.i(TAG, "onActionEntrySelected");
onCredentialEntrySelected(providerPendingIntentResponse);
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index c10f564..b0b72bc 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -171,11 +171,11 @@
@Override
protected ProviderData prepareUiData() {
if (!ProviderSession.isUiInvokingStatus(getStatus())) {
- Slog.d(TAG, "No date for UI coming from: " + mComponentName.flattenToString());
+ Slog.i(TAG, "No date for UI coming from: " + mComponentName.flattenToString());
return null;
}
if (mProviderResponse == null) {
- Slog.w(TAG, "In prepareUiData but response is null. This is strange.");
+ Slog.w(TAG, "response is null when preparing ui data. This is strange.");
return null;
}
return new GetCredentialProviderData.Builder(
@@ -196,13 +196,13 @@
case CREDENTIAL_ENTRY_KEY:
CredentialEntry credentialEntry = mUiCredentialEntries.get(entryKey);
if (credentialEntry == null) {
- Slog.w(TAG, "Unexpected credential entry key");
+ Slog.i(TAG, "Unexpected credential entry key");
return;
}
onCredentialEntrySelected(credentialEntry, providerPendingIntentResponse);
break;
default:
- Slog.w(TAG, "Unsupported entry type selected");
+ Slog.i(TAG, "Unsupported entry type selected");
}
}
@@ -256,6 +256,7 @@
@Override
protected void invokeSession() {
+ startCandidateMetrics();
mProviderResponse = mCredentialDescriptionRegistry
.getFilteredResultForProvider(mCredentialProviderPackageName,
mElementKeys);
@@ -266,7 +267,7 @@
.collect(Collectors.toList());
updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED,
/*source=*/ CredentialsSource.REGISTRY);
- // TODO(use metric later)
+ mProviderSessionMetric.collectCandidateEntryMetrics(mCredentialEntries);
}
@Nullable
@@ -279,7 +280,7 @@
GetCredentialException exception = PendingIntentResultHandler
.extractGetCredentialException(pendingIntentResponse.getResultData());
if (exception != null) {
- Slog.d(TAG, "Pending intent contains provider exception");
+ Slog.i(TAG, "Pending intent contains provider exception");
return exception;
}
} else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index d02a8c1..73fdc1c 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 @@
/*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/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index 0ad73c9..f5e3b86 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -292,13 +292,13 @@
callback.onProviderResponseSuccess(result);
} else {
if (error instanceof TimeoutException) {
- Slog.d(TAG, "Remote provider response timed tuo for: " + mComponentName);
+ Slog.i(TAG, "Remote provider response timed tuo for: " + mComponentName);
dispatchCancellationSignal(cancellationSink.get());
callback.onProviderResponseFailure(
CredentialProviderErrors.ERROR_TIMEOUT,
null);
} else if (error instanceof CancellationException) {
- Slog.d(TAG, "Cancellation exception for remote provider: " + mComponentName);
+ Slog.i(TAG, "Cancellation exception for remote provider: " + mComponentName);
dispatchCancellationSignal(cancellationSink.get());
callback.onProviderResponseFailure(
CredentialProviderErrors.ERROR_TASK_CANCELED,
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 15a30e4..7caa921 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -201,7 +201,7 @@
}
protected void finishSession(boolean propagateCancellation) {
- Slog.d(TAG, "finishing session with propagateCancellation " + propagateCancellation);
+ Slog.i(TAG, "finishing session with propagateCancellation " + propagateCancellation);
if (propagateCancellation) {
mProviders.values().forEach(ProviderSession::cancelProviderRemoteSession);
}
@@ -265,7 +265,7 @@
@NonNull
protected ArrayList<ProviderData> getProviderDataForUi() {
- Slog.d(TAG, "In getProviderDataAndInitiateUi providers size: " + mProviders.size());
+ Slog.i(TAG, "For ui, provider data size: " + mProviders.size());
ArrayList<ProviderData> providerDataList = new ArrayList<>();
mRequestSessionMetric.logCandidatePhaseMetrics(mProviders);
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ApiName.java b/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
index b99f28d..1930a48 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ApiName.java
@@ -27,7 +27,7 @@
import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_UNKNOWN;
import android.credentials.ui.RequestInfo;
-import android.util.Log;
+import android.util.Slog;
import java.util.AbstractMap;
import java.util.Map;
@@ -79,7 +79,7 @@
*/
public static int getMetricCodeFromRequestInfo(String stringKey) {
if (!sRequestInfoToMetric.containsKey(stringKey)) {
- Log.w(TAG, "Attempted to use an unsupported string key request info");
+ Slog.i(TAG, "Attempted to use an unsupported string key request info");
return UNKNOWN.mInnerMetricCode;
}
return sRequestInfoToMetric.get(stringKey);
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java
index 0e1e0389..07af654 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java
@@ -27,8 +27,6 @@
* though collection will begin in the candidate phase when the user begins browsing options.
*/
public class CandidateBrowsingPhaseMetric {
-
- private static final String TAG = "CandidateBrowsingPhaseMetric";
// The session id associated with the API Call this candidate provider is a part of, default -1
private int mSessionId = -1;
// The EntryEnum that was pressed, defaults to -1
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 721d3d7..3ea9b1ce 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
@@ -16,21 +16,18 @@
package com.android.server.credentials.metrics;
-import android.util.IntArray;
-import android.util.Log;
+import android.util.Slog;
import com.android.server.credentials.MetricUtilities;
+import com.android.server.credentials.metrics.shared.ResponseCollective;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
+import java.util.Map;
/**
* The central candidate provider metric object that mimics our defined metric setup.
* 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,26 +53,12 @@
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
- private int mNumEntriesTotal = -1;
- // The count of action entries from this provider, defaults to -1
- private int mActionEntryCount = -1;
- // The count of credential entries from this provider, defaults to -1
- private int mCredentialEntryCount = -1;
- // The *type-count* of the credential entries, defaults to -1
- private int mCredentialEntryTypeCount = -1;
- // The count of remote entries from this provider, defaults to -1
- private int mRemoteEntryCount = -1;
- // The count of authentication entries from this provider, defaults to -1
- private int mAuthenticationEntryCount = -1;
- // Gathered to pass on to chosen provider when required
- private final IntArray mAvailableEntries = new IntArray();
- // The *framework only* exception held by this provider, empty string by default
private String mFrameworkException = "";
+ // Stores the response credential information, as well as the response entry information which
+ // by default, contains empty info
+ private ResponseCollective mResponseCollective = new ResponseCollective(Map.of(), Map.of());
+
public CandidatePhaseMetric() {
}
@@ -129,7 +112,7 @@
*/
public int getTimestampFromReferenceStartMicroseconds(long specificTimestamp) {
if (specificTimestamp < mServiceBeganTimeNanoseconds) {
- Log.i(TAG, "The timestamp is before service started, falling back to default int");
+ Slog.i(TAG, "The timestamp is before service started, falling back to default int");
return MetricUtilities.DEFAULT_INT_32;
}
return (int) ((specificTimestamp
@@ -186,88 +169,13 @@
return mHasException;
}
- /* -------------- Number of Entries ---------------- */
-
- public void setNumEntriesTotal(int numEntriesTotal) {
- mNumEntriesTotal = numEntriesTotal;
+ /* -------------- The Entries and Responses Gathered ---------------- */
+ public void setResponseCollective(ResponseCollective responseCollective) {
+ mResponseCollective = responseCollective;
}
- public int getNumEntriesTotal() {
- return mNumEntriesTotal;
- }
-
- /* -------------- Count of Action Entries ---------------- */
-
- public void setActionEntryCount(int actionEntryCount) {
- mActionEntryCount = actionEntryCount;
- }
-
- public int getActionEntryCount() {
- return mActionEntryCount;
- }
-
- /* -------------- Count of Credential Entries ---------------- */
-
- public void setCredentialEntryCount(int credentialEntryCount) {
- mCredentialEntryCount = credentialEntryCount;
- }
-
- public int getCredentialEntryCount() {
- return mCredentialEntryCount;
- }
-
- /* -------------- Count of Credential Entry Types ---------------- */
-
- public void setCredentialEntryTypeCount(int credentialEntryTypeCount) {
- mCredentialEntryTypeCount = credentialEntryTypeCount;
- }
-
- public int getCredentialEntryTypeCount() {
- return mCredentialEntryTypeCount;
- }
-
- /* -------------- Count of Remote Entries ---------------- */
-
- public void setRemoteEntryCount(int remoteEntryCount) {
- mRemoteEntryCount = remoteEntryCount;
- }
-
- public int getRemoteEntryCount() {
- return mRemoteEntryCount;
- }
-
- /* -------------- Count of Authentication Entries ---------------- */
-
- public void setAuthenticationEntryCount(int authenticationEntryCount) {
- mAuthenticationEntryCount = authenticationEntryCount;
- }
-
- public int getAuthenticationEntryCount() {
- return mAuthenticationEntryCount;
- }
-
- /* -------------- The Entries Gathered ---------------- */
-
- /**
- * Allows adding an entry record to this metric collector, which can then be propagated to
- * the final phase to retain information on the data available to the candidate.
- *
- * @param e the entry enum collected by the candidate provider associated with this metric
- * collector
- */
- public void addEntry(EntryEnum e) {
- mAvailableEntries.add(e.getMetricCode());
- }
-
- /**
- * Returns a safely copied list of the entries captured by this metric collector associated
- * with a particular candidate provider.
- *
- * @return the full collection of entries encountered by the candidate provider associated with
- * this metric
- */
- public List<Integer> getAvailableEntries() {
- return Arrays.stream(mAvailableEntries.toArray()).boxed().collect(Collectors.toList());
+ public ResponseCollective getResponseCollective() {
+ return mResponseCollective;
}
/* ------ Framework Exception for this Candidate ------ */
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 c80cc24..93a8290 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
@@ -16,12 +16,12 @@
package com.android.server.credentials.metrics;
-import android.util.Log;
+import android.util.Slog;
import com.android.server.credentials.MetricUtilities;
+import com.android.server.credentials.metrics.shared.ResponseCollective;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Map;
/**
* The central chosen provider metric object that mimics our defined metric setup. This is used
@@ -29,11 +29,8 @@
* 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;
@@ -69,21 +66,10 @@
private int mChosenProviderStatus = -1;
// Indicates if an exception was thrown by this provider, false by default
private boolean mHasException = false;
- // Indicates the number of total entries available, defaults to -1. Not presently emitted, but
- // left as a utility
- private int mNumEntriesTotal = -1;
- // The count of action entries from this provider, defaults to -1
- private int mActionEntryCount = -1;
- // The count of credential entries from this provider, defaults to -1
- private int mCredentialEntryCount = -1;
- // The *type-count* of the credential entries, defaults to -1
- private int mCredentialEntryTypeCount = -1;
- // The count of remote entries from this provider, defaults to -1
- private int mRemoteEntryCount = -1;
- // The count of authentication entries from this provider, defaults to -1
- private int mAuthenticationEntryCount = -1;
- // Gathered to pass on to chosen provider when required
- private List<Integer> mAvailableEntries = new ArrayList<>();
+
+ // Stores the response credential information, as well as the response entry information which
+ // by default, contains empty info
+ private ResponseCollective mResponseCollective = new ResponseCollective(Map.of(), Map.of());
public ChosenProviderFinalPhaseMetric() {
@@ -230,7 +216,7 @@
*/
public int getTimestampFromReferenceStartMicroseconds(long specificTimestamp) {
if (specificTimestamp < mServiceBeganTimeNanoseconds) {
- Log.i(TAG, "The timestamp is before service started, falling back to default int");
+ Slog.i(TAG, "The timestamp is before service started, falling back to default int");
return MetricUtilities.DEFAULT_INT_32;
}
return (int) ((specificTimestamp
@@ -267,87 +253,6 @@
return mUiReturned;
}
- /* -------------- Number of Entries ---------------- */
-
- public void setNumEntriesTotal(int numEntriesTotal) {
- mNumEntriesTotal = numEntriesTotal;
- }
-
- public int getNumEntriesTotal() {
- return mNumEntriesTotal;
- }
-
- /* -------------- Count of Action Entries ---------------- */
-
- public void setActionEntryCount(int actionEntryCount) {
- mActionEntryCount = actionEntryCount;
- }
-
- public int getActionEntryCount() {
- return mActionEntryCount;
- }
-
- /* -------------- Count of Credential Entries ---------------- */
-
- public void setCredentialEntryCount(int credentialEntryCount) {
- mCredentialEntryCount = credentialEntryCount;
- }
-
- public int getCredentialEntryCount() {
- return mCredentialEntryCount;
- }
-
- /* -------------- Count of Credential Entry Types ---------------- */
-
- public void setCredentialEntryTypeCount(int credentialEntryTypeCount) {
- mCredentialEntryTypeCount = credentialEntryTypeCount;
- }
-
- public int getCredentialEntryTypeCount() {
- return mCredentialEntryTypeCount;
- }
-
- /* -------------- Count of Remote Entries ---------------- */
-
- public void setRemoteEntryCount(int remoteEntryCount) {
- mRemoteEntryCount = remoteEntryCount;
- }
-
- public int getRemoteEntryCount() {
- return mRemoteEntryCount;
- }
-
- /* -------------- Count of Authentication Entries ---------------- */
-
- public void setAuthenticationEntryCount(int authenticationEntryCount) {
- mAuthenticationEntryCount = authenticationEntryCount;
- }
-
- public int getAuthenticationEntryCount() {
- return mAuthenticationEntryCount;
- }
-
- /* -------------- The Entries Gathered ---------------- */
-
- /**
- * Sets the collected list of entries from the candidate phase to be retrievable in the
- * chosen phase in a semantically correct way.
- */
- public void setAvailableEntries(List<Integer> entries) {
- mAvailableEntries = new ArrayList<>(entries); // no alias copy
- }
-
- /**
- * Returns a list of the entries captured by this metric collector associated
- * with a particular chosen provider.
- *
- * @return the full collection of entries encountered by the chosen provider during the
- * candidate phase.
- */
- public List<Integer> getAvailableEntries() {
- return new ArrayList<>(mAvailableEntries); // no alias copy
- }
-
/* -------------- Has Exception ---------------- */
public void setHasException(boolean hasException) {
@@ -357,4 +262,14 @@
public boolean isHasException() {
return mHasException;
}
+
+ /* -------------- The Entries and Responses Gathered ---------------- */
+
+ public void setResponseCollective(ResponseCollective responseCollective) {
+ mResponseCollective = responseCollective;
+ }
+
+ public ResponseCollective getResponseCollective() {
+ return mResponseCollective;
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java b/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java
index b9125dd..530f01c 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java
@@ -26,7 +26,7 @@
import static com.android.server.credentials.ProviderGetSession.CREDENTIAL_ENTRY_KEY;
import static com.android.server.credentials.ProviderGetSession.REMOTE_ENTRY_KEY;
-import android.util.Log;
+import android.util.Slog;
import java.util.AbstractMap;
import java.util.Map;
@@ -77,7 +77,7 @@
*/
public static int getMetricCodeFromString(String stringKey) {
if (!sKeyToEntryCode.containsKey(stringKey)) {
- Log.w(TAG, "Attempted to use an unsupported string key entry type");
+ Slog.i(TAG, "Attempted to use an unsupported string key entry type");
return UNKNOWN.mInnerMetricCode;
}
return sKeyToEntryCode.get(stringKey);
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 0ecd9cc..060e56c 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
@@ -16,8 +16,6 @@
package com.android.server.credentials.metrics;
-import android.util.Log;
-
import java.util.LinkedHashMap;
import java.util.Map;
@@ -26,7 +24,6 @@
* 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,10 +44,9 @@
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}.
+ // Stores the deduped request information, particularly {"req":5}
private Map<String, Integer> mRequestCounts = new LinkedHashMap<>();
@@ -140,26 +136,20 @@
}
/**
- * Reruns the unique, deduped, request classtypes for logging.
+ * Returns the unique, deduped, request classtypes for logging.
* @return a string array for deduped classtypes
*/
public String[] getUniqueRequestStrings() {
- if (mRequestCounts.isEmpty()) {
- Log.w(TAG, "There are no unique string request types collected");
- }
String[] result = new String[mRequestCounts.keySet().size()];
mRequestCounts.keySet().toArray(result);
return result;
}
/**
- * Reruns the unique, deduped, request classtype counts for logging.
+ * Returns the unique, deduped, request classtype counts for logging.
* @return a string array for deduped classtype counts
*/
public int[] getUniqueRequestCounts() {
- if (mRequestCounts.isEmpty()) {
- Log.w(TAG, "There are no unique string request type counts collected");
- }
return mRequestCounts.values().stream().mapToInt(Integer::intValue).toArray();
}
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java
index 9a88255..f011b55 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java
@@ -16,15 +16,21 @@
package com.android.server.credentials.metrics;
+import static com.android.server.credentials.MetricUtilities.DELTA_CUT;
+import static com.android.server.credentials.MetricUtilities.generateMetricKey;
+
import android.annotation.NonNull;
import android.service.credentials.BeginCreateCredentialResponse;
import android.service.credentials.BeginGetCredentialResponse;
import android.service.credentials.CredentialEntry;
-import android.util.Log;
+import android.util.Slog;
import com.android.server.credentials.MetricUtilities;
+import com.android.server.credentials.metrics.shared.ResponseCollective;
-import java.util.stream.Collectors;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
/**
* Provides contextual metric collection for objects generated from
@@ -68,7 +74,7 @@
try {
mCandidatePhasePerProviderMetric.setFrameworkException(exceptionType);
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.i(TAG, "Unexpected error during candidate exception metric logging: " + e);
}
}
@@ -97,7 +103,7 @@
.getMetricCode());
}
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.i(TAG, "Unexpected error during candidate update metric logging: " + e);
}
}
@@ -118,7 +124,7 @@
initMetric.getCredentialServiceStartedTimeNanoseconds());
mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.i(TAG, "Unexpected error during candidate setup metric logging: " + e);
}
}
@@ -138,59 +144,79 @@
beginCreateCredentialResponseCollectionCandidateEntryMetrics(
(BeginCreateCredentialResponse) response);
} else {
- Log.i(TAG, "Your response type is unsupported for metric logging");
+ Slog.i(TAG, "Your response type is unsupported for metric logging");
}
-
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.i(TAG, "Unexpected error during candidate entry metric logging: " + e);
}
}
+ /**
+ * Once entries are received from the registry, this helps collect their info for metric
+ * purposes.
+ *
+ * @param entries contains matching entries from the Credential Registry.
+ */
+ public void collectCandidateEntryMetrics(List<CredentialEntry> entries) {
+ int numCredEntries = entries.size();
+ int numRemoteEntry = MetricUtilities.ZERO;
+ int numActionEntries = MetricUtilities.ZERO;
+ int numAuthEntries = MetricUtilities.ZERO;
+ Map<EntryEnum, Integer> entryCounts = new LinkedHashMap<>();
+ Map<String, Integer> responseCounts = new LinkedHashMap<>();
+ entryCounts.put(EntryEnum.REMOTE_ENTRY, numRemoteEntry);
+ entryCounts.put(EntryEnum.CREDENTIAL_ENTRY, numCredEntries);
+ entryCounts.put(EntryEnum.ACTION_ENTRY, numActionEntries);
+ entryCounts.put(EntryEnum.AUTHENTICATION_ENTRY, numAuthEntries);
+
+ entries.forEach(entry -> {
+ String entryKey = generateMetricKey(entry.getType(), DELTA_CUT);
+ responseCounts.put(entryKey, responseCounts.getOrDefault(entryKey, 0) + 1);
+ });
+
+ ResponseCollective responseCollective = new ResponseCollective(responseCounts, entryCounts);
+ mCandidatePhasePerProviderMetric.setResponseCollective(responseCollective);
+ }
+
private void beginCreateCredentialResponseCollectionCandidateEntryMetrics(
BeginCreateCredentialResponse response) {
+ Map<EntryEnum, Integer> entryCounts = new LinkedHashMap<>();
var createEntries = response.getCreateEntries();
- int numRemoteEntry = MetricUtilities.ZERO;
- if (response.getRemoteCreateEntry() != null) {
- numRemoteEntry = MetricUtilities.UNIT;
- mCandidatePhasePerProviderMetric.addEntry(EntryEnum.REMOTE_ENTRY);
- }
- int numCreateEntries =
- createEntries == null ? MetricUtilities.ZERO : createEntries.size();
- if (numCreateEntries > MetricUtilities.ZERO) {
- createEntries.forEach(c ->
- mCandidatePhasePerProviderMetric.addEntry(EntryEnum.CREDENTIAL_ENTRY));
- }
- mCandidatePhasePerProviderMetric.setNumEntriesTotal(numCreateEntries + numRemoteEntry);
- mCandidatePhasePerProviderMetric.setRemoteEntryCount(numRemoteEntry);
- mCandidatePhasePerProviderMetric.setCredentialEntryCount(numCreateEntries);
- mCandidatePhasePerProviderMetric.setCredentialEntryTypeCount(MetricUtilities.UNIT);
+ int numRemoteEntry = response.getRemoteCreateEntry() != null ? MetricUtilities.ZERO :
+ MetricUtilities.UNIT;
+ int numCreateEntries = createEntries.size();
+ entryCounts.put(EntryEnum.REMOTE_ENTRY, numRemoteEntry);
+ entryCounts.put(EntryEnum.CREDENTIAL_ENTRY, numCreateEntries);
+
+ Map<String, Integer> responseCounts = new LinkedHashMap<>();
+ responseCounts.put(MetricUtilities.DEFAULT_STRING, numCreateEntries);
+ // We don't store create response because it's directly related to the request
+ // We do still store the count, however
+
+ ResponseCollective responseCollective = new ResponseCollective(responseCounts, entryCounts);
+ mCandidatePhasePerProviderMetric.setResponseCollective(responseCollective);
}
private void beginGetCredentialResponseCollectionCandidateEntryMetrics(
BeginGetCredentialResponse response) {
+ Map<EntryEnum, Integer> entryCounts = new LinkedHashMap<>();
+ Map<String, Integer> responseCounts = new LinkedHashMap<>();
int numCredEntries = response.getCredentialEntries().size();
int numActionEntries = response.getActions().size();
int numAuthEntries = response.getAuthenticationActions().size();
- int numRemoteEntry = MetricUtilities.ZERO;
- if (response.getRemoteCredentialEntry() != null) {
- numRemoteEntry = MetricUtilities.UNIT;
- mCandidatePhasePerProviderMetric.addEntry(EntryEnum.REMOTE_ENTRY);
- }
- response.getCredentialEntries().forEach(c ->
- mCandidatePhasePerProviderMetric.addEntry(EntryEnum.CREDENTIAL_ENTRY));
- response.getActions().forEach(c ->
- mCandidatePhasePerProviderMetric.addEntry(EntryEnum.ACTION_ENTRY));
- response.getAuthenticationActions().forEach(c ->
- mCandidatePhasePerProviderMetric.addEntry(EntryEnum.AUTHENTICATION_ENTRY));
- mCandidatePhasePerProviderMetric.setNumEntriesTotal(numCredEntries + numAuthEntries
- + numActionEntries + numRemoteEntry);
- mCandidatePhasePerProviderMetric.setCredentialEntryCount(numCredEntries);
- int numTypes = (response.getCredentialEntries().stream()
- .map(CredentialEntry::getType).collect(
- Collectors.toSet())).size(); // Dedupe type strings
- mCandidatePhasePerProviderMetric.setCredentialEntryTypeCount(numTypes);
- mCandidatePhasePerProviderMetric.setActionEntryCount(numActionEntries);
- mCandidatePhasePerProviderMetric.setAuthenticationEntryCount(numAuthEntries);
- mCandidatePhasePerProviderMetric.setRemoteEntryCount(numRemoteEntry);
+ int numRemoteEntry = response.getRemoteCredentialEntry() != null ? MetricUtilities.ZERO :
+ MetricUtilities.UNIT;
+ entryCounts.put(EntryEnum.REMOTE_ENTRY, numRemoteEntry);
+ entryCounts.put(EntryEnum.CREDENTIAL_ENTRY, numCredEntries);
+ entryCounts.put(EntryEnum.ACTION_ENTRY, numActionEntries);
+ entryCounts.put(EntryEnum.AUTHENTICATION_ENTRY, numAuthEntries);
+
+ response.getCredentialEntries().forEach(entry -> {
+ String entryKey = generateMetricKey(entry.getType(), DELTA_CUT);
+ responseCounts.put(entryKey, responseCounts.getOrDefault(entryKey, 0) + 1);
+ });
+
+ ResponseCollective responseCollective = new ResponseCollective(responseCounts, entryCounts);
+ mCandidatePhasePerProviderMetric.setResponseCollective(responseCollective);
}
}
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 547c09a..4624e0b 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
@@ -24,7 +24,7 @@
import android.credentials.GetCredentialRequest;
import android.credentials.ui.UserSelectionDialogResult;
import android.os.IBinder;
-import android.util.Log;
+import android.util.Slog;
import com.android.server.credentials.ProviderSession;
@@ -90,7 +90,7 @@
mInitialPhaseMetric.setCallerUid(mCallingUid);
mInitialPhaseMetric.setApiName(metricCode);
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.i(TAG, "Unexpected error collecting initial metrics: " + e);
}
}
@@ -103,7 +103,7 @@
try {
mChosenProviderFinalPhaseMetric.setUiReturned(uiReturned);
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.i(TAG, "Unexpected error collecting ui end time metric: " + e);
}
}
@@ -116,7 +116,7 @@
try {
mChosenProviderFinalPhaseMetric.setUiCallStartTimeNanoseconds(uiCallStartTime);
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.i(TAG, "Unexpected error collecting ui start metric: " + e);
}
}
@@ -132,7 +132,7 @@
mChosenProviderFinalPhaseMetric.setUiReturned(uiReturned);
mChosenProviderFinalPhaseMetric.setUiCallEndTimeNanoseconds(uiEndTimestamp);
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.i(TAG, "Unexpected error collecting ui response metric: " + e);
}
}
@@ -146,7 +146,7 @@
try {
mChosenProviderFinalPhaseMetric.setChosenProviderStatus(status);
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.i(TAG, "Unexpected error setting chosen provider status metric: " + e);
}
}
@@ -159,7 +159,7 @@
try {
mInitialPhaseMetric.setOriginSpecified(origin);
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.i(TAG, "Unexpected error collecting create flow metric: " + e);
}
}
@@ -175,7 +175,7 @@
uniqueRequestCounts.put(optionKey, uniqueRequestCounts.get(optionKey) + 1);
});
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during get request metric logging: " + e);
+ Slog.i(TAG, "Unexpected error during get request metric logging: " + e);
}
return uniqueRequestCounts;
}
@@ -190,7 +190,7 @@
mInitialPhaseMetric.setOriginSpecified(request.getOrigin() != null);
mInitialPhaseMetric.setRequestCounts(getRequestCountMap(request));
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.i(TAG, "Unexpected error collecting get flow metric: " + e);
}
}
@@ -213,7 +213,7 @@
browsingPhaseMetric.setProviderUid(selectedProviderPhaseMetric.getCandidateUid());
mCandidateBrowsingPhaseMetric.add(browsingPhaseMetric);
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.i(TAG, "Unexpected error collecting browsing metric: " + e);
}
}
@@ -226,7 +226,7 @@
try {
mChosenProviderFinalPhaseMetric.setHasException(exceptionBitFinalPhase);
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.i(TAG, "Unexpected error setting final exception metric: " + e);
}
}
@@ -244,7 +244,7 @@
mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
finalStatus.getMetricCode());
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.i(TAG, "Unexpected error during metric logging: " + e);
}
}
@@ -272,24 +272,11 @@
candidatePhaseMetric.getStartQueryTimeNanoseconds());
mChosenProviderFinalPhaseMetric.setQueryEndTimeNanoseconds(candidatePhaseMetric
.getQueryFinishTimeNanoseconds());
-
- mChosenProviderFinalPhaseMetric.setNumEntriesTotal(candidatePhaseMetric
- .getNumEntriesTotal());
- mChosenProviderFinalPhaseMetric.setCredentialEntryCount(candidatePhaseMetric
- .getCredentialEntryCount());
- mChosenProviderFinalPhaseMetric.setCredentialEntryTypeCount(
- candidatePhaseMetric.getCredentialEntryTypeCount());
- mChosenProviderFinalPhaseMetric.setActionEntryCount(candidatePhaseMetric
- .getActionEntryCount());
- mChosenProviderFinalPhaseMetric.setRemoteEntryCount(candidatePhaseMetric
- .getRemoteEntryCount());
- mChosenProviderFinalPhaseMetric.setAuthenticationEntryCount(
- candidatePhaseMetric.getAuthenticationEntryCount());
- mChosenProviderFinalPhaseMetric.setAvailableEntries(candidatePhaseMetric
- .getAvailableEntries());
+ mChosenProviderFinalPhaseMetric.setResponseCollective(
+ candidatePhaseMetric.getResponseCollective());
mChosenProviderFinalPhaseMetric.setFinalFinishTimeNanoseconds(System.nanoTime());
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.i(TAG, "Unexpected error during metric candidate to final transfer: " + e);
}
}
@@ -312,7 +299,7 @@
/* apiStatus */ ApiStatus.FAILURE.getMetricCode());
}
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.i(TAG, "Unexpected error during final metric failure emit: " + e);
}
}
@@ -326,7 +313,7 @@
try {
logApiCalledCandidatePhase(providers, ++mSequenceCounter, mInitialPhaseMetric);
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.i(TAG, "Unexpected error during candidate metric emit: " + e);
}
}
@@ -337,15 +324,11 @@
*/
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);
} catch (Exception e) {
- Log.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.i(TAG, "Unexpected error during final metric emit: " + e);
}
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/shared/ResponseCollective.java b/services/credentials/java/com/android/server/credentials/metrics/shared/ResponseCollective.java
new file mode 100644
index 0000000..fd785c2
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/shared/ResponseCollective.java
@@ -0,0 +1,115 @@
+/*
+ * 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.credentials.metrics.shared;
+
+import android.annotation.NonNull;
+
+import com.android.server.credentials.metrics.EntryEnum;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Some data is directly shared between the
+ * {@link com.android.server.credentials.metrics.CandidatePhaseMetric} and the
+ * {@link com.android.server.credentials.metrics.ChosenProviderFinalPhaseMetric}. This
+ * aims to create an abstraction that holds that information, to avoid duplication.
+ *
+ * This class should be immutable and threadsafe once generated.
+ */
+public class ResponseCollective {
+ /*
+ Abstract Function (responseCounts, entryCounts) -> A 'ResponseCollective' containing information
+ about a chosen or candidate providers available responses, be they entries or credentials.
+
+ RepInvariant: mResponseCounts and mEntryCounts are always initialized
+
+ Threadsafe and Immutability: Once generated, the maps remain unchangeable. The object is
+ threadsafe and immutable, and safe from external changes. This is threadsafe because it is
+ immutable after creation and only allows reads, not writes.
+ */
+
+ private static final String TAG = "ResponseCollective";
+
+ // Stores the deduped credential response information, eg {"response":5} for this provider
+ private final Map<String, Integer> mResponseCounts;
+ // Stores the deduped entry information, eg {ENTRY_ENUM:5} for this provider
+ private final Map<EntryEnum, Integer> mEntryCounts;
+
+ public ResponseCollective(@NonNull Map<String, Integer> responseCounts,
+ @NonNull Map<EntryEnum, Integer> entryCounts) {
+ mResponseCounts = responseCounts == null ? new LinkedHashMap<>() :
+ new LinkedHashMap<>(responseCounts);
+ mEntryCounts = entryCounts == null ? new LinkedHashMap<>() :
+ new LinkedHashMap<>(entryCounts);
+ }
+
+ /**
+ * Returns the unique, deduped, response classtypes for logging associated with this provider.
+ *
+ * @return a string array for deduped classtypes
+ */
+ public String[] getUniqueResponseStrings() {
+ String[] result = new String[mResponseCounts.keySet().size()];
+ mResponseCounts.keySet().toArray(result);
+ return result;
+ }
+
+ /**
+ * Returns the unique, deduped, response classtype counts for logging associated with this
+ * provider.
+ * @return a string array for deduped classtype counts
+ */
+ public int[] getUniqueResponseCounts() {
+ return mResponseCounts.values().stream().mapToInt(Integer::intValue).toArray();
+ }
+
+ /**
+ * Returns the unique, deduped, entry types for logging associated with this provider.
+ * @return an int array for deduped entries
+ */
+ public int[] getUniqueEntries() {
+ return mEntryCounts.keySet().stream().mapToInt(Enum::ordinal).toArray();
+ }
+
+ /**
+ * Returns the unique, deduped, entry classtype counts for logging associated with this
+ * provider.
+ * @return a string array for deduped classtype counts
+ */
+ public int[] getUniqueEntryCounts() {
+ return mEntryCounts.values().stream().mapToInt(Integer::intValue).toArray();
+ }
+
+ /**
+ * Given a specific {@link EntryEnum}, this provides us with the count of that entry within
+ * this particular provider.
+ * @param e the entry enum with which we want to know the count of
+ * @return a count of this particular entry enum stored by this provider
+ */
+ public int getCountForEntry(EntryEnum e) {
+ return mEntryCounts.get(e);
+ }
+
+ /**
+ * Indicates the total number of existing entries for this provider.
+ * @return a count of the total number of entries for this provider
+ */
+ public int getNumEntriesTotal() {
+ return mEntryCounts.values().stream().mapToInt(Integer::intValue).sum();
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index cf49dcf..0c4830a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -684,6 +684,38 @@
}
}
+ <V> void transferPolicies(EnforcingAdmin oldAdmin, EnforcingAdmin newAdmin) {
+ Set<PolicyKey> globalPolicies = new HashSet<>(mGlobalPolicies.keySet());
+ for (PolicyKey policy : globalPolicies) {
+ PolicyState<?> policyState = mGlobalPolicies.get(policy);
+ if (policyState.getPoliciesSetByAdmins().containsKey(oldAdmin)) {
+ PolicyDefinition<V> policyDefinition =
+ (PolicyDefinition<V>) policyState.getPolicyDefinition();
+ PolicyValue<V> policyValue =
+ (PolicyValue<V>) policyState.getPoliciesSetByAdmins().get(oldAdmin);
+ setGlobalPolicy(policyDefinition, newAdmin, policyValue);
+ }
+ }
+
+ for (int i = 0; i < mLocalPolicies.size(); i++) {
+ int userId = mLocalPolicies.keyAt(i);
+ Set<PolicyKey> localPolicies = new HashSet<>(
+ mLocalPolicies.get(userId).keySet());
+ for (PolicyKey policy : localPolicies) {
+ PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy);
+ if (policyState.getPoliciesSetByAdmins().containsKey(oldAdmin)) {
+ PolicyDefinition<V> policyDefinition =
+ (PolicyDefinition<V>) policyState.getPolicyDefinition();
+ PolicyValue<V> policyValue =
+ (PolicyValue<V>) policyState.getPoliciesSetByAdmins().get(oldAdmin);
+ setLocalPolicy(policyDefinition, newAdmin, policyValue, userId);
+ }
+ }
+ }
+
+ removePoliciesForAdmin(oldAdmin);
+ }
+
private Set<UserRestrictionPolicyKey> getUserRestrictionPolicyKeysForAdminLocked(
Map<PolicyKey, PolicyState<?>> policies,
EnforcingAdmin admin) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 231fee3..3578b16 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_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 @@
// 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";
@@ -3881,6 +3882,17 @@
final ActiveAdmin adminToTransfer = policy.mAdminMap.get(outgoingReceiver);
final int oldAdminUid = adminToTransfer.getUid();
+ if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) {
+ EnforcingAdmin oldAdmin =
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ outgoingReceiver, userHandle, adminToTransfer);
+ EnforcingAdmin newAdmin =
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ incomingReceiver, userHandle, adminToTransfer);
+
+ mDevicePolicyEngine.transferPolicies(oldAdmin, newAdmin);
+ }
+
adminToTransfer.transfer(incomingDeviceInfo);
policy.mAdminMap.remove(outgoingReceiver);
policy.mAdminMap.put(incomingReceiver, adminToTransfer);
@@ -6050,7 +6062,7 @@
@Override
public void lockNow(int flags, String callerPackageName, boolean parent) {
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
caller = getCallerIdentity(callerPackageName);
} else {
caller = getCallerIdentity();
@@ -6062,7 +6074,7 @@
ActiveAdmin admin;
// Make sure the caller has any active admin with the right policy or
// the required permission.
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
admin = enforcePermissionAndGetEnforcingAdmin(
/* admin= */ null,
/* permission= */ MANAGE_DEVICE_POLICY_LOCK,
@@ -8916,13 +8928,13 @@
}
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
caller = getCallerIdentity(who, callerPackageName);
} else {
caller = getCallerIdentity(who);
}
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
// The effect of this policy is device-wide.
enforcePermission(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL);
} else {
@@ -8950,13 +8962,13 @@
return false;
}
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
caller = getCallerIdentity(who, callerPackageName);
} else {
caller = getCallerIdentity(who);
}
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
enforceCanQuery(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL);
} else {
Objects.requireNonNull(who, "ComponentName is null");
@@ -8979,13 +8991,13 @@
}
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
caller = getCallerIdentity(who, callerPackageName);
} else {
caller = getCallerIdentity(who);
}
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
// The effect of this policy is device-wide.
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
who,
@@ -9025,13 +9037,13 @@
}
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
caller = getCallerIdentity(who, callerPackageName);
} else {
caller = getCallerIdentity(who);
}
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
// The effect of this policy is device-wide.
enforceCanQuery(SET_TIME_ZONE, caller.getPackageName(), UserHandle.USER_ALL);
} else {
@@ -9334,7 +9346,7 @@
}
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
caller = getCallerIdentity(who, callerPackageName);
} else {
caller = getCallerIdentity(who);
@@ -9344,7 +9356,7 @@
final int userHandle = caller.getUserId();
int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
synchronized (getLockObject()) {
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
// SUPPORT USES_POLICY_DISABLE_KEYGUARD_FEATURES
EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
who, MANAGE_DEVICE_POLICY_KEYGUARD, caller.getPackageName(),
@@ -9423,13 +9435,14 @@
synchronized (getLockObject()) {
if (who != null) {
- if (isPermissionCheckFlagEnabled()) {
- EnforcingAdmin admin = getEnforcingAdminForCaller(
- who, who.getPackageName());
+ if (isUnicornFlagEnabled()) {
+ EnforcingAdmin admin = getEnforcingAdminForPackage(
+ who, who.getPackageName(), userHandle);
Integer features = mDevicePolicyEngine.getLocalPolicySetByAdmin(
PolicyDefinition.KEYGUARD_DISABLED_FEATURES,
admin,
affectedUserId);
+
return features == null ? 0 : features;
} else {
ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
@@ -9437,7 +9450,7 @@
}
}
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
Integer features = mDevicePolicyEngine.getResolvedPolicy(
PolicyDefinition.KEYGUARD_DISABLED_FEATURES,
affectedUserId);
@@ -9998,10 +10011,6 @@
"clearDeviceOwner can only be called by the device owner");
}
enforceUserUnlocked(deviceOwnerUserId);
- DevicePolicyData policy = getUserData(deviceOwnerUserId);
- if (policy.mPasswordTokenHandle != 0) {
- mLockPatternUtils.removeEscrowToken(policy.mPasswordTokenHandle, deviceOwnerUserId);
- }
final ActiveAdmin admin = getDeviceOwnerAdminLocked();
mInjector.binderWithCleanCallingIdentity(() -> {
@@ -10056,6 +10065,10 @@
}
final DevicePolicyData policyData = getUserData(userId);
policyData.mCurrentInputMethodSet = false;
+ if (policyData.mPasswordTokenHandle != 0) {
+ mLockPatternUtils.removeEscrowToken(policyData.mPasswordTokenHandle, userId);
+ policyData.mPasswordTokenHandle = 0;
+ }
saveSettingsLocked(userId);
mPolicyCache.onUserRemoved(userId);
final DevicePolicyData systemPolicyData = getUserData(UserHandle.USER_SYSTEM);
@@ -10747,7 +10760,9 @@
@VisibleForTesting
boolean hasDeviceIdAccessUnchecked(String packageName, int uid) {
final int userId = UserHandle.getUserId(uid);
- if (isPermissionCheckFlagEnabled()) {
+ // TODO(b/280048070): Introduce a permission to handle device ID access
+ if (isPermissionCheckFlagEnabled()
+ && !(isUidProfileOwnerLocked(uid) || isUidDeviceOwnerLocked(uid))) {
return hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, packageName, userId);
} else {
ComponentName deviceOwner = getDeviceOwnerComponent(true);
@@ -11634,7 +11649,7 @@
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS);
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
who,
MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
@@ -12195,7 +12210,7 @@
}
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
caller = getCallerIdentity(who, callerPackageName);
} else {
caller = getCallerIdentity(who);
@@ -12205,7 +12220,7 @@
int userId = getProfileParentUserIfRequested(
caller.getUserId(), calledOnParentInstance);
if (calledOnParentInstance) {
- if (!isPermissionCheckFlagEnabled()) {
+ if (!isPolicyEngineForFinanceFlagEnabled()) {
Preconditions.checkCallAuthorization(
isProfileOwnerOfOrganizationOwnedDevice(caller));
}
@@ -12213,7 +12228,7 @@
"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 +12256,9 @@
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,
@@ -13059,7 +13076,7 @@
String packageName) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin(
who,
MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
@@ -13129,7 +13146,7 @@
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
ActiveAdmin admin;
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
who,
MANAGE_DEVICE_POLICY_PACKAGE_STATE,
@@ -13226,7 +13243,7 @@
public boolean isPackageSuspended(ComponentName who, String callerPackage, String packageName) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
enforcePermission(
MANAGE_DEVICE_POLICY_PACKAGE_STATE,
caller.getPackageName(),
@@ -13436,6 +13453,13 @@
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 +13476,6 @@
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);
@@ -13832,7 +13849,7 @@
boolean hidden, boolean parent) {
CallerIdentity caller = getCallerIdentity(who, callerPackage);
final int userId = parent ? getProfileParentId(caller.getUserId()) : caller.getUserId();
- if (isPermissionCheckFlagEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
// TODO: We need to ensure the delegate with DELEGATION_PACKAGE_ACCESS can do this
enforcePermission(MANAGE_DEVICE_POLICY_PACKAGE_STATE, caller.getPackageName(), userId);
} else {
@@ -13851,7 +13868,7 @@
boolean result;
synchronized (getLockObject()) {
if (parent) {
- if (!isPermissionCheckFlagEnabled()) {
+ if (!isPolicyEngineForFinanceFlagEnabled()) {
Preconditions.checkCallAuthorization(
isProfileOwnerOfOrganizationOwnedDevice(
caller.getUserId()) && isManagedProfile(caller.getUserId()));
@@ -13868,7 +13885,7 @@
Slogf.v(LOG_TAG, "calling pm.setApplicationHiddenSettingAsUser(%s, %b, %d)",
packageName, hidden, userId);
}
- if (isPermissionCheckFlagEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackage);
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.APPLICATION_HIDDEN(packageName),
@@ -13907,7 +13924,7 @@
String packageName, boolean parent) {
CallerIdentity caller = getCallerIdentity(who, callerPackage);
int userId = parent ? getProfileParentId(caller.getUserId()) : caller.getUserId();
- if (isPermissionCheckFlagEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
// TODO: Also support DELEGATION_PACKAGE_ACCESS
enforcePermission(MANAGE_DEVICE_POLICY_PACKAGE_STATE, caller.getPackageName(), userId);
} else {
@@ -13919,7 +13936,7 @@
synchronized (getLockObject()) {
if (parent) {
- if (!isPermissionCheckFlagEnabled()) {
+ if (!isPolicyEngineForFinanceFlagEnabled()) {
Preconditions.checkCallAuthorization(
isProfileOwnerOfOrganizationOwnedDevice(caller.getUserId())
&& isManagedProfile(caller.getUserId()));
@@ -14111,13 +14128,13 @@
enforceMaxStringLength(accountType, "account type");
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
caller = getCallerIdentity(who, callerPackageName);
} else {
caller = getCallerIdentity(who);
}
synchronized (getLockObject()) {
- if (isPermissionCheckFlagEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
int affectedUser = getAffectedUser(parent);
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
who,
@@ -14180,7 +14197,7 @@
CallerIdentity caller;
Preconditions.checkArgumentNonnegative(userId, "Invalid userId");
final ArraySet<String> resultSet = new ArraySet<>();
- if (isPermissionCheckFlagEnabled()) {
+ if (isPolicyEngineForFinanceFlagEnabled()) {
int affectedUser = parent ? getProfileParentId(userId) : userId;
caller = getCallerIdentity(callerPackageName);
if (!hasPermission(MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT,
@@ -15551,12 +15568,12 @@
public boolean setStatusBarDisabled(ComponentName who, String callerPackageName,
boolean disabled) {
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
caller = getCallerIdentity(who, callerPackageName);
} else {
caller = getCallerIdentity(who);
}
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
enforcePermission(MANAGE_DEVICE_POLICY_STATUS_BAR, caller.getPackageName(),
UserHandle.USER_ALL);
} else {
@@ -15567,7 +15584,7 @@
int userId = caller.getUserId();
synchronized (getLockObject()) {
- if (!isPermissionCheckFlagEnabled()) {
+ if (!isUnicornFlagEnabled()) {
Preconditions.checkCallAuthorization(isUserAffiliatedWithDeviceLocked(userId),
"Admin " + who + " is neither the device owner or affiliated "
+ "user's profile owner.");
@@ -15626,7 +15643,7 @@
@Override
public boolean isStatusBarDisabled(String callerPackage) {
final CallerIdentity caller = getCallerIdentity(callerPackage);
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
enforceCanQuery(
MANAGE_DEVICE_POLICY_STATUS_BAR, caller.getPackageName(), caller.getUserId());
} else {
@@ -15636,7 +15653,7 @@
int userId = caller.getUserId();
synchronized (getLockObject()) {
- if (!isPermissionCheckFlagEnabled()) {
+ if (!isUnicornFlagEnabled()) {
Preconditions.checkCallAuthorization(isUserAffiliatedWithDeviceLocked(userId),
"Admin " + callerPackage
+ " is neither the device owner or affiliated user's profile owner.");
@@ -16371,7 +16388,8 @@
// 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;
@@ -16796,7 +16814,7 @@
}
}
EnforcingAdmin enforcingAdmin;
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
admin,
MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
@@ -16967,7 +16985,7 @@
public int getPermissionGrantState(ComponentName admin, String callerPackage,
String packageName, String permission) throws RemoteException {
final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
enforceCanQuery(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, caller.getPackageName(),
caller.getUserId());
} else {
@@ -19105,14 +19123,14 @@
throw new IllegalArgumentException("token must be at least 32-byte long");
}
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
caller = getCallerIdentity(admin, callerPackageName);
} else {
caller = getCallerIdentity(admin);
}
final int userId = caller.getUserId();
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
admin,
MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -19168,7 +19186,7 @@
return false;
}
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
caller = getCallerIdentity(admin, callerPackageName);
} else {
caller = getCallerIdentity(admin);
@@ -19176,7 +19194,7 @@
final int userId = caller.getUserId();
boolean result = false;
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
admin,
MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -19215,14 +19233,14 @@
return false;
}
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
caller = getCallerIdentity(admin, callerPackageName);
} else {
caller = getCallerIdentity(admin);
}
int userId = caller.getUserId();
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
admin,
MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -19264,7 +19282,7 @@
Objects.requireNonNull(token);
CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
caller = getCallerIdentity(admin, callerPackageName);
} else {
caller = getCallerIdentity(admin);
@@ -19274,7 +19292,7 @@
boolean result = false;
final String password = passwordOrNull != null ? passwordOrNull : "";
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
admin,
MANAGE_DEVICE_POLICY_RESET_PASSWORD,
@@ -19305,7 +19323,7 @@
}
if (result) {
- if (isPermissionCheckFlagEnabled()) {
+ if (isUnicornFlagEnabled()) {
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.RESET_PASSWORD_WITH_TOKEN)
.setAdmin(callerPackageName)
@@ -22839,6 +22857,7 @@
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,15 +22933,18 @@
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,
MANAGE_DEVICE_POLICY_LOCATION,
MANAGE_DEVICE_POLICY_LOCK,
MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
+ MANAGE_DEVICE_POLICY_CERTIFICATES,
MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION,
MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
MANAGE_DEVICE_POLICY_PACKAGE_STATE,
@@ -22949,7 +22971,6 @@
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 +23095,12 @@
//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 =
@@ -23559,6 +23581,30 @@
return EnforcingAdmin.createEnforcingAdmin(caller.getPackageName(), userId, admin);
}
+ private EnforcingAdmin getEnforcingAdminForPackage(@Nullable ComponentName who,
+ String packageName, int userId) {
+ ActiveAdmin admin;
+ if (who != null) {
+ if (isDeviceOwner(who, userId) || isProfileOwner(who, userId)) {
+ synchronized (getLockObject()) {
+ admin = getActiveAdminUncheckedLocked(who, userId);
+ }
+ if (admin != null) {
+ return EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userId, admin);
+ }
+ } else {
+ // Check for non-DPC active admins.
+ admin = getActiveAdminUncheckedLocked(who, userId);
+ if (admin != null) {
+ return EnforcingAdmin.createDeviceAdminEnforcingAdmin(who, userId, admin);
+ }
+ }
+ }
+
+ admin = getUserData(userId).createOrGetPermissionBasedAdmin(userId);
+ return EnforcingAdmin.createEnforcingAdmin(packageName, userId, admin);
+ }
+
private int getAffectedUser(boolean calledOnParent) {
int callingUserId = mInjector.userHandleGetCallingUserId();
return calledOnParent ? getProfileParentId(callingUserId) : callingUserId;
@@ -23614,6 +23660,10 @@
DEFAULT_KEEP_PROFILES_RUNNING_FLAG);
}
+ private boolean isUnicornFlagEnabled() {
+ return false;
+ }
+
private boolean isWorkProfileTelephonyEnabled() {
return isWorkProfileTelephonyDevicePolicyManagerFlagEnabled()
&& isWorkProfileTelephonySubscriptionManagerFlagEnabled();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b1d6131..a8a1c03 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -593,8 +593,8 @@
* Spawn a thread that monitors for fd leaks.
*/
private static void spawnFdLeakCheckThread() {
- final int enableThreshold = SystemProperties.getInt(SYSPROP_FDTRACK_ENABLE_THRESHOLD, 1024);
- final int abortThreshold = SystemProperties.getInt(SYSPROP_FDTRACK_ABORT_THRESHOLD, 2048);
+ final int enableThreshold = SystemProperties.getInt(SYSPROP_FDTRACK_ENABLE_THRESHOLD, 1600);
+ final int abortThreshold = SystemProperties.getInt(SYSPROP_FDTRACK_ABORT_THRESHOLD, 3000);
final int checkInterval = SystemProperties.getInt(SYSPROP_FDTRACK_INTERVAL, 120);
new Thread(() -> {
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index f05b1d4..475966e 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -20,9 +20,11 @@
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.os.Handler;
import android.os.IBinder.DeathRecipient;
import android.os.Looper;
@@ -53,7 +55,8 @@
public static final String LOG_TAG = "ProfcollectForwardingService";
private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
-
+ private static final String INTENT_UPLOAD_PROFILES =
+ "com.android.server.profcollect.UPLOAD_PROFILES";
private static final long BG_PROCESS_PERIOD = TimeUnit.HOURS.toMillis(4); // every 4 hours.
private IProfCollectd mIProfcollect;
@@ -66,6 +69,16 @@
}
};
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction() == INTENT_UPLOAD_PROFILES) {
+ Log.d(LOG_TAG, "Received broadcast to pack and upload reports");
+ packAndUploadReport();
+ }
+ }
+ };
+
public ProfcollectForwardingService(Context context) {
super(context);
@@ -73,6 +86,10 @@
throw new AssertionError("only one service instance allowed");
}
sSelfService = this;
+
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(INTENT_UPLOAD_PROFILES);
+ context.registerReceiver(mBroadcastReceiver, filter);
}
/**
@@ -296,7 +313,7 @@
}
if (status == UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT) {
- packProfileReport();
+ packAndUploadReport();
}
}
@@ -307,7 +324,7 @@
});
}
- private void packProfileReport() {
+ private void packAndUploadReport() {
if (mIProfcollect == null) {
return;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index 3a47b47..2b57c59 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 @@
// Freeze time for testing.
long nowElapsed;
boolean useMotionSensor = true;
+ boolean isLocationPrefetchEnabled = true;
InjectorForTest(Context ctx) {
super(ctx);
@@ -223,6 +224,11 @@
}
@Override
+ boolean isLocationPrefetchEnabled() {
+ return isLocationPrefetchEnabled;
+ }
+
+ @Override
PowerManager getPowerManager() {
return mPowerManager;
}
@@ -991,6 +997,43 @@
}
@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 @@
}
@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 17fba9f..0000000
--- 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/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
index 941a3a4..3faf394 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
@@ -72,18 +72,28 @@
mResources = InstrumentationRegistry.getContext().getResources();
// These Resources are common to all tests.
- doReturn(mResources.getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMin))
+ doReturn(4000)
.when(mMockedResources)
.getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMin);
- doReturn(mResources.getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMax))
+ doReturn(8000)
.when(mMockedResources)
.getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMax);
- doReturn(mResources.getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault))
+ doReturn(6500)
.when(mMockedResources)
.getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault);
- doReturn(mResources.getStringArray(R.array.config_displayWhiteBalanceDisplayNominalWhite))
- .when(mMockedResources)
- .getStringArray(R.array.config_displayWhiteBalanceDisplayNominalWhite);
+ doReturn(new String[] {"0.950456", "1.000000", "1.089058"})
+ .when(mMockedResources)
+ .getStringArray(R.array.config_displayWhiteBalanceDisplayNominalWhite);
+ doReturn(6500)
+ .when(mMockedResources)
+ .getInteger(R.integer.config_displayWhiteBalanceDisplayNominalWhiteCct);
+ doReturn(new int[] {0})
+ .when(mMockedResources)
+ .getIntArray(R.array.config_displayWhiteBalanceDisplaySteps);
+ doReturn(new int[] {20})
+ .when(mMockedResources)
+ .getIntArray(R.array.config_displayWhiteBalanceDisplayRangeMinimums);
+
doReturn(mMockedResources).when(mMockedContext).getResources();
mDisplayToken = new Binder();
@@ -195,7 +205,7 @@
* Matrix should match the precalculated one for given cct and display primaries.
*/
@Test
- public void displayWhiteBalance_validateTransformMatrix() {
+ public void displayWhiteBalance_getAndSetMatrix_validateTransformMatrix() {
DisplayPrimaries displayPrimaries = new DisplayPrimaries();
displayPrimaries.red = new CieXyz();
displayPrimaries.red.X = 0.412315f;
@@ -223,10 +233,12 @@
final int cct = 6500;
mDisplayWhiteBalanceTintController.setMatrix(cct);
+ mDisplayWhiteBalanceTintController.setAppliedCct(
+ mDisplayWhiteBalanceTintController.getTargetCct());
+
assertWithMessage("Failed to set temperature")
.that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
.isEqualTo(cct);
-
float[] matrixDwb = mDisplayWhiteBalanceTintController.getMatrix();
final float[] expectedMatrixDwb = {
0.971848f, -0.001421f, 0.000491f, 0.0f,
@@ -238,6 +250,54 @@
1e-6f /* tolerance */);
}
+ /**
+ * Matrix should match the precalculated one for given cct and display primaries.
+ */
+ @Test
+ public void displayWhiteBalance_targetApplied_validateTransformMatrix() {
+ DisplayPrimaries displayPrimaries = new DisplayPrimaries();
+ displayPrimaries.red = new CieXyz();
+ displayPrimaries.red.X = 0.412315f;
+ displayPrimaries.red.Y = 0.212600f;
+ displayPrimaries.red.Z = 0.019327f;
+ displayPrimaries.green = new CieXyz();
+ displayPrimaries.green.X = 0.357600f;
+ displayPrimaries.green.Y = 0.715200f;
+ displayPrimaries.green.Z = 0.119200f;
+ displayPrimaries.blue = new CieXyz();
+ displayPrimaries.blue.X = 0.180500f;
+ displayPrimaries.blue.Y = 0.072200f;
+ displayPrimaries.blue.Z = 0.950633f;
+ displayPrimaries.white = new CieXyz();
+ displayPrimaries.white.X = 0.950456f;
+ displayPrimaries.white.Y = 1.000000f;
+ displayPrimaries.white.Z = 1.089058f;
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+ .thenReturn(displayPrimaries);
+
+ setUpTintController();
+ assertWithMessage("Setup with valid SurfaceControl failed")
+ .that(mDisplayWhiteBalanceTintController.mSetUp)
+ .isTrue();
+
+ final int cct = 6500;
+ mDisplayWhiteBalanceTintController.setTargetCct(cct);
+ final float[] matrixDwb = mDisplayWhiteBalanceTintController.computeMatrixForCct(cct);
+ mDisplayWhiteBalanceTintController.setAppliedCct(cct);
+
+ assertWithMessage("Failed to set temperature")
+ .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
+ .isEqualTo(cct);
+ final float[] expectedMatrixDwb = {
+ 0.971848f, -0.001421f, 0.000491f, 0.0f,
+ 0.028193f, 0.945798f, 0.003207f, 0.0f,
+ -0.000042f, -0.000989f, 0.988659f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f
+ };
+ assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
+ 1e-6f /* tolerance */);
+ }
+
private void setUpTintController() {
mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController(
mDisplayManagerInternal);
diff --git a/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java
index f788c92..46974cf7 100644
--- a/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java
@@ -28,6 +28,8 @@
import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static com.android.server.am.ProcessList.UNKNOWN_ADJ;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
@@ -81,7 +83,7 @@
public void testEnqueueUidChange() {
int change = mUidObserverController.enqueueUidChange(null, TEST_UID1,
UidRecord.CHANGE_ACTIVE, PROCESS_STATE_FOREGROUND_SERVICE,
- PROCESS_CAPABILITY_ALL, 0, false);
+ UNKNOWN_ADJ, PROCESS_CAPABILITY_ALL, 0, false);
assertEquals("expected=ACTIVE,actual=" + changeToStr(change),
UidRecord.CHANGE_ACTIVE, change);
assertPendingChange(TEST_UID1, UidRecord.CHANGE_ACTIVE, PROCESS_STATE_FOREGROUND_SERVICE,
@@ -91,8 +93,8 @@
final ChangeRecord record2 = new ChangeRecord();
change = mUidObserverController.enqueueUidChange(record2, TEST_UID2,
- UidRecord.CHANGE_CACHED, PROCESS_STATE_CACHED_RECENT, PROCESS_CAPABILITY_NONE,
- 99, true);
+ UidRecord.CHANGE_CACHED, PROCESS_STATE_CACHED_RECENT, UNKNOWN_ADJ,
+ PROCESS_CAPABILITY_NONE, 99, true);
assertEquals("expected=ACTIVE,actual=" + changeToStr(change),
UidRecord.CHANGE_CACHED, change);
assertPendingChange(TEST_UID1, UidRecord.CHANGE_ACTIVE, PROCESS_STATE_FOREGROUND_SERVICE,
@@ -101,7 +103,8 @@
PROCESS_CAPABILITY_NONE, 99, true, record2);
change = mUidObserverController.enqueueUidChange(record1, TEST_UID1,
- UidRecord.CHANGE_UNCACHED, PROCESS_STATE_TOP, PROCESS_CAPABILITY_ALL, 0, false);
+ UidRecord.CHANGE_UNCACHED, PROCESS_STATE_TOP, UNKNOWN_ADJ,
+ PROCESS_CAPABILITY_ALL, 0, false);
assertEquals("expected=ACTIVE|UNCACHED,actual=" + changeToStr(change),
UidRecord.CHANGE_ACTIVE | UidRecord.CHANGE_UNCACHED, change);
assertPendingChange(TEST_UID1, UidRecord.CHANGE_ACTIVE | UidRecord.CHANGE_UNCACHED,
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index d12741a..317fd58 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -100,6 +100,7 @@
import com.android.server.FgThread;
import com.android.server.SystemService;
import com.android.server.am.UserState.KeyEvictedCallback;
+import com.android.server.pm.UserJourneyLogger;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerService;
import com.android.server.wm.WindowManagerService;
@@ -199,6 +200,7 @@
mUserController.setAllowUserUnlocking(true);
setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS);
setUpUser(TEST_PRE_CREATED_USER_ID, NO_USERINFO_FLAGS, /* preCreated= */ true, null);
+ mInjector.mRelevantUser = null;
});
}
@@ -232,6 +234,25 @@
}
@Test
+ public void testStartUser_background_duringBootHsum() {
+ mockIsHeadlessSystemUserMode(true);
+ mUserController.setAllowUserUnlocking(false);
+ mInjector.mRelevantUser = TEST_USER_ID;
+ boolean started = mUserController.startUser(TEST_USER_ID, USER_START_MODE_BACKGROUND);
+ assertWithMessage("startUser(%s, foreground=false)", TEST_USER_ID).that(started).isTrue();
+
+ // ACTION_LOCKED_BOOT_COMPLETED not sent yet
+ startUserAssertions(newArrayList(Intent.ACTION_USER_STARTED, Intent.ACTION_USER_STARTING),
+ START_BACKGROUND_USER_MESSAGE_CODES);
+
+ mUserController.onBootComplete(null);
+
+ startUserAssertions(newArrayList(Intent.ACTION_USER_STARTED, Intent.ACTION_USER_STARTING,
+ Intent.ACTION_LOCKED_BOOT_COMPLETED),
+ START_BACKGROUND_USER_MESSAGE_CODES);
+ }
+
+ @Test
public void testStartUser_sendsNoBroadcastsForSystemUserInNonHeadlessMode() {
setUpUser(SYSTEM_USER_ID, UserInfo.FLAG_SYSTEM, /* preCreated= */ false,
UserManager.USER_TYPE_FULL_SYSTEM);
@@ -1074,8 +1095,12 @@
private final KeyguardManager mKeyguardManagerMock;
private final LockPatternUtils mLockPatternUtilsMock;
+ private final UserJourneyLogger mUserJourneyLoggerMock;
+
private final Context mCtx;
+ private Integer mRelevantUser;
+
TestInjector(Context ctx) {
super(null);
mCtx = ctx;
@@ -1090,6 +1115,7 @@
mKeyguardManagerMock = mock(KeyguardManager.class);
when(mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true);
mLockPatternUtilsMock = mock(LockPatternUtils.class);
+ mUserJourneyLoggerMock = mock(UserJourneyLogger.class);
}
@Override
@@ -1162,7 +1188,9 @@
boolean sticky, int callingPid, int callingUid, int realCallingUid,
int realCallingPid, int userId) {
Log.i(TAG, "broadcastIntentLocked " + intent);
- mSentIntents.add(intent);
+ if (mRelevantUser == null || mRelevantUser == userId || userId == UserHandle.USER_ALL) {
+ mSentIntents.add(intent);
+ }
return 0;
}
@@ -1220,6 +1248,11 @@
void onSystemUserVisibilityChanged(boolean visible) {
Log.i(TAG, "onSystemUserVisibilityChanged(" + visible + ")");
}
+
+ @Override
+ protected UserJourneyLogger getUserJourneyLogger() {
+ return mUserJourneyLoggerMock;
+ }
}
private static class TestHandler extends Handler {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorListTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorListTest.java
new file mode 100644
index 0000000..3d80916b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorListTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.biometrics.sensors;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.app.IActivityManager;
+import android.app.SynchronousUserSwitchObserver;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.sensors.face.aidl.Sensor;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class SensorListTest {
+ @Rule
+ public MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Mock
+ Sensor mSensor;
+ @Mock
+ IActivityManager mActivityManager;
+ @Mock
+ SynchronousUserSwitchObserver mUserSwitchObserver;
+
+ SensorList<Sensor> mSensorList;
+
+ @Before
+ public void setUp() throws RemoteException {
+ mSensorList = new SensorList<>(mActivityManager);
+ }
+
+ @Test
+ public void testAddingSensor() throws RemoteException {
+ mSensorList.addSensor(0, mSensor, UserHandle.USER_NULL, mUserSwitchObserver);
+
+ verify(mUserSwitchObserver).onUserSwitching(UserHandle.USER_SYSTEM);
+ verify(mActivityManager).registerUserSwitchObserver(eq(mUserSwitchObserver), anyString());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 41f7433..31a58cd 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.face.aidl;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -30,6 +32,7 @@
import android.hardware.biometrics.face.ISession;
import android.hardware.biometrics.face.SensorProps;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
@@ -38,6 +41,7 @@
import androidx.test.filters.SmallTest;
import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -98,16 +102,34 @@
mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext);
}
+ @Test
+ public void testAddingSensors() {
+ waitForIdle();
+
+ for (SensorProps prop : mSensorProps) {
+ final BiometricScheduler scheduler =
+ mFaceProvider.mFaceSensors.get(prop.commonProps.sensorId)
+ .getScheduler();
+ BaseClientMonitor currentClient = scheduler.getCurrentClient();
+
+ assertThat(currentClient).isInstanceOf(FaceInternalCleanupClient.class);
+ assertThat(currentClient.getSensorId()).isEqualTo(prop.commonProps.sensorId);
+ assertThat(currentClient.getTargetUserId()).isEqualTo(UserHandle.USER_SYSTEM);
+ }
+ }
+
@SuppressWarnings("rawtypes")
@Test
public void halServiceDied_resetsAllSchedulers() {
+ waitForIdle();
+
assertEquals(mSensorProps.length, mFaceProvider.getSensorProperties().size());
// Schedule N operations on each sensor
final int numFakeOperations = 10;
for (SensorProps prop : mSensorProps) {
final BiometricScheduler scheduler =
- mFaceProvider.mSensors.get(prop.commonProps.sensorId).getScheduler();
+ mFaceProvider.mFaceSensors.get(prop.commonProps.sensorId).getScheduler();
for (int i = 0; i < numFakeOperations; i++) {
final HalClientMonitor testMonitor = mock(HalClientMonitor.class);
when(testMonitor.getFreshDaemon()).thenReturn(new Object());
@@ -119,8 +141,8 @@
// The right amount of pending and current operations are scheduled
for (SensorProps prop : mSensorProps) {
final BiometricScheduler scheduler =
- mFaceProvider.mSensors.get(prop.commonProps.sensorId).getScheduler();
- assertEquals(numFakeOperations - 1, scheduler.getCurrentPendingCount());
+ mFaceProvider.mFaceSensors.get(prop.commonProps.sensorId).getScheduler();
+ assertEquals(numFakeOperations, scheduler.getCurrentPendingCount());
assertNotNull(scheduler.getCurrentClient());
}
@@ -132,7 +154,7 @@
// No pending operations, no current operation.
for (SensorProps prop : mSensorProps) {
final BiometricScheduler scheduler =
- mFaceProvider.mSensors.get(prop.commonProps.sensorId).getScheduler();
+ mFaceProvider.mFaceSensors.get(prop.commonProps.sensorId).getScheduler();
assertNull(scheduler.getCurrentClient());
assertEquals(0, scheduler.getCurrentPendingCount());
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index 9c9d3f8..25bd9bc 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -29,9 +29,9 @@
import android.content.Context;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.common.CommonProps;
-import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.ISession;
import android.hardware.biometrics.face.SensorProps;
+import android.hardware.face.FaceSensorPropertiesInternal;
import android.os.Handler;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -42,7 +42,6 @@
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.BiometricScheduler;
-import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
@@ -82,17 +81,13 @@
@Mock
private AuthSessionCoordinator mAuthSessionCoordinator;
@Mock
- private IFace mDaemon;
- @Mock
- private BiometricStateCallback mBiometricStateCallback;
+ FaceProvider mFaceProvider;
private final TestLooper mLooper = new TestLooper();
private final LockoutCache mLockoutCache = new LockoutCache();
private UserAwareBiometricScheduler mScheduler;
private Sensor.HalSessionCallback mHalCallback;
- private FaceProvider mFaceProvider;
- private SensorProps[] mSensorProps;
@Before
public void setUp() {
@@ -113,16 +108,6 @@
TAG, mScheduler, SENSOR_ID,
USER_ID, mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
mHalSessionCallback);
-
- final SensorProps sensor1 = new SensorProps();
- sensor1.commonProps = new CommonProps();
- sensor1.commonProps.sensorId = 0;
- final SensorProps sensor2 = new SensorProps();
- sensor2.commonProps = new CommonProps();
- sensor2.commonProps.sensorId = 1;
- mSensorProps = new SensorProps[]{sensor1, sensor2};
- mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback,
- mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext);
}
@Test
@@ -154,14 +139,26 @@
@Test
public void onBinderDied_noErrorOnNullClient() {
- mScheduler.reset();
- assertNull(mScheduler.getCurrentClient());
- mFaceProvider.binderDied();
+ mLooper.dispatchAll();
- for (int i = 0; i < mFaceProvider.mSensors.size(); i++) {
- final Sensor sensor = mFaceProvider.mSensors.valueAt(i);
- assertNull(sensor.getSessionForUser(USER_ID));
- }
+ final SensorProps sensorProps = new SensorProps();
+ sensorProps.commonProps = new CommonProps();
+ sensorProps.commonProps.sensorId = 1;
+ final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
+ sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength,
+ sensorProps.commonProps.maxEnrollmentsPerUser, null,
+ sensorProps.sensorType, sensorProps.supportsDetectInteraction,
+ sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
+ final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null,
+ internalProp, mLockoutResetDispatcher, mBiometricContext);
+
+ mScheduler.reset();
+
+ assertNull(mScheduler.getCurrentClient());
+
+ sensor.onBinderDied();
+
+ assertNull(sensor.getSessionForUser(USER_ID));
}
private void verifyNotLocked() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index c6ddf27..9c01de6 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -33,6 +35,7 @@
import android.hardware.biometrics.fingerprint.SensorLocation;
import android.hardware.biometrics.fingerprint.SensorProps;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
@@ -41,6 +44,7 @@
import androidx.test.filters.SmallTest;
import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -111,16 +115,38 @@
mGestureAvailabilityDispatcher, mBiometricContext);
}
+ @Test
+ public void testAddingSensors() {
+ mFingerprintProvider = new TestableFingerprintProvider(mDaemon, mContext,
+ mBiometricStateCallback, mSensorProps, TAG, mLockoutResetDispatcher,
+ mGestureAvailabilityDispatcher, mBiometricContext);
+
+ waitForIdle();
+
+ for (SensorProps prop : mSensorProps) {
+ final BiometricScheduler scheduler =
+ mFingerprintProvider.mFingerprintSensors.get(prop.commonProps.sensorId)
+ .getScheduler();
+ BaseClientMonitor currentClient = scheduler.getCurrentClient();
+
+ assertThat(currentClient).isInstanceOf(FingerprintInternalCleanupClient.class);
+ assertThat(currentClient.getSensorId()).isEqualTo(prop.commonProps.sensorId);
+ assertThat(currentClient.getTargetUserId()).isEqualTo(UserHandle.USER_SYSTEM);
+ }
+ }
+
@SuppressWarnings("rawtypes")
@Test
public void halServiceDied_resetsAllSchedulers() {
+ waitForIdle();
assertEquals(mSensorProps.length, mFingerprintProvider.getSensorProperties().size());
// Schedule N operations on each sensor
final int numFakeOperations = 10;
for (SensorProps prop : mSensorProps) {
final BiometricScheduler scheduler =
- mFingerprintProvider.mSensors.get(prop.commonProps.sensorId).getScheduler();
+ mFingerprintProvider.mFingerprintSensors.get(prop.commonProps.sensorId)
+ .getScheduler();
for (int i = 0; i < numFakeOperations; i++) {
final HalClientMonitor testMonitor = mock(HalClientMonitor.class);
when(testMonitor.getFreshDaemon()).thenReturn(new Object());
@@ -132,8 +158,9 @@
// The right amount of pending and current operations are scheduled
for (SensorProps prop : mSensorProps) {
final BiometricScheduler scheduler =
- mFingerprintProvider.mSensors.get(prop.commonProps.sensorId).getScheduler();
- assertEquals(numFakeOperations - 1, scheduler.getCurrentPendingCount());
+ mFingerprintProvider.mFingerprintSensors.get(prop.commonProps.sensorId)
+ .getScheduler();
+ assertEquals(numFakeOperations, scheduler.getCurrentPendingCount());
assertNotNull(scheduler.getCurrentClient());
}
@@ -145,7 +172,8 @@
// No pending operations, no current operation.
for (SensorProps prop : mSensorProps) {
final BiometricScheduler scheduler =
- mFingerprintProvider.mSensors.get(prop.commonProps.sensorId).getScheduler();
+ mFingerprintProvider.mFingerprintSensors.get(prop.commonProps.sensorId)
+ .getScheduler();
assertNull(scheduler.getCurrentClient());
assertEquals(0, scheduler.getCurrentPendingCount());
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java
index c5a9af7..dcd06c9 100644
--- a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java
@@ -30,7 +30,7 @@
@Test
public void call_writeToParcel_fromParcel_reconstructsSuccessfully() {
final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
- final long id = 5;
+ final String id = "5";
final String callerId = "callerId";
final byte[] appIcon = "appIcon".getBytes();
final String appName = "appName";
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallServiceTest.java
index a488ab4..afddf3c 100644
--- a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallServiceTest.java
@@ -47,24 +47,24 @@
@Test
public void getCallForId_invalid() {
- when(mMockCrossDeviceCall.getId()).thenReturn(-1L);
- final CrossDeviceCall call = mSyncInCallService.getCallForId(-1L,
+ when(mMockCrossDeviceCall.getId()).thenReturn(null);
+ final CrossDeviceCall call = mSyncInCallService.getCallForId(null,
List.of(mMockCrossDeviceCall));
assertWithMessage("Unexpectedly found a match for call id").that(call).isNull();
}
@Test
public void getCallForId_noMatch() {
- when(mMockCrossDeviceCall.getId()).thenReturn(5L);
- final CrossDeviceCall call = mSyncInCallService.getCallForId(1L,
+ when(mMockCrossDeviceCall.getId()).thenReturn("123abc");
+ final CrossDeviceCall call = mSyncInCallService.getCallForId("abc123",
List.of(mMockCrossDeviceCall));
assertWithMessage("Unexpectedly found a match for call id").that(call).isNull();
}
@Test
public void getCallForId_hasMatch() {
- when(mMockCrossDeviceCall.getId()).thenReturn(5L);
- final CrossDeviceCall call = mSyncInCallService.getCallForId(5L,
+ when(mMockCrossDeviceCall.getId()).thenReturn("123abc");
+ final CrossDeviceCall call = mSyncInCallService.getCallForId("123abc",
List.of(mMockCrossDeviceCall));
assertWithMessage("Unexpectedly did not find a match for call id").that(call).isNotNull();
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java
index 9d42a5b..5a0646c 100644
--- a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java
@@ -62,9 +62,9 @@
assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
.isEqualTo(android.companion.Telecom.Call.RINGING);
assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
- .isEqualTo(Set.of(android.companion.Telecom.Call.ACCEPT,
- android.companion.Telecom.Call.REJECT,
- android.companion.Telecom.Call.SILENCE));
+ .isEqualTo(Set.of(android.companion.Telecom.ACCEPT,
+ android.companion.Telecom.REJECT,
+ android.companion.Telecom.SILENCE));
}
@Test
@@ -77,9 +77,9 @@
assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
.isEqualTo(android.companion.Telecom.Call.ONGOING);
assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
- .isEqualTo(Set.of(android.companion.Telecom.Call.END,
- android.companion.Telecom.Call.MUTE,
- android.companion.Telecom.Call.PUT_ON_HOLD));
+ .isEqualTo(Set.of(android.companion.Telecom.END,
+ android.companion.Telecom.MUTE,
+ android.companion.Telecom.PUT_ON_HOLD));
}
@Test
@@ -92,8 +92,8 @@
assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
.isEqualTo(android.companion.Telecom.Call.ON_HOLD);
assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
- .isEqualTo(Set.of(android.companion.Telecom.Call.END,
- android.companion.Telecom.Call.TAKE_OFF_HOLD));
+ .isEqualTo(Set.of(android.companion.Telecom.END,
+ android.companion.Telecom.TAKE_OFF_HOLD));
}
@Test
@@ -106,8 +106,8 @@
assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
.isEqualTo(android.companion.Telecom.Call.ONGOING);
assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
- .isEqualTo(Set.of(android.companion.Telecom.Call.END,
- android.companion.Telecom.Call.MUTE));
+ .isEqualTo(Set.of(android.companion.Telecom.END,
+ android.companion.Telecom.MUTE));
}
@Test
@@ -120,8 +120,8 @@
assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
.isEqualTo(android.companion.Telecom.Call.ONGOING);
assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
- .isEqualTo(Set.of(android.companion.Telecom.Call.END,
- android.companion.Telecom.Call.PUT_ON_HOLD));
+ .isEqualTo(Set.of(android.companion.Telecom.END,
+ android.companion.Telecom.PUT_ON_HOLD));
}
@Test
@@ -134,17 +134,17 @@
assertWithMessage("Wrong status for ringing state").that(crossDeviceCall.getStatus())
.isEqualTo(android.companion.Telecom.Call.RINGING);
assertWithMessage("Wrong controls for ringing state").that(crossDeviceCall.getControls())
- .isEqualTo(Set.of(android.companion.Telecom.Call.ACCEPT,
- android.companion.Telecom.Call.REJECT,
- android.companion.Telecom.Call.SILENCE));
+ .isEqualTo(Set.of(android.companion.Telecom.ACCEPT,
+ android.companion.Telecom.REJECT,
+ android.companion.Telecom.SILENCE));
crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_ACTIVE,
Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE));
assertWithMessage("Wrong status for active state").that(crossDeviceCall.getStatus())
.isEqualTo(android.companion.Telecom.Call.ONGOING);
assertWithMessage("Wrong controls for active state").that(crossDeviceCall.getControls())
- .isEqualTo(Set.of(android.companion.Telecom.Call.END,
- android.companion.Telecom.Call.MUTE,
- android.companion.Telecom.Call.PUT_ON_HOLD));
+ .isEqualTo(Set.of(android.companion.Telecom.END,
+ android.companion.Telecom.MUTE,
+ android.companion.Telecom.PUT_ON_HOLD));
}
@Test
@@ -158,8 +158,8 @@
assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
.isEqualTo(android.companion.Telecom.Call.RINGING_SILENCED);
assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
- .isEqualTo(Set.of(android.companion.Telecom.Call.ACCEPT,
- android.companion.Telecom.Call.REJECT));
+ .isEqualTo(Set.of(android.companion.Telecom.ACCEPT,
+ android.companion.Telecom.REJECT));
}
@Test
@@ -173,9 +173,9 @@
assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
.isEqualTo(android.companion.Telecom.Call.ONGOING);
assertWithMessage("Wrong controls").that(crossDeviceCall.getControls())
- .isEqualTo(Set.of(android.companion.Telecom.Call.END,
- android.companion.Telecom.Call.MUTE,
- android.companion.Telecom.Call.PUT_ON_HOLD));
+ .isEqualTo(Set.of(android.companion.Telecom.END,
+ android.companion.Telecom.MUTE,
+ android.companion.Telecom.PUT_ON_HOLD));
}
@Test
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 0000000..25b0ae4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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("123abc");
+ 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.ACCEPT,
+ android.companion.Telecom.REJECT,
+ android.companion.Telecom.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("123abc");
+ 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);
+ }
+
+ @Test
+ public void processTelecomDataFromMessage_createCallControlMessage_hasCallControlRequest() {
+ final byte[] data = CrossDeviceSyncController.createCallControlMessage(
+ /* callId= */ "123abc", /* status= */ android.companion.Telecom.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("123abc");
+ 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.ACCEPT));
+ assertWithMessage("Unexpectedly has active calls").that(
+ callMetadataSyncData.getCalls()).isEmpty();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 1e342f5..57755a9 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1512,6 +1512,7 @@
* Validates that when the device owner is removed, the reset password token is cleared
*/
@Test
+ @Ignore("b/277916462")
public void testClearDeviceOwner_clearResetPasswordToken() throws Exception {
mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
@@ -2602,6 +2603,7 @@
}
@Test
+ @Ignore("b/277916462")
public void testSetApplicationHiddenWithDO() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
@@ -2627,6 +2629,7 @@
}
@Test
+ @Ignore("b/277916462")
public void testSetApplicationHiddenWithPOOfOrganizationOwnedDevice() throws Exception {
final int MANAGED_PROFILE_USER_ID = CALLER_USER_HANDLE;
final int MANAGED_PROFILE_ADMIN_UID =
@@ -4373,6 +4376,7 @@
}
@Test
+ @Ignore("b/277916462")
public void testSetAutoTimeZoneEnabledModifiesSetting() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
@@ -4384,6 +4388,7 @@
}
@Test
+ @Ignore("b/277916462")
public void testSetAutoTimeZoneEnabledWithPOOnUser0() throws Exception {
mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
setupProfileOwnerOnUser0();
@@ -4395,6 +4400,7 @@
}
@Test
+ @Ignore("b/277916462")
public void testSetAutoTimeZoneEnabledFailWithPONotOnUser0() throws Exception {
setupProfileOwner();
assertExpectException(SecurityException.class, null,
@@ -4404,6 +4410,7 @@
}
@Test
+ @Ignore("b/277916462")
public void testSetAutoTimeZoneEnabledWithPOOfOrganizationOwnedDevice() throws Exception {
setupProfileOwner();
configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
@@ -5377,6 +5384,7 @@
}
@Test
+ @Ignore("b/277916462")
public void testResetPasswordWithToken() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
@@ -5411,6 +5419,7 @@
}
@Test
+ @Ignore("b/277916462")
public void resetPasswordWithToken_NumericPin() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
@@ -5431,6 +5440,7 @@
}
@Test
+ @Ignore("b/277916462")
public void resetPasswordWithToken_EmptyPassword() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
@@ -7251,6 +7261,7 @@
}
@Test
+ @Ignore("b/277916462")
public void testCanProfileOwnerResetPasswordWhenLocked() throws Exception {
setDeviceEncryptionPerUser();
setupProfileOwner();
@@ -7314,6 +7325,7 @@
}
@Test
+ @Ignore("b/277916462")
public void testSetAccountTypesWithManagementDisabledOnManagedProfile() throws Exception {
setupProfileOwner();
@@ -7333,6 +7345,7 @@
}
@Test
+ @Ignore("b/277916462")
public void testSetAccountTypesWithManagementDisabledOnOrgOwnedManagedProfile()
throws Exception {
mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS);
diff --git a/services/tests/servicestests/src/com/android/server/display/color/CctEvaluatorTest.java b/services/tests/servicestests/src/com/android/server/display/color/CctEvaluatorTest.java
new file mode 100644
index 0000000..b96666a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/color/CctEvaluatorTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.color;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class CctEvaluatorTest {
+
+ @Test
+ public void noEntriesInParallelArrays_setsEverythingToOne() {
+ final CctEvaluator evaluator = new CctEvaluator(0, 5, new int[]{}, new int[]{});
+ assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(new int[]{1, 1, 1, 1, 1, 1});
+ assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+ new int[]{0, 1, 2, 3, 4, 5});
+ }
+
+ @Test
+ public void unevenNumberOfEntriesInParallelArrays_setsEverythingToOne() {
+ final CctEvaluator evaluator = new CctEvaluator(0, 5, new int[]{0}, new int[]{});
+ assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(new int[]{1, 1, 1, 1, 1, 1});
+ assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+ new int[]{0, 1, 2, 3, 4, 5});
+ }
+
+ @Test
+ public void singleEntryInParallelArray_computesCorrectly() {
+ final CctEvaluator evaluator = new CctEvaluator(0, 5, new int[]{0}, new int[]{2});
+ assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(new int[]{2, 2, 2, 2, 2, 2});
+ assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+ new int[]{0, 0, 2, 2, 4, 4});
+ }
+
+ @Test
+ public void minimumIsBelowFirstRange_computesCorrectly() {
+ final CctEvaluator evaluator = new CctEvaluator(3000, 3005, new int[]{3002},
+ new int[]{20});
+ assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(new int[]{20, 20, 20, 20, 20, 20});
+ assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+ new int[]{3000, 3000, 3000, 3000, 3000, 3000});
+ }
+
+ @Test
+ public void minimumIsAboveFirstRange_computesCorrectly() {
+ final CctEvaluator evaluator = new CctEvaluator(3000, 3008, new int[]{3002},
+ new int[]{20});
+ assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(
+ new int[]{20, 20, 20, 20, 20, 20, 20, 20, 20});
+ assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+ new int[]{3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000});
+ }
+
+ @Test
+ public void multipleStepsStartsAtThreshold_computesCorrectly() {
+ final CctEvaluator evaluator = new CctEvaluator(5, 20, new int[]{0, 4, 5, 10, 18},
+ new int[]{11, 7, 2, 15, 9});
+ assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(
+ new int[]{2, 2, 2, 2, 2, 15, 15, 15, 15, 15, 15, 15, 15, 9, 9, 9});
+ assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+ new int[]{5, 5, 7, 7, 9, 9, 9, 9, 9, 9, 9, 9, 9, 18, 18, 18});
+ }
+
+ @Test
+ public void multipleStepsStartsInBetween_computesCorrectly() {
+ final CctEvaluator evaluator = new CctEvaluator(4, 20, new int[]{0, 5, 10, 18},
+ new int[]{14, 2, 15, 9});
+ assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(
+ new int[]{14, 2, 2, 2, 2, 2, 15, 15, 15, 15, 15, 15, 15, 15, 9, 9, 9});
+ assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+ new int[]{4, 4, 6, 6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 18, 18, 18});
+ }
+}
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 e492252..b2a3a57 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 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 @@
LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
-
- clearSmoothDisplaySetting();
- clearForcePeakRefreshRateSetting();
}
private DisplayModeDirector createDirectorFromRefreshRateArray(
@@ -922,6 +918,7 @@
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 @@
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 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 @@
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 @@
}
@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 @@
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 @@
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 @@
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 @@
}
@Override
- public void registerSmoothDisplayObserver(@NonNull ContentResolver cr,
+ public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
@NonNull ContentObserver observer) {
- mSmoothDisplaySettingObserver = observer;
- }
-
- @Override
- public void registerForcePeakRefreshRateObserver(@NonNull ContentResolver cr,
- @NonNull ContentObserver observer) {
- mForcePeakRefreshRateSettingObserver = observer;
+ mPeakRefreshRateObserver = observer;
}
@Override
@@ -2976,17 +2838,10 @@
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 a446e10..c53a7a7 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 @@
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 @@
@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 4dd5e94..fd6eb92 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 @@
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/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index d0d28c3..55c45df 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -85,6 +85,9 @@
const val DEVICE_ID = 1
const val VENDOR_SPECIFIC_DEVICE_ID = 2
const val ENGLISH_DVORAK_DEVICE_ID = 3
+ const val ENGLISH_QWERTY_DEVICE_ID = 4
+ const val DEFAULT_VENDOR_ID = 123
+ const val DEFAULT_PRODUCT_ID = 456
const val USER_ID = 4
const val IME_ID = "ime_id"
const val PACKAGE_NAME = "KeyboardLayoutManagerTests"
@@ -122,6 +125,7 @@
private lateinit var keyboardDevice: InputDevice
private lateinit var vendorSpecificKeyboardDevice: InputDevice
private lateinit var englishDvorakKeyboardDevice: InputDevice
+ private lateinit var englishQwertyKeyboardDevice: InputDevice
@Before
fun setup() {
@@ -150,17 +154,26 @@
Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
.thenReturn(inputManager)
- keyboardDevice = createKeyboard(DEVICE_ID, 0, 0, "", "")
+ keyboardDevice = createKeyboard(DEVICE_ID, DEFAULT_VENDOR_ID, DEFAULT_PRODUCT_ID, "", "")
vendorSpecificKeyboardDevice = createKeyboard(VENDOR_SPECIFIC_DEVICE_ID, 1, 1, "", "")
- englishDvorakKeyboardDevice =
- createKeyboard(ENGLISH_DVORAK_DEVICE_ID, 0, 0, "en", "dvorak")
+ englishDvorakKeyboardDevice = createKeyboard(ENGLISH_DVORAK_DEVICE_ID, DEFAULT_VENDOR_ID,
+ DEFAULT_PRODUCT_ID, "en", "dvorak")
+ englishQwertyKeyboardDevice = createKeyboard(ENGLISH_QWERTY_DEVICE_ID, DEFAULT_VENDOR_ID,
+ DEFAULT_PRODUCT_ID, "en", "qwerty")
Mockito.`when`(iInputManager.inputDeviceIds)
- .thenReturn(intArrayOf(DEVICE_ID, VENDOR_SPECIFIC_DEVICE_ID, ENGLISH_DVORAK_DEVICE_ID))
+ .thenReturn(intArrayOf(
+ DEVICE_ID,
+ VENDOR_SPECIFIC_DEVICE_ID,
+ ENGLISH_DVORAK_DEVICE_ID,
+ ENGLISH_QWERTY_DEVICE_ID
+ ))
Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice)
Mockito.`when`(iInputManager.getInputDevice(VENDOR_SPECIFIC_DEVICE_ID))
.thenReturn(vendorSpecificKeyboardDevice)
Mockito.`when`(iInputManager.getInputDevice(ENGLISH_DVORAK_DEVICE_ID))
.thenReturn(englishDvorakKeyboardDevice)
+ Mockito.`when`(iInputManager.getInputDevice(ENGLISH_QWERTY_DEVICE_ID))
+ .thenReturn(englishQwertyKeyboardDevice)
}
private fun setupBroadcastReceiver() {
@@ -778,14 +791,23 @@
@Test
fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withHwLanguageTagAndLayoutType() {
NewSettingsApiFlag(true).use {
- // Should return English dvorak even if IME current layout is qwerty, since HW says the
+ val frenchSubtype = createImeSubtypeForLanguageTagAndLayoutType("fr", "azerty")
+ // Should return English dvorak even if IME current layout is French, since HW says the
// keyboard is a Dvorak keyboard
assertCorrectLayout(
englishDvorakKeyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("en", "qwerty"),
+ frenchSubtype,
createLayoutDescriptor("keyboard_layout_english_us_dvorak")
)
+ // Back to back changing HW keyboards with same product and vendor ID but different
+ // language and layout type should configure the layouts correctly.
+ assertCorrectLayout(
+ englishQwertyKeyboardDevice,
+ frenchSubtype,
+ createLayoutDescriptor("keyboard_layout_english_us")
+ )
+
// Fallback to IME information if the HW provided layout script is incompatible with the
// provided IME subtype
assertCorrectLayout(
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
index d07831d..fd65807 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
@@ -20,16 +20,25 @@
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
+import android.platform.test.annotations.Presubmit;
+
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public class InputMethodManagerServiceTests {
@@ -87,4 +96,25 @@
InputMethodManagerService.computeImeDisplayIdForTarget(
SYSTEM_DECORATION_SUPPORT_DISPLAY_ID, sChecker));
}
+
+ @Test
+ public void testSoftInputShowHideHistoryDump_withNulls_doesntThrow() {
+ var writer = new StringWriter();
+ var history = new InputMethodManagerService.SoftInputShowHideHistory();
+ history.addEntry(new InputMethodManagerService.SoftInputShowHideHistory.Entry(
+ null,
+ null,
+ null,
+ SOFT_INPUT_STATE_UNSPECIFIED,
+ SoftInputShowHideReason.SHOW_SOFT_INPUT,
+ false,
+ null,
+ null,
+ null,
+ null));
+
+ history.dump(new PrintWriter(writer), "" /* prefix */);
+
+ // Asserts that dump doesn't throw an NPE.
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index e65f8cf..7c1845f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -201,7 +201,7 @@
mInjectedCurrentTimeMillis = START_TIME + 4 * INTERVAL + 50;
assertResetTimes(START_TIME + 4 * INTERVAL, START_TIME + 5 * INTERVAL);
- mService.saveBaseStateLocked();
+ mService.saveBaseState();
dumpBaseStateFile();
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index 01e56a0..1cfaf7c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -228,6 +228,15 @@
});
}
+ public void testShortcutIdTruncated() {
+ ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(),
+ "s".repeat(Short.MAX_VALUE)).build();
+
+ assertTrue(
+ "id must be truncated to MAX_ID_LENGTH",
+ si.getId().length() <= ShortcutInfo.MAX_ID_LENGTH);
+ }
+
public void testShortcutInfoParcel() {
setCaller(CALLING_PACKAGE_1, USER_10);
ShortcutInfo si = parceled(new ShortcutInfo.Builder(mClientContext)
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserJourneyLoggerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserJourneyLoggerTest.java
new file mode 100644
index 0000000..20e2692
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserJourneyLoggerTest.java
@@ -0,0 +1,580 @@
+/*
+ * 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.pm;
+
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_ABORTED;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_INCOMPLETE_OR_TIMEOUT;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_NULL_USER_INFO;
+import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_UNSPECIFIED;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_BEGIN;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_CANCEL;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_ERROR;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_FINISH;
+import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_NONE;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_GRANT_ADMIN;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_CREATE;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_REMOVE;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_START;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_STOP;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_CREATE_USER;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_REMOVE_USER;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_REVOKE_ADMIN;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_START_USER;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_STOP_USER;
+import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.pm.UserInfo;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class UserJourneyLoggerTest {
+
+ public static final int FULL_USER_ADMIN_FLAG = 0x00000402;
+ private UserJourneyLogger mUserJourneyLogger;
+
+ @Before
+ public void setup() throws Exception {
+ mUserJourneyLogger = spy(new UserJourneyLogger());
+ }
+
+ @Test
+ public void testUserStartLifecycleJourneyReported() {
+ final UserLifecycleJourneyReportedCaptor report1 = new UserLifecycleJourneyReportedCaptor();
+ final UserJourneyLogger.UserJourneySession session = new UserJourneyLogger
+ .UserJourneySession(10, USER_JOURNEY_USER_START);
+
+ report1.captureLogAndAssert(mUserJourneyLogger, session,
+ USER_JOURNEY_USER_START, 0, 10,
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY, 1,
+ ERROR_CODE_UNSPECIFIED);
+ }
+
+
+ @Test
+ public void testUserLifecycleEventOccurred() {
+ final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+ final UserJourneyLogger.UserJourneySession session = new UserJourneyLogger
+ .UserJourneySession(10, USER_JOURNEY_USER_START);
+
+ report1.captureLogAndAssert(mUserJourneyLogger, session, 0,
+ USER_LIFECYCLE_EVENT_START_USER, EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED);
+ }
+
+ @Test
+ public void testLogUserLifecycleEvent() {
+ final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+ final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+ .logUserJourneyBegin(10, USER_JOURNEY_USER_START);
+
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+ USER_LIFECYCLE_EVENT_START_USER, EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+ mUserJourneyLogger.logUserLifecycleEvent(10, USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
+ EVENT_STATE_NONE);
+
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+ USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED,
+ EVENT_STATE_NONE, ERROR_CODE_UNSPECIFIED, 2);
+ }
+
+
+ @Test
+ public void testCreateUserJourney() {
+ final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+ final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+ .logUserJourneyBegin(-1, USER_JOURNEY_USER_CREATE);
+
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, -1,
+ USER_LIFECYCLE_EVENT_CREATE_USER,
+ EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+ final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+ UserInfo targetUser = new UserInfo(10, "test target user",
+ UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+ mUserJourneyLogger.logUserCreateJourneyFinish(0, targetUser);
+
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+ USER_LIFECYCLE_EVENT_CREATE_USER,
+ EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
+
+ report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+ USER_JOURNEY_USER_CREATE, 0, 10,
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+ 0x00000402, ERROR_CODE_UNSPECIFIED, 1);
+ }
+
+ @Test
+ public void testRemoveUserJourney() {
+ final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+ final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+ .logUserJourneyBegin(10, USER_JOURNEY_USER_REMOVE);
+
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+ USER_LIFECYCLE_EVENT_REMOVE_USER,
+ EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+ final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+ final UserInfo targetUser = new UserInfo(10, "test target user",
+ UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+ mUserJourneyLogger.logUserJourneyFinish(0, targetUser,
+ USER_JOURNEY_USER_REMOVE);
+
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+ USER_LIFECYCLE_EVENT_REMOVE_USER,
+ EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
+
+ report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+ USER_JOURNEY_USER_REMOVE, 0, 10,
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+ 0x00000402, ERROR_CODE_UNSPECIFIED, 1);
+ }
+
+ @Test
+ public void testStartUserJourney() {
+ final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+ final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+ .logUserJourneyBegin(10, USER_JOURNEY_USER_START);
+
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+ USER_LIFECYCLE_EVENT_START_USER,
+ EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+ final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+ final UserInfo targetUser = new UserInfo(10, "test target user",
+ UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+ mUserJourneyLogger.logUserJourneyFinish(0, targetUser,
+ USER_JOURNEY_USER_START);
+
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+ USER_LIFECYCLE_EVENT_START_USER,
+ EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
+
+ report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+ USER_JOURNEY_USER_START, 0, 10,
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+ 0x00000402, ERROR_CODE_UNSPECIFIED, 1);
+ }
+
+ @Test
+ public void testStopUserJourney() {
+ final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+ final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+ .logUserJourneyBegin(10, USER_JOURNEY_USER_STOP);
+
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+ USER_LIFECYCLE_EVENT_STOP_USER, EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+ final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+ final UserInfo targetUser = new UserInfo(10, "test target user",
+ UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+ mUserJourneyLogger.logUserJourneyFinish(0, targetUser,
+ USER_JOURNEY_USER_STOP);
+
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+ USER_LIFECYCLE_EVENT_STOP_USER, EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
+
+ report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+ USER_JOURNEY_USER_STOP, 0, 10,
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+ 0x00000402, ERROR_CODE_UNSPECIFIED, 1);
+ }
+
+ @Test
+ public void testAbortStopUserJourney() {
+ final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+ final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+ .logUserJourneyBegin(10, USER_JOURNEY_USER_STOP);
+
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+ USER_LIFECYCLE_EVENT_STOP_USER, EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+ final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+ final UserInfo targetUser = new UserInfo(10, "test target user",
+ UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+
+ mUserJourneyLogger.logUserJourneyFinishWithError(-1, targetUser,
+ USER_JOURNEY_USER_STOP, ERROR_CODE_ABORTED);
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+ USER_LIFECYCLE_EVENT_STOP_USER,
+ EVENT_STATE_CANCEL, ERROR_CODE_ABORTED, 2);
+
+ report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+ USER_JOURNEY_USER_STOP, -1, 10,
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+ 0x00000402, ERROR_CODE_ABORTED, 1);
+ }
+
+ @Test
+ public void testIncompleteStopUserJourney() {
+ final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+ final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+
+ final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+ .logUserJourneyBegin(10, USER_JOURNEY_USER_STOP);
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+ USER_LIFECYCLE_EVENT_STOP_USER,
+ EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+ mUserJourneyLogger.finishAndClearIncompleteUserJourney(10, USER_JOURNEY_USER_STOP);
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+ USER_LIFECYCLE_EVENT_STOP_USER,
+ EVENT_STATE_ERROR,
+ ERROR_CODE_INCOMPLETE_OR_TIMEOUT, 2);
+
+ report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+ USER_JOURNEY_USER_STOP, -1, 10,
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN,
+ -1, // information about user are incomplete
+ ERROR_CODE_INCOMPLETE_OR_TIMEOUT, 1);
+ }
+
+ @Test
+ public void testGrantAdminUserJourney() {
+ final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+ final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+ .logUserJourneyBegin(10, USER_JOURNEY_GRANT_ADMIN);
+
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+ UserJourneyLogger.USER_LIFECYCLE_EVENT_GRANT_ADMIN,
+ EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+ final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+ final UserInfo targetUser = new UserInfo(10, "test target user",
+ UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+ mUserJourneyLogger.logUserJourneyFinish(0, targetUser,
+ USER_JOURNEY_GRANT_ADMIN);
+
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+ UserJourneyLogger.USER_LIFECYCLE_EVENT_GRANT_ADMIN,
+ EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
+
+ report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+ USER_JOURNEY_GRANT_ADMIN, 0, 10,
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+ 0x00000402, ERROR_CODE_UNSPECIFIED, 1);
+ }
+
+ @Test
+ public void testNullUserErrorGrantAdminUserJourney() {
+ final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+ final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+
+ UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+ .logUserJourneyBegin(10, USER_JOURNEY_GRANT_ADMIN);
+
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+ UserJourneyLogger.USER_LIFECYCLE_EVENT_GRANT_ADMIN,
+ EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+ mUserJourneyLogger.logNullUserJourneyError(USER_JOURNEY_GRANT_ADMIN,
+ 0, 10, "", -1);
+
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+ UserJourneyLogger.USER_LIFECYCLE_EVENT_GRANT_ADMIN,
+ EVENT_STATE_ERROR, ERROR_CODE_NULL_USER_INFO, 2);
+
+ report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+ USER_JOURNEY_GRANT_ADMIN, 0, 10,
+ FrameworkStatsLog.USER_LIFECYCLE_EVENT_OCCURRED__EVENT__UNKNOWN,
+ -1, ERROR_CODE_NULL_USER_INFO, 1);
+ }
+
+ @Test
+ public void testRevokeAdminUserJourney() {
+ final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+ final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+ .logUserJourneyBegin(10, UserJourneyLogger.USER_JOURNEY_REVOKE_ADMIN);
+
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+ USER_LIFECYCLE_EVENT_REVOKE_ADMIN,
+ EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+ final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+ final UserInfo targetUser = new UserInfo(10, "test target user", UserInfo.FLAG_FULL);
+ mUserJourneyLogger.logUserJourneyFinish(0, targetUser,
+ UserJourneyLogger.USER_JOURNEY_REVOKE_ADMIN);
+
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 10,
+ USER_LIFECYCLE_EVENT_REVOKE_ADMIN,
+ EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 2);
+
+ report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+ UserJourneyLogger.USER_JOURNEY_REVOKE_ADMIN, 0, 10,
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+ 0x00000400, ERROR_CODE_UNSPECIFIED, 1);
+ }
+
+ @Test
+ public void testSwitchFGUserJourney() {
+ final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+ final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+ .logUserJourneyBegin(11, UserJourneyLogger.USER_JOURNEY_USER_SWITCH_FG);
+
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
+ UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
+ EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+ final UserJourneyLogger.UserJourneySession session2 = mUserJourneyLogger
+ .logUserJourneyBegin(11, USER_JOURNEY_USER_START);
+
+ report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 11,
+ USER_LIFECYCLE_EVENT_START_USER,
+ EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 2);
+
+ final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+ final UserInfo targetUser = new UserInfo(11, "test target user",
+ UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+ mUserJourneyLogger.logUserJourneyFinish(10, targetUser,
+ USER_JOURNEY_USER_START);
+
+ report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 11,
+ USER_LIFECYCLE_EVENT_START_USER,
+ EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 3);
+
+ report2.captureAndAssert(mUserJourneyLogger, session2.mSessionId,
+ USER_JOURNEY_USER_START, 10, 11,
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+ 0x00000402, ERROR_CODE_UNSPECIFIED, 1);
+
+ mUserJourneyLogger.logUserSwitchJourneyFinish(10, targetUser);
+
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
+ UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
+ EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 4);
+
+ report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+ UserJourneyLogger.USER_JOURNEY_USER_SWITCH_FG, 10, 11,
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+ 0x00000402, ERROR_CODE_UNSPECIFIED, 2);
+ }
+
+
+ @Test
+ public void testSwitchUIUserJourney() {
+ final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+ final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+ .logUserJourneyBegin(11, UserJourneyLogger.USER_JOURNEY_USER_SWITCH_UI);
+
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
+ UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
+ EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+ final UserJourneyLogger.UserJourneySession session2 = mUserJourneyLogger
+ .logUserJourneyBegin(11, USER_JOURNEY_USER_START);
+
+ report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 11,
+ USER_LIFECYCLE_EVENT_START_USER,
+ EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 2);
+
+ final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+ UserInfo targetUser = new UserInfo(11, "test target user",
+ UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+ mUserJourneyLogger.logUserJourneyFinish(10, targetUser,
+ USER_JOURNEY_USER_START);
+
+ report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 11,
+ USER_LIFECYCLE_EVENT_START_USER,
+ EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 3);
+
+ report2.captureAndAssert(mUserJourneyLogger, session2.mSessionId,
+ USER_JOURNEY_USER_START, 10, 11,
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+ 0x00000402, ERROR_CODE_UNSPECIFIED, 1);
+
+ mUserJourneyLogger.logUserSwitchJourneyFinish(10, targetUser);
+
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
+ UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
+ EVENT_STATE_FINISH,
+ ERROR_CODE_UNSPECIFIED, 4);
+
+ report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+ UserJourneyLogger.USER_JOURNEY_USER_SWITCH_UI, 10, 11,
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+ 0x00000402, ERROR_CODE_UNSPECIFIED, 2);
+ }
+
+
+ @Test
+ public void testSwitchWithStopUIUserJourney() {
+ final UserLifecycleEventOccurredCaptor report1 = new UserLifecycleEventOccurredCaptor();
+
+ // BEGIN USER SWITCH
+ final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+ .logUserJourneyBegin(11, UserJourneyLogger.USER_JOURNEY_USER_SWITCH_UI);
+
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
+ UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
+ EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 1);
+
+ // BEGIN USER STOP
+ final UserJourneyLogger.UserJourneySession session2 = mUserJourneyLogger
+ .logUserJourneyBegin(10, USER_JOURNEY_USER_STOP);
+
+ report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 10,
+ USER_LIFECYCLE_EVENT_STOP_USER,
+ EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 2);
+
+ // BEGIN USER START
+ UserJourneyLogger.UserJourneySession session3 = mUserJourneyLogger
+ .logUserJourneyBegin(11, USER_JOURNEY_USER_START);
+
+ report1.captureAndAssert(mUserJourneyLogger, session3.mSessionId, 11,
+ USER_LIFECYCLE_EVENT_START_USER,
+ EVENT_STATE_BEGIN, ERROR_CODE_UNSPECIFIED, 3);
+
+
+ // FINISH USER STOP
+ final UserLifecycleJourneyReportedCaptor report2 = new UserLifecycleJourneyReportedCaptor();
+ final UserInfo targetUser = new UserInfo(10, "test target user",
+ UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+ mUserJourneyLogger.logUserJourneyFinish(-1, targetUser,
+ USER_JOURNEY_USER_STOP);
+
+ report1.captureAndAssert(mUserJourneyLogger, session2.mSessionId, 10,
+ USER_LIFECYCLE_EVENT_STOP_USER,
+ EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 4);
+
+ report2.captureAndAssert(mUserJourneyLogger, session2.mSessionId,
+ USER_JOURNEY_USER_STOP, -1, 10,
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+ FULL_USER_ADMIN_FLAG, ERROR_CODE_UNSPECIFIED, 1);
+
+ // FINISH USER START
+ final UserInfo targetUser2 = new UserInfo(11, "test target user",
+ UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+ mUserJourneyLogger.logUserJourneyFinish(10, targetUser2,
+ USER_JOURNEY_USER_START);
+
+ report1.captureAndAssert(mUserJourneyLogger, session3.mSessionId, 11,
+ USER_LIFECYCLE_EVENT_START_USER,
+ EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 5);
+
+ report2.captureAndAssert(mUserJourneyLogger, session3.mSessionId,
+ USER_JOURNEY_USER_START, 10, 11,
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+ FULL_USER_ADMIN_FLAG, ERROR_CODE_UNSPECIFIED, 2);
+
+
+ // FINISH USER SWITCH
+ mUserJourneyLogger.logUserSwitchJourneyFinish(10, targetUser2);
+
+ report1.captureAndAssert(mUserJourneyLogger, session.mSessionId, 11,
+ UserJourneyLogger.USER_LIFECYCLE_EVENT_SWITCH_USER,
+ EVENT_STATE_FINISH, ERROR_CODE_UNSPECIFIED, 6);
+
+ report2.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+ UserJourneyLogger.USER_JOURNEY_USER_SWITCH_UI, 10, 11,
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+ 0x00000402, ERROR_CODE_UNSPECIFIED, 3);
+ }
+
+ static class UserLifecycleJourneyReportedCaptor {
+ ArgumentCaptor<Long> mSessionId = ArgumentCaptor.forClass(Long.class);
+ ArgumentCaptor<Integer> mJourney = ArgumentCaptor.forClass(Integer.class);
+ ArgumentCaptor<Integer> mOriginalUserId = ArgumentCaptor.forClass(Integer.class);
+ ArgumentCaptor<Integer> mTargetUserId = ArgumentCaptor.forClass(Integer.class);
+ ArgumentCaptor<Integer> mUserType = ArgumentCaptor.forClass(Integer.class);
+ ArgumentCaptor<Integer> mUserFlags = ArgumentCaptor.forClass(Integer.class);
+ ArgumentCaptor<Integer> mErrorCode = ArgumentCaptor.forClass(Integer.class);
+
+ public void captureAndAssert(UserJourneyLogger mUserJourneyLogger,
+ long sessionId, int journey, int originalUserId,
+ int targetUserId, int userType, int userFlags, int errorCode, int times) {
+ verify(mUserJourneyLogger, times(times))
+ .writeUserLifecycleJourneyReported(mSessionId.capture(),
+ mJourney.capture(),
+ mOriginalUserId.capture(),
+ mTargetUserId.capture(),
+ mUserType.capture(),
+ mUserFlags.capture(),
+ mErrorCode.capture());
+
+ assertThat(mSessionId.getValue()).isEqualTo(sessionId);
+ assertThat(mJourney.getValue()).isEqualTo(journey);
+ assertThat(mOriginalUserId.getValue()).isEqualTo(originalUserId);
+ assertThat(mTargetUserId.getValue()).isEqualTo(targetUserId);
+ assertThat(mUserType.getValue()).isEqualTo(userType);
+ assertThat(mUserFlags.getValue()).isEqualTo(userFlags);
+ assertThat(mErrorCode.getValue()).isEqualTo(errorCode);
+ }
+
+
+ public void captureLogAndAssert(UserJourneyLogger mUserJourneyLogger,
+ UserJourneyLogger.UserJourneySession session, int journey, int originalUserId,
+ int targetUserId, int userType, int userFlags, int errorCode) {
+ mUserJourneyLogger.logUserLifecycleJourneyReported(session, journey, originalUserId,
+ targetUserId, userType, userFlags, errorCode);
+
+ captureAndAssert(mUserJourneyLogger, session.mSessionId, journey, originalUserId,
+ targetUserId, userType, userFlags, errorCode, 1);
+ }
+ }
+
+
+ static class UserLifecycleEventOccurredCaptor {
+ ArgumentCaptor<Long> mSessionId = ArgumentCaptor.forClass(Long.class);
+ ArgumentCaptor<Integer> mTargetUserId = ArgumentCaptor.forClass(Integer.class);
+ ArgumentCaptor<Integer> mEvent = ArgumentCaptor.forClass(Integer.class);
+ ArgumentCaptor<Integer> mStste = ArgumentCaptor.forClass(Integer.class);
+ ArgumentCaptor<Integer> mErrorCode = ArgumentCaptor.forClass(Integer.class);
+
+
+ public void captureAndAssert(UserJourneyLogger mUserJourneyLogger,
+ long sessionId, int targetUserId, int event, int state, int errorCode, int times) {
+ verify(mUserJourneyLogger, times(times))
+ .writeUserLifecycleEventOccurred(mSessionId.capture(),
+ mTargetUserId.capture(),
+ mEvent.capture(),
+ mStste.capture(),
+ mErrorCode.capture());
+
+ assertThat(mSessionId.getValue()).isEqualTo(sessionId);
+ assertThat(mTargetUserId.getValue()).isEqualTo(targetUserId);
+ assertThat(mEvent.getValue()).isEqualTo(event);
+ assertThat(mStste.getValue()).isEqualTo(state);
+ assertThat(mErrorCode.getValue()).isEqualTo(errorCode);
+ }
+
+
+ public void captureLogAndAssert(UserJourneyLogger mUserJourneyLogger,
+ UserJourneyLogger.UserJourneySession session, int targetUserId, int event,
+ int state, int errorCode) {
+ mUserJourneyLogger.logUserLifecycleEventOccurred(session, targetUserId, event,
+ state, errorCode);
+
+ captureAndAssert(mUserJourneyLogger, session.mSessionId, targetUserId, event,
+ state, errorCode, 1);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 52bf244..ae3ceb1 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -20,6 +20,7 @@
import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.os.PowerManager.USER_ACTIVITY_EVENT_BUTTON;
import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
@@ -41,6 +42,7 @@
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.atMost;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -112,6 +114,7 @@
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
+import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
@@ -2468,4 +2471,18 @@
verify(mNotifierMock).onWakeLockReleased(anyInt(), eq(tag), eq(packageName), anyInt(),
anyInt(), any(), any(), same(callback2));
}
+
+ @Test
+ public void testUserActivity_futureEventsAreIgnored() {
+ createService();
+ startSystem();
+ // Starting the system triggers a user activity event, so clear that before calling
+ // userActivity() directly.
+ clearInvocations(mNotifierMock);
+ final long eventTime = mClock.now() + Duration.ofHours(10).toMillis();
+ mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, eventTime,
+ USER_ACTIVITY_EVENT_BUTTON, /* flags= */ 0);
+ verify(mNotifierMock, never()).onUserActivity(anyInt(), anyInt(), anyInt());
+ }
+
}
diff --git a/services/tests/servicestests/utils/com/android/server/testutils/OWNERS b/services/tests/servicestests/utils/com/android/server/testutils/OWNERS
new file mode 100644
index 0000000..bdacf7f
--- /dev/null
+++ b/services/tests/servicestests/utils/com/android/server/testutils/OWNERS
@@ -0,0 +1 @@
+per-file *Transaction.java = file:/services/core/java/com/android/server/wm/OWNERS
\ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
similarity index 98%
rename from services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
rename to services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
index 31546e8..34e8ff2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.wm;
+package com.android.server.testutils;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -30,6 +30,8 @@
import android.view.Surface;
import android.view.SurfaceControl;
+import com.android.server.testutils.StubTransaction;
+
import java.util.HashSet;
import java.util.concurrent.Executor;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
index 397e3c1..539f329 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
@@ -36,6 +36,7 @@
import static org.mockito.Mockito.when;
import android.Manifest;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
@@ -47,7 +48,6 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.server.UiServiceTestCase;
-import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -67,7 +67,7 @@
public class PermissionHelperTest extends UiServiceTestCase {
@Mock
- private PermissionManagerServiceInternal mPmi;
+ private Context mContext;
@Mock
private IPackageManager mPackageManager;
@Mock
@@ -80,7 +80,7 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mPermissionHelper = new PermissionHelper(mPmi, mPackageManager, mPermManager);
+ mPermissionHelper = new PermissionHelper(mContext, mPackageManager, mPermManager);
PackageInfo testPkgInfo = new PackageInfo();
testPkgInfo.requestedPermissions = new String[]{ Manifest.permission.POST_NOTIFICATIONS };
when(mPackageManager.getPackageInfo(anyString(), anyLong(), anyInt()))
@@ -89,12 +89,12 @@
@Test
public void testHasPermission() throws Exception {
- when(mPmi.checkUidPermission(anyInt(), anyString()))
+ when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
.thenReturn(PERMISSION_GRANTED);
assertThat(mPermissionHelper.hasPermission(1)).isTrue();
- when(mPmi.checkUidPermission(anyInt(), anyString()))
+ when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
.thenReturn(PERMISSION_DENIED);
assertThat(mPermissionHelper.hasPermission(1)).isFalse();
@@ -241,7 +241,7 @@
@Test
public void testSetNotificationPermission_grantUserSet() throws Exception {
- when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+ when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
.thenReturn(PERMISSION_DENIED);
mPermissionHelper.setNotificationPermission("pkg", 10, true, true);
@@ -255,7 +255,7 @@
@Test
public void testSetNotificationPermission_pkgPerm_grantedByDefaultPermSet_allUserSet()
throws Exception {
- when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+ when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
.thenReturn(PERMISSION_DENIED);
when(mPermManager.getPermissionFlags(anyString(),
eq(Manifest.permission.POST_NOTIFICATIONS),
@@ -273,7 +273,7 @@
@Test
public void testSetNotificationPermission_revokeUserSet() throws Exception {
- when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+ when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
.thenReturn(PERMISSION_GRANTED);
mPermissionHelper.setNotificationPermission("pkg", 10, false, true);
@@ -287,7 +287,7 @@
@Test
public void testSetNotificationPermission_grantNotUserSet() throws Exception {
- when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+ when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
.thenReturn(PERMISSION_DENIED);
mPermissionHelper.setNotificationPermission("pkg", 10, true, false);
@@ -300,7 +300,7 @@
@Test
public void testSetNotificationPermission_revokeNotUserSet() throws Exception {
- when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+ when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
.thenReturn(PERMISSION_GRANTED);
mPermissionHelper.setNotificationPermission("pkg", 10, false, false);
@@ -340,7 +340,7 @@
@Test
public void testSetNotificationPermission_alreadyGrantedNotRegranted() throws Exception {
- when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+ when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
.thenReturn(PERMISSION_GRANTED);
mPermissionHelper.setNotificationPermission("pkg", 10, true, false);
@@ -350,7 +350,7 @@
@Test
public void testSetNotificationPermission_alreadyRevokedNotRerevoked() throws Exception {
- when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+ when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
.thenReturn(PERMISSION_DENIED);
mPermissionHelper.setNotificationPermission("pkg", 10, false, false);
@@ -360,16 +360,19 @@
@Test
public void testSetNotificationPermission_doesntRequestNotChanged() throws Exception {
- when(mPmi.checkPermission(anyString(), anyString(), anyInt()))
+ int testUid = -1;
+ when(mContext.checkPermission(anyString(), anyInt(), anyInt()))
.thenReturn(PERMISSION_GRANTED);
+ when(mPackageManager.getPackageUid(anyString(), anyInt(), anyInt()))
+ .thenReturn(testUid);
PackageInfo testPkgInfo = new PackageInfo();
testPkgInfo.requestedPermissions = new String[]{ Manifest.permission.RECORD_AUDIO };
when(mPackageManager.getPackageInfo(anyString(), anyLong(), anyInt()))
.thenReturn(testPkgInfo);
mPermissionHelper.setNotificationPermission("pkg", 10, false, false);
- verify(mPmi, never()).checkPermission(
- eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS), eq(10));
+ verify(mContext, never()).checkPermission(
+ eq(Manifest.permission.POST_NOTIFICATIONS), eq(-1), eq(testUid));
verify(mPermManager, never()).revokeRuntimePermission(
eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS), eq(10), anyString());
}
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/DeviceStateHandlerTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/DeviceStateHandlerTest.java
new file mode 100644
index 0000000..089bd45
--- /dev/null
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/DeviceStateHandlerTest.java
@@ -0,0 +1,261 @@
+/*
+ * 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.soundtrigger;
+
+import static android.os.PowerManager.SOUND_TRIGGER_MODE_ALL_DISABLED;
+import static android.os.PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED;
+import static android.os.PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY;
+
+import static com.android.server.soundtrigger.DeviceStateHandler.SoundTriggerDeviceState;
+import static com.android.server.soundtrigger.DeviceStateHandler.SoundTriggerDeviceState.*;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.SystemClock;
+
+import androidx.test.filters.FlakyTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.utils.EventLogger;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public final class DeviceStateHandlerTest {
+ private final long CONFIRM_NO_EVENT_WAIT_MS = 1000;
+ // A wait substantially less than the duration we delay phone notifications by
+ private final long PHONE_DELAY_BRIEF_WAIT_MS =
+ DeviceStateHandler.CALL_INACTIVE_MSG_DELAY_MS / 4;
+
+ private DeviceStateHandler mHandler;
+ private DeviceStateHandler.DeviceStateListener mDeviceStateCallback;
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private SoundTriggerDeviceState mState;
+
+ @GuardedBy("mLock")
+ private CountDownLatch mLatch;
+
+ private EventLogger mEventLogger;
+
+ @Before
+ public void setup() {
+ // Reset the state prior to each test
+ mEventLogger = new EventLogger(256, "test logger");
+ synchronized (mLock) {
+ mLatch = new CountDownLatch(1);
+ }
+ mDeviceStateCallback =
+ (SoundTriggerDeviceState state) -> {
+ synchronized (mLock) {
+ mState = state;
+ mLatch.countDown();
+ }
+ };
+ mHandler = new DeviceStateHandler(Runnable::run, mEventLogger);
+ mHandler.onPhoneCallStateChanged(false);
+ mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
+ mHandler.registerListener(mDeviceStateCallback);
+ try {
+ waitAndAssertState(ENABLE);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void waitAndAssertState(SoundTriggerDeviceState state) throws InterruptedException {
+ CountDownLatch latch;
+ synchronized (mLock) {
+ latch = mLatch;
+ }
+ latch.await();
+ synchronized (mLock) {
+ assertThat(mState).isEqualTo(state);
+ mLatch = new CountDownLatch(1);
+ }
+ }
+
+ private void waitToConfirmNoEventReceived() throws InterruptedException {
+ CountDownLatch latch;
+ synchronized (mLock) {
+ latch = mLatch;
+ }
+ // Check that we time out
+ assertThat(latch.await(CONFIRM_NO_EVENT_WAIT_MS, TimeUnit.MILLISECONDS)).isFalse();
+ }
+
+ @Test
+ public void onPowerModeChangedCritical_receiveStateChange() throws Exception {
+ mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+ waitAndAssertState(CRITICAL);
+ mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
+ waitAndAssertState(ENABLE);
+ }
+
+ @Test
+ public void onPowerModeChangedDisabled_receiveStateChange() throws Exception {
+ mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
+ waitAndAssertState(DISABLE);
+ mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
+ waitAndAssertState(ENABLE);
+ }
+
+ @Test
+ public void onPowerModeChangedMultiple_receiveStateChange() throws Exception {
+ mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
+ waitAndAssertState(DISABLE);
+ mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+ waitAndAssertState(CRITICAL);
+ mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
+ waitAndAssertState(DISABLE);
+ }
+
+ @Test
+ public void onPowerModeSameState_noStateChange() throws Exception {
+ mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
+ waitAndAssertState(DISABLE);
+ mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
+ waitToConfirmNoEventReceived();
+ mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+ waitAndAssertState(CRITICAL);
+ mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+ waitToConfirmNoEventReceived();
+ mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
+ waitAndAssertState(ENABLE);
+ mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
+ waitToConfirmNoEventReceived();
+ }
+
+ @Test
+ public void onPhoneCall_receiveStateChange() throws Exception {
+ mHandler.onPhoneCallStateChanged(true);
+ waitAndAssertState(DISABLE);
+ mHandler.onPhoneCallStateChanged(false);
+ waitAndAssertState(ENABLE);
+ }
+
+ @Test
+ public void onPhoneCall_receiveStateChangeIsDelayed() throws Exception {
+ mHandler.onPhoneCallStateChanged(true);
+ waitAndAssertState(DISABLE);
+ long beforeTime = SystemClock.uptimeMillis();
+ mHandler.onPhoneCallStateChanged(false);
+ waitAndAssertState(ENABLE);
+ long afterTime = SystemClock.uptimeMillis();
+ assertThat(afterTime - beforeTime).isAtLeast(DeviceStateHandler.CALL_INACTIVE_MSG_DELAY_MS);
+ }
+
+ @Test
+ public void onPhoneCallEnterExitEnter_receiveNoStateChange() throws Exception {
+ mHandler.onPhoneCallStateChanged(true);
+ waitAndAssertState(DISABLE);
+ mHandler.onPhoneCallStateChanged(false);
+ SystemClock.sleep(PHONE_DELAY_BRIEF_WAIT_MS);
+ mHandler.onPhoneCallStateChanged(true);
+ waitToConfirmNoEventReceived();
+ }
+
+ @Test
+ public void onBatteryCallbackDuringPhoneWait_receiveStateChangeDelayed() throws Exception {
+ mHandler.onPhoneCallStateChanged(true);
+ waitAndAssertState(DISABLE);
+ mHandler.onPhoneCallStateChanged(false);
+ SystemClock.sleep(PHONE_DELAY_BRIEF_WAIT_MS);
+ mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+ waitAndAssertState(CRITICAL);
+ // Ensure we don't get an ENABLE event after
+ waitToConfirmNoEventReceived();
+ }
+
+ @Test
+ public void onBatteryChangeWhenInPhoneCall_receiveNoStateChange() throws Exception {
+ mHandler.onPhoneCallStateChanged(true);
+ waitAndAssertState(DISABLE);
+ mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
+ waitToConfirmNoEventReceived();
+ mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+ waitToConfirmNoEventReceived();
+ }
+
+ @Test
+ public void whenBatteryCriticalChangeDuringCallAfterPhoneCall_receiveCriticalStateChange()
+ throws Exception {
+ mHandler.onPhoneCallStateChanged(true);
+ waitAndAssertState(DISABLE);
+ mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+ waitToConfirmNoEventReceived();
+ mHandler.onPhoneCallStateChanged(false);
+ waitAndAssertState(CRITICAL);
+ }
+
+ @Test
+ public void whenBatteryDisableDuringCallAfterPhoneCallBatteryEnable_receiveStateChange()
+ throws Exception {
+ mHandler.onPhoneCallStateChanged(true);
+ waitAndAssertState(DISABLE);
+ mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
+ waitToConfirmNoEventReceived();
+ mHandler.onPhoneCallStateChanged(false);
+ waitToConfirmNoEventReceived();
+ mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+ waitAndAssertState(CRITICAL);
+ }
+
+ @Test
+ public void whenPhoneCallDuringBatteryDisable_receiveNoStateChange() throws Exception {
+ mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
+ waitAndAssertState(DISABLE);
+ mHandler.onPhoneCallStateChanged(true);
+ waitToConfirmNoEventReceived();
+ mHandler.onPhoneCallStateChanged(false);
+ waitToConfirmNoEventReceived();
+ }
+
+ @Test
+ public void whenPhoneCallDuringBatteryCritical_receiveStateChange() throws Exception {
+ mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
+ waitAndAssertState(CRITICAL);
+ mHandler.onPhoneCallStateChanged(true);
+ waitAndAssertState(DISABLE);
+ mHandler.onPhoneCallStateChanged(false);
+ waitAndAssertState(CRITICAL);
+ }
+
+ // This test could be flaky, but we want to verify that we only delay notification if
+ // we are exiting a call, NOT if we are entering a call.
+ @FlakyTest
+ @Test
+ public void whenPhoneCallReceived_receiveStateChangeFast() throws Exception {
+ mHandler.onPhoneCallStateChanged(true);
+ CountDownLatch latch;
+ synchronized (mLock) {
+ latch = mLatch;
+ }
+ assertThat(latch.await(PHONE_DELAY_BRIEF_WAIT_MS, TimeUnit.MILLISECONDS)).isTrue();
+ synchronized (mLock) {
+ assertThat(mState).isEqualTo(DISABLE);
+ }
+ }
+}
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
index 2d0755d..3ac9a27 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
@@ -16,6 +16,8 @@
package com.android.server.soundtrigger_middleware;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -34,12 +36,12 @@
import static org.mockito.Mockito.when;
import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
import android.media.soundtrigger.Properties;
import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
import android.media.soundtrigger.RecognitionStatus;
import android.media.soundtrigger.Status;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
import android.os.HwParcel;
import android.os.IBinder;
import android.os.IHwBinder;
@@ -617,13 +619,16 @@
final int handle = 85;
final int status =
android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.ABORT;
- ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
- RecognitionEvent.class);
+ ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEventSys.class);
hwCallback.recognitionCallback(TestUtil.createRecognitionEvent_2_0(handle, status), 99);
mCanonical.flushCallbacks();
verify(canonicalCallback).recognitionCallback(eq(handle), eventCaptor.capture());
- TestUtil.validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.ABORTED,
+ RecognitionEventSys lastEvent = eventCaptor.getValue();
+ assertThat(lastEvent.halEventReceivedMillis).isGreaterThan(0);
+ TestUtil.validateRecognitionEvent(lastEvent.recognitionEvent,
+ RecognitionStatus.ABORTED,
false);
}
@@ -631,14 +636,16 @@
final int handle = 92;
final int status =
android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.SUCCESS;
- ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
- PhraseRecognitionEvent.class);
+ ArgumentCaptor<PhraseRecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+ PhraseRecognitionEventSys.class);
hwCallback.phraseRecognitionCallback(
TestUtil.createPhraseRecognitionEvent_2_0(handle, status), 99);
mCanonical.flushCallbacks();
verify(canonicalCallback).phraseRecognitionCallback(eq(handle), eventCaptor.capture());
- TestUtil.validatePhraseRecognitionEvent(eventCaptor.getValue(),
+ PhraseRecognitionEventSys lastEvent = eventCaptor.getValue();
+ assertThat(lastEvent.halEventReceivedMillis).isGreaterThan(0);
+ TestUtil.validatePhraseRecognitionEvent(lastEvent.phraseRecognitionEvent,
RecognitionStatus.SUCCESS, false);
}
verifyNoMoreInteractions(canonicalCallback);
@@ -652,28 +659,34 @@
final int handle = 85;
final int status =
android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.ABORT;
- ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
- RecognitionEvent.class);
+ ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEventSys.class);
hwCallback.recognitionCallback_2_1(TestUtil.createRecognitionEvent_2_1(handle, status),
99);
mCanonical.flushCallbacks();
verify(canonicalCallback).recognitionCallback(eq(handle), eventCaptor.capture());
- TestUtil.validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.ABORTED,
+ RecognitionEventSys lastEvent = eventCaptor.getValue();
+ assertThat(lastEvent.halEventReceivedMillis).isGreaterThan(0);
+ TestUtil.validateRecognitionEvent(lastEvent.recognitionEvent,
+ RecognitionStatus.ABORTED,
false);
}
{
final int handle = 87;
final int status = 3; // FORCED;
- ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
- RecognitionEvent.class);
+ ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEventSys.class);
hwCallback.recognitionCallback_2_1(TestUtil.createRecognitionEvent_2_1(handle, status),
99);
mCanonical.flushCallbacks();
verify(canonicalCallback).recognitionCallback(eq(handle), eventCaptor.capture());
- TestUtil.validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.FORCED,
+ RecognitionEventSys lastEvent = eventCaptor.getValue();
+ assertThat(lastEvent.halEventReceivedMillis).isGreaterThan(0);
+ TestUtil.validateRecognitionEvent(lastEvent.recognitionEvent,
+ RecognitionStatus.FORCED,
true);
}
@@ -681,28 +694,32 @@
final int handle = 92;
final int status =
android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.SUCCESS;
- ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
- PhraseRecognitionEvent.class);
+ ArgumentCaptor<PhraseRecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+ PhraseRecognitionEventSys.class);
hwCallback.phraseRecognitionCallback_2_1(
TestUtil.createPhraseRecognitionEvent_2_1(handle, status), 99);
mCanonical.flushCallbacks();
verify(canonicalCallback).phraseRecognitionCallback(eq(handle), eventCaptor.capture());
- TestUtil.validatePhraseRecognitionEvent(eventCaptor.getValue(),
+ PhraseRecognitionEventSys lastEvent = eventCaptor.getValue();
+ assertThat(lastEvent.halEventReceivedMillis).isGreaterThan(0);
+ TestUtil.validatePhraseRecognitionEvent(lastEvent.phraseRecognitionEvent,
RecognitionStatus.SUCCESS, false);
}
{
final int handle = 102;
final int status = 3; // FORCED;
- ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
- PhraseRecognitionEvent.class);
+ ArgumentCaptor<PhraseRecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+ PhraseRecognitionEventSys.class);
hwCallback.phraseRecognitionCallback_2_1(
TestUtil.createPhraseRecognitionEvent_2_1(handle, status), 99);
mCanonical.flushCallbacks();
verify(canonicalCallback).phraseRecognitionCallback(eq(handle), eventCaptor.capture());
- TestUtil.validatePhraseRecognitionEvent(eventCaptor.getValue(),
+ PhraseRecognitionEventSys lastEvent = eventCaptor.getValue();
+ assertThat(lastEvent.halEventReceivedMillis).isGreaterThan(0);
+ TestUtil.validatePhraseRecognitionEvent(lastEvent.phraseRecognitionEvent,
RecognitionStatus.FORCED, true);
}
verifyNoMoreInteractions(canonicalCallback);
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java
index 6198925..9a59ede 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java
@@ -30,8 +30,8 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
-import android.media.soundtrigger.RecognitionEvent;
import android.media.soundtrigger.RecognitionStatus;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
import androidx.annotation.NonNull;
@@ -68,13 +68,14 @@
mNotifier.setActive(true);
verify(mUnderlying).stopRecognition(handle);
- ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
- RecognitionEvent.class);
+ ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEventSys.class);
Thread.sleep(50);
verify(callback).recognitionCallback(eq(handle), eventCaptor.capture());
- RecognitionEvent event = eventCaptor.getValue();
- assertEquals(event.status, RecognitionStatus.ABORTED);
- assertFalse(event.recognitionStillActive);
+ RecognitionEventSys event = eventCaptor.getValue();
+ assertEquals(event.halEventReceivedMillis, -1);
+ assertEquals(event.recognitionEvent.status, RecognitionStatus.ABORTED);
+ assertFalse(event.recognitionEvent.recognitionStillActive);
verifyZeroInteractions(mGlobalCallback);
clearInvocations(callback, mUnderlying);
@@ -116,8 +117,11 @@
mNotifier.setActive(true);
verify(mUnderlying, times(1)).stopRecognition(handle);
+ ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEventSys.class);
mHandler.stopRecognition(handle);
- verify(callback, times(1)).recognitionCallback(eq(handle), any());
+ verify(callback).recognitionCallback(eq(handle), eventCaptor.capture());
+ assertEquals(eventCaptor.getValue().halEventReceivedMillis, -1);
}
@Test(timeout = 200)
@@ -133,19 +137,21 @@
verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
doAnswer(invocation -> {
- RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.ABORTED,
+ RecognitionEventSys recognitionEventSys = new RecognitionEventSys();
+ recognitionEventSys.recognitionEvent = TestUtil.createRecognitionEvent(
+ RecognitionStatus.ABORTED,
false);
+ recognitionEventSys.halEventReceivedMillis = 12345;
// Call the callback from a different thread to detect deadlocks by preventing recursive
// locking from working.
- runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+ runOnSeparateThread(
+ () -> modelCallback.recognitionCallback(handle, recognitionEventSys));
return null;
}).when(mUnderlying).stopRecognition(handle);
mHandler.stopRecognition(handle);
verify(mUnderlying, times(1)).stopRecognition(handle);
- ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
- RecognitionEvent.class);
- verify(callback, atMost(1)).recognitionCallback(eq(handle), eventCaptor.capture());
+ verify(callback, atMost(1)).recognitionCallback(eq(handle), any(RecognitionEventSys.class));
}
@Test(timeout = 200)
@@ -162,11 +168,15 @@
doAnswer(invocation -> {
// The stop request causes a callback to be flushed.
- RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.FORCED,
+ RecognitionEventSys recognitionEventSys = new RecognitionEventSys();
+ recognitionEventSys.recognitionEvent = TestUtil.createRecognitionEvent(
+ RecognitionStatus.FORCED,
true);
+ recognitionEventSys.halEventReceivedMillis = 12345;
// Call the callback from a different thread to detect deadlocks by preventing recursive
// locking from working.
- runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+ runOnSeparateThread(
+ () -> modelCallback.recognitionCallback(handle, recognitionEventSys));
// While the HAL is processing the stop request, capture state becomes active.
new Thread(() -> mNotifier.setActive(true)).start();
Thread.sleep(50);
@@ -194,11 +204,15 @@
doAnswer(invocation -> {
// The stop request causes a callback to be flushed.
- RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.FORCED,
+ RecognitionEventSys recognitionEventSys = new RecognitionEventSys();
+ recognitionEventSys.recognitionEvent = TestUtil.createRecognitionEvent(
+ RecognitionStatus.FORCED,
true);
+ recognitionEventSys.halEventReceivedMillis = 12345;
// Call the callback from a different thread to detect deadlocks by preventing recursive
// locking from working.
- runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+ runOnSeparateThread(
+ () -> modelCallback.recognitionCallback(handle, recognitionEventSys));
// While the HAL is processing the stop request, client requests stop.
new Thread(() -> mHandler.stopRecognition(handle)).start();
Thread.sleep(50);
@@ -223,23 +237,22 @@
verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
doAnswer(invocation -> {
- RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.SUCCESS,
+ RecognitionEventSys recognitionEventSys = new RecognitionEventSys();
+ recognitionEventSys.recognitionEvent = TestUtil.createRecognitionEvent(
+ RecognitionStatus.SUCCESS,
false);
+ recognitionEventSys.halEventReceivedMillis = 12345;
// Call the callback from a different thread to detect deadlocks by preventing recursive
// locking from working.
- runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+ runOnSeparateThread(
+ () -> modelCallback.recognitionCallback(handle, recognitionEventSys));
return null;
}).when(mUnderlying).stopRecognition(handle);
mNotifier.setActive(true);
verify(mUnderlying, times(1)).stopRecognition(handle);
Thread.sleep(50);
- ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
- RecognitionEvent.class);
- verify(callback, atMost(2)).recognitionCallback(eq(handle), eventCaptor.capture());
- RecognitionEvent lastEvent = eventCaptor.getValue();
- assertEquals(lastEvent.status, RecognitionStatus.ABORTED);
- assertFalse(lastEvent.recognitionStillActive);
+ verify(callback, atMost(2)).recognitionCallback(eq(handle), any());
}
@@ -256,11 +269,15 @@
verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
doAnswer(invocation -> {
- RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.FORCED,
+ RecognitionEventSys recognitionEventSys = new RecognitionEventSys();
+ recognitionEventSys.recognitionEvent = TestUtil.createRecognitionEvent(
+ RecognitionStatus.FORCED,
true);
+ recognitionEventSys.halEventReceivedMillis = 12345;
// Call the callback from a different thread to detect deadlocks by preventing recursive
// locking from working.
- runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+ runOnSeparateThread(
+ () -> modelCallback.recognitionCallback(handle, recognitionEventSys));
return null;
}).when(mUnderlying).stopRecognition(handle);
@@ -268,12 +285,7 @@
verify(mUnderlying, times(1)).stopRecognition(handle);
Thread.sleep(50);
- ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
- RecognitionEvent.class);
- verify(callback, atMost(2)).recognitionCallback(eq(handle), eventCaptor.capture());
- RecognitionEvent lastEvent = eventCaptor.getValue();
- assertEquals(lastEvent.status, RecognitionStatus.ABORTED);
- assertFalse(lastEvent.recognitionStillActive);
+ verify(callback, atMost(2)).recognitionCallback(eq(handle), any());
}
private static void runOnSeparateThread(Runnable runnable) {
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index 3bebc94..5a2451f 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -30,18 +30,19 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.ElapsedRealtimeLong;
import android.media.soundtrigger.ModelParameter;
import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
import android.media.soundtrigger.PhraseSoundModel;
import android.media.soundtrigger.Properties;
import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
import android.media.soundtrigger.RecognitionStatus;
import android.media.soundtrigger.SoundModel;
import android.media.soundtrigger.Status;
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
import android.os.RemoteException;
import android.util.Pair;
@@ -224,10 +225,12 @@
// Stop the recognition.
stopRecognition(module, handle, hwHandle);
- ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
- RecognitionEvent.class);
+ ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEventSys.class);
verify(callback).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
- assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().status);
+ RecognitionEventSys lastEvent = eventCaptor.getValue();
+ assertEquals(-1, lastEvent.halEventReceivedMillis);
+ assertEquals(RecognitionStatus.ABORTED, lastEvent.recognitionEvent.status);
// Unload the model.
unloadModel(module, handle, hwHandle);
@@ -273,10 +276,12 @@
// Stop the recognition.
stopRecognition(module, handle, hwHandle);
- ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
- PhraseRecognitionEvent.class);
+ ArgumentCaptor<PhraseRecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+ PhraseRecognitionEventSys.class);
verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture(), eq(101));
- assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().common.status);
+ PhraseRecognitionEventSys lastEvent = eventCaptor.getValue();
+ assertEquals(-1, lastEvent.halEventReceivedMillis);
+ assertEquals(RecognitionStatus.ABORTED, lastEvent.phraseRecognitionEvent.common.status);
// Unload the model.
unloadModel(module, handle, hwHandle);
@@ -299,11 +304,11 @@
{
// Signal a capture from the driver (with "still active").
- RecognitionEvent event = hwCallback.sendRecognitionEvent(hwHandle,
- RecognitionStatus.SUCCESS, true);
+ RecognitionEventSys event = hwCallback.sendRecognitionEvent(hwHandle,
+ RecognitionStatus.SUCCESS, true, 12345);
- ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
- RecognitionEvent.class);
+ ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEventSys.class);
verify(callback).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
// Validate the event.
@@ -312,11 +317,11 @@
{
// Signal a capture from the driver (without "still active").
- RecognitionEvent event = hwCallback.sendRecognitionEvent(hwHandle,
- RecognitionStatus.SUCCESS, false);
+ RecognitionEventSys event = hwCallback.sendRecognitionEvent(hwHandle,
+ RecognitionStatus.SUCCESS, false, 12345);
- ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
- RecognitionEvent.class);
+ ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEventSys.class);
verify(callback, times(2)).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
// Validate the event.
@@ -343,11 +348,11 @@
startRecognition(module, handle, hwHandle);
// Signal a capture from the driver.
- PhraseRecognitionEvent event = hwCallback.sendPhraseRecognitionEvent(hwHandle,
- RecognitionStatus.SUCCESS, false);
+ PhraseRecognitionEventSys event = hwCallback.sendPhraseRecognitionEvent(hwHandle,
+ RecognitionStatus.SUCCESS, false, 12345);
- ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
- PhraseRecognitionEvent.class);
+ ArgumentCaptor<PhraseRecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+ PhraseRecognitionEventSys.class);
verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture(), eq(101));
// Validate the event.
@@ -377,11 +382,11 @@
verify(mHalDriver).forceRecognitionEvent(hwHandle);
// Signal a capture from the driver.
- RecognitionEvent event = hwCallback.sendRecognitionEvent(hwHandle,
- RecognitionStatus.FORCED, true);
+ RecognitionEventSys event = hwCallback.sendRecognitionEvent(hwHandle,
+ RecognitionStatus.FORCED, true, 12345);
- ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
- RecognitionEvent.class);
+ ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEventSys.class);
verify(callback).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
// Validate the event.
@@ -445,11 +450,11 @@
verify(mHalDriver).forceRecognitionEvent(hwHandle);
// Signal a capture from the driver.
- PhraseRecognitionEvent event = hwCallback.sendPhraseRecognitionEvent(hwHandle,
- RecognitionStatus.FORCED, true);
+ PhraseRecognitionEventSys event = hwCallback.sendPhraseRecognitionEvent(hwHandle,
+ RecognitionStatus.FORCED, true, 12345);
- ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
- PhraseRecognitionEvent.class);
+ ArgumentCaptor<PhraseRecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+ PhraseRecognitionEventSys.class);
verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture(), eq(101));
// Validate the event.
@@ -510,14 +515,16 @@
startRecognition(module, handle, hwHandle);
// Abort.
- hwCallback.sendRecognitionEvent(hwHandle, RecognitionStatus.ABORTED, false);
+ hwCallback.sendRecognitionEvent(hwHandle, RecognitionStatus.ABORTED, false, 12345);
- ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
- RecognitionEvent.class);
+ ArgumentCaptor<RecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEventSys.class);
verify(callback).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
// Validate the event.
- assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().status);
+ RecognitionEventSys lastEvent = eventCaptor.getValue();
+ assertEquals(12345, lastEvent.halEventReceivedMillis);
+ assertEquals(RecognitionStatus.ABORTED, lastEvent.recognitionEvent.status);
// Unload the model.
unloadModel(module, handle, hwHandle);
@@ -540,14 +547,16 @@
startRecognition(module, handle, hwHandle);
// Abort.
- hwCallback.sendPhraseRecognitionEvent(hwHandle, RecognitionStatus.ABORTED, false);
+ hwCallback.sendPhraseRecognitionEvent(hwHandle, RecognitionStatus.ABORTED, false, 12345);
- ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
- PhraseRecognitionEvent.class);
+ ArgumentCaptor<PhraseRecognitionEventSys> eventCaptor = ArgumentCaptor.forClass(
+ PhraseRecognitionEventSys.class);
verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture(), eq(101));
// Validate the event.
- assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().common.status);
+ PhraseRecognitionEventSys lastEvent = eventCaptor.getValue();
+ assertEquals(12345, lastEvent.halEventReceivedMillis);
+ assertEquals(RecognitionStatus.ABORTED, lastEvent.phraseRecognitionEvent.common.status);
// Unload the model.
unloadModel(module, handle, hwHandle);
@@ -630,18 +639,24 @@
mCallback = callback;
}
- private RecognitionEvent sendRecognitionEvent(int hwHandle, @RecognitionStatus int status,
- boolean recognitionStillActive) {
- RecognitionEvent event = TestUtil.createRecognitionEvent(status,
+ private RecognitionEventSys sendRecognitionEvent(int hwHandle,
+ @RecognitionStatus int status,
+ boolean recognitionStillActive, @ElapsedRealtimeLong long halEventReceivedMillis) {
+ RecognitionEventSys event = new RecognitionEventSys();
+ event.recognitionEvent = TestUtil.createRecognitionEvent(status,
recognitionStillActive);
+ event.halEventReceivedMillis = halEventReceivedMillis;
mCallback.recognitionCallback(hwHandle, event);
return event;
}
- private PhraseRecognitionEvent sendPhraseRecognitionEvent(int hwHandle,
- @RecognitionStatus int status, boolean recognitionStillActive) {
- PhraseRecognitionEvent event = TestUtil.createPhraseRecognitionEvent(status,
+ private PhraseRecognitionEventSys sendPhraseRecognitionEvent(int hwHandle,
+ @RecognitionStatus int status, boolean recognitionStillActive,
+ @ElapsedRealtimeLong long halEventReceivedMillis) {
+ PhraseRecognitionEventSys event = new PhraseRecognitionEventSys();
+ event.phraseRecognitionEvent = TestUtil.createPhraseRecognitionEvent(status,
recognitionStillActive);
+ event.halEventReceivedMillis = halEventReceivedMillis;
mCallback.phraseRecognitionCallback(hwHandle, event);
return event;
}
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
new file mode 100644
index 0000000..cc357d7
--- /dev/null
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
@@ -0,0 +1,192 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import static com.android.internal.util.LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.verify;
+
+import android.Manifest;
+import android.app.ActivityThread;
+import android.media.permission.Identity;
+import android.media.permission.IdentityContext;
+import android.media.soundtrigger.PhraseRecognitionEvent;
+import android.media.soundtrigger.PhraseRecognitionExtra;
+import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.RecognitionStatus;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.os.BatteryStatsInternal;
+import android.os.Process;
+import android.os.RemoteException;
+
+import androidx.test.filters.FlakyTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.util.FakeLatencyTracker;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public class SoundTriggerMiddlewareLoggingLatencyTest {
+
+ private FakeLatencyTracker mLatencyTracker;
+ @Mock
+ private BatteryStatsInternal mBatteryStatsInternal;
+ @Mock
+ private ISoundTriggerMiddlewareInternal mDelegateMiddleware;
+ @Mock
+ private ISoundTriggerCallback mISoundTriggerCallback;
+ private SoundTriggerMiddlewareLogging mSoundTriggerMiddlewareLogging;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.WRITE_DEVICE_CONFIG,
+ Manifest.permission.READ_DEVICE_CONFIG);
+
+ Identity identity = new Identity();
+ identity.uid = Process.myUid();
+ identity.pid = Process.myPid();
+ identity.packageName = ActivityThread.currentOpPackageName();
+ IdentityContext.create(identity);
+
+ mLatencyTracker = FakeLatencyTracker.create();
+ mLatencyTracker.forceEnabled(ACTION_SHOW_VOICE_INTERACTION, -1);
+ mSoundTriggerMiddlewareLogging = new SoundTriggerMiddlewareLogging(mLatencyTracker,
+ () -> mBatteryStatsInternal,
+ mDelegateMiddleware);
+ }
+
+ @After
+ public void tearDown() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+ @Test
+ @FlakyTest(bugId = 275113847)
+ public void testSetUpAndTearDown() {
+ }
+
+ @Test
+ @FlakyTest(bugId = 275113847)
+ public void testOnPhraseRecognitionStartsLatencyTrackerWithSuccessfulPhraseIdTrigger()
+ throws RemoteException {
+ ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
+ ISoundTriggerCallback.class);
+ mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
+ verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
+
+ triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
+ RecognitionStatus.SUCCESS, 100 /* keyphraseId */);
+
+ assertThat(mLatencyTracker.getActiveActionStartTime(
+ ACTION_SHOW_VOICE_INTERACTION)).isGreaterThan(-1);
+ }
+
+ @Test
+ @FlakyTest(bugId = 275113847)
+ public void testOnPhraseRecognitionRestartsActiveSession() throws RemoteException {
+ ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
+ ISoundTriggerCallback.class);
+ mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
+ verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
+
+ triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
+ RecognitionStatus.SUCCESS, 100 /* keyphraseId */);
+ long firstTriggerSessionStartTime = mLatencyTracker.getActiveActionStartTime(
+ ACTION_SHOW_VOICE_INTERACTION);
+ triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
+ RecognitionStatus.SUCCESS, 100 /* keyphraseId */);
+ assertThat(mLatencyTracker.getActiveActionStartTime(
+ ACTION_SHOW_VOICE_INTERACTION)).isGreaterThan(-1);
+ assertThat(mLatencyTracker.getActiveActionStartTime(
+ ACTION_SHOW_VOICE_INTERACTION)).isNotEqualTo(firstTriggerSessionStartTime);
+ }
+
+ @Test
+ @FlakyTest(bugId = 275113847)
+ public void testOnPhraseRecognitionNeverStartsLatencyTrackerWithNonSuccessEvent()
+ throws RemoteException {
+ ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
+ ISoundTriggerCallback.class);
+ mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
+ verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
+
+ triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
+ RecognitionStatus.ABORTED, 100 /* keyphraseId */);
+
+ assertThat(
+ mLatencyTracker.getActiveActionStartTime(ACTION_SHOW_VOICE_INTERACTION)).isEqualTo(
+ -1);
+ }
+
+ @Test
+ @FlakyTest(bugId = 275113847)
+ public void testOnPhraseRecognitionNeverStartsLatencyTrackerWithNoKeyphraseId()
+ throws RemoteException {
+ ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
+ ISoundTriggerCallback.class);
+ mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
+ verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
+
+ triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
+ RecognitionStatus.SUCCESS);
+
+ assertThat(
+ mLatencyTracker.getActiveActionStartTime(ACTION_SHOW_VOICE_INTERACTION)).isEqualTo(
+ -1);
+ }
+
+ private void triggerPhraseRecognitionEvent(ISoundTriggerCallback callback,
+ @RecognitionStatus int triggerEventStatus) throws RemoteException {
+ triggerPhraseRecognitionEvent(callback, triggerEventStatus, -1 /* keyphraseId */);
+ }
+
+ private void triggerPhraseRecognitionEvent(ISoundTriggerCallback callback,
+ @RecognitionStatus int triggerEventStatus, int keyphraseId) throws RemoteException {
+ // trigger a phrase recognition to start a latency tracker session
+ PhraseRecognitionEvent successEventWithKeyphraseId = new PhraseRecognitionEvent();
+ successEventWithKeyphraseId.common = new RecognitionEvent();
+ successEventWithKeyphraseId.common.status = triggerEventStatus;
+ if (keyphraseId > 0) {
+ PhraseRecognitionExtra recognitionExtra = new PhraseRecognitionExtra();
+ recognitionExtra.id = keyphraseId;
+ successEventWithKeyphraseId.phraseExtras =
+ new PhraseRecognitionExtra[]{recognitionExtra};
+ }
+ PhraseRecognitionEventSys phraseRecognitionEventSys = new PhraseRecognitionEventSys();
+ phraseRecognitionEventSys.phraseRecognitionEvent = successEventWithKeyphraseId;
+ phraseRecognitionEventSys.halEventReceivedMillis = 12345;
+ callback.onPhraseRecognition(0 /* modelHandle */, phraseRecognitionEventSys,
+ 0 /* captureSession */);
+ }
+
+}
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java
index eb117d1..f92e0db 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java
@@ -18,186 +18,27 @@
import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.ServiceEvent;
import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent;
-import static com.android.internal.util.LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.verify;
-
-import android.Manifest;
-import android.app.ActivityThread;
-import android.media.permission.Identity;
-import android.media.permission.IdentityContext;
-import android.media.soundtrigger.PhraseRecognitionEvent;
-import android.media.soundtrigger.PhraseRecognitionExtra;
-import android.media.soundtrigger.RecognitionEvent;
-import android.media.soundtrigger.RecognitionStatus;
-import android.media.soundtrigger_middleware.ISoundTriggerCallback;
-import android.media.soundtrigger_middleware.ISoundTriggerModule;
-import android.os.BatteryStatsInternal;
-import android.os.Process;
-import android.os.RemoteException;
-
-import androidx.test.filters.FlakyTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.internal.util.FakeLatencyTracker;
-
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Optional;
@RunWith(JUnit4.class)
public class SoundTriggerMiddlewareLoggingTest {
private static final ServiceEvent.Type SERVICE_TYPE = ServiceEvent.Type.ATTACH;
private static final SessionEvent.Type SESSION_TYPE = SessionEvent.Type.LOAD_MODEL;
- private FakeLatencyTracker mLatencyTracker;
- @Mock
- private BatteryStatsInternal mBatteryStatsInternal;
- @Mock
- private ISoundTriggerMiddlewareInternal mDelegateMiddleware;
- @Mock
- private ISoundTriggerCallback mISoundTriggerCallback;
- @Mock
- private ISoundTriggerModule mSoundTriggerModule;
- private SoundTriggerMiddlewareLogging mSoundTriggerMiddlewareLogging;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- InstrumentationRegistry.getInstrumentation().getUiAutomation()
- .adoptShellPermissionIdentity(Manifest.permission.WRITE_DEVICE_CONFIG,
- Manifest.permission.READ_DEVICE_CONFIG);
-
- Identity identity = new Identity();
- identity.uid = Process.myUid();
- identity.pid = Process.myPid();
- identity.packageName = ActivityThread.currentOpPackageName();
- IdentityContext.create(identity);
-
- mLatencyTracker = FakeLatencyTracker.create();
- mLatencyTracker.forceEnabled(ACTION_SHOW_VOICE_INTERACTION, -1);
- mSoundTriggerMiddlewareLogging = new SoundTriggerMiddlewareLogging(mLatencyTracker,
- () -> mBatteryStatsInternal,
- mDelegateMiddleware);
- }
-
- @After
- public void tearDown() {
- InstrumentationRegistry.getInstrumentation().getUiAutomation()
- .dropShellPermissionIdentity();
- }
-
- @Test
- @FlakyTest(bugId = 275113847)
- public void testSetUpAndTearDown() {
- }
-
- @Test
- @FlakyTest(bugId = 275113847)
- public void testOnPhraseRecognitionStartsLatencyTrackerWithSuccessfulPhraseIdTrigger()
- throws RemoteException {
- ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
- ISoundTriggerCallback.class);
- mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
- verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
-
- triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
- RecognitionStatus.SUCCESS, Optional.of(100) /* keyphraseId */);
-
- assertThat(mLatencyTracker.getActiveActionStartTime(
- ACTION_SHOW_VOICE_INTERACTION)).isGreaterThan(-1);
- }
-
- @Test
- @FlakyTest(bugId = 275113847)
- public void testOnPhraseRecognitionRestartsActiveSession() throws RemoteException {
- ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
- ISoundTriggerCallback.class);
- mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
- verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
-
- triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
- RecognitionStatus.SUCCESS, Optional.of(100) /* keyphraseId */);
- long firstTriggerSessionStartTime = mLatencyTracker.getActiveActionStartTime(
- ACTION_SHOW_VOICE_INTERACTION);
- triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
- RecognitionStatus.SUCCESS, Optional.of(100) /* keyphraseId */);
- assertThat(mLatencyTracker.getActiveActionStartTime(
- ACTION_SHOW_VOICE_INTERACTION)).isGreaterThan(-1);
- assertThat(mLatencyTracker.getActiveActionStartTime(
- ACTION_SHOW_VOICE_INTERACTION)).isNotEqualTo(firstTriggerSessionStartTime);
- }
-
- @Test
- @FlakyTest(bugId = 275113847)
- public void testOnPhraseRecognitionNeverStartsLatencyTrackerWithNonSuccessEvent()
- throws RemoteException {
- ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
- ISoundTriggerCallback.class);
- mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
- verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
-
- triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
- RecognitionStatus.ABORTED, Optional.of(100) /* keyphraseId */);
-
- assertThat(
- mLatencyTracker.getActiveActionStartTime(ACTION_SHOW_VOICE_INTERACTION)).isEqualTo(
- -1);
- }
-
- @Test
- @FlakyTest(bugId = 275113847)
- public void testOnPhraseRecognitionNeverStartsLatencyTrackerWithNoKeyphraseId()
- throws RemoteException {
- ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
- ISoundTriggerCallback.class);
- mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
- verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
-
- triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
- RecognitionStatus.SUCCESS, Optional.empty() /* keyphraseId */);
-
- assertThat(
- mLatencyTracker.getActiveActionStartTime(ACTION_SHOW_VOICE_INTERACTION)).isEqualTo(
- -1);
- }
-
- private void triggerPhraseRecognitionEvent(ISoundTriggerCallback callback,
- @RecognitionStatus int triggerEventStatus, Optional<Integer> optionalKeyphraseId)
- throws RemoteException {
- // trigger a phrase recognition to start a latency tracker session
- PhraseRecognitionEvent successEventWithKeyphraseId = new PhraseRecognitionEvent();
- successEventWithKeyphraseId.common = new RecognitionEvent();
- successEventWithKeyphraseId.common.status = triggerEventStatus;
- if (optionalKeyphraseId.isPresent()) {
- PhraseRecognitionExtra recognitionExtra = new PhraseRecognitionExtra();
- recognitionExtra.id = optionalKeyphraseId.get();
- successEventWithKeyphraseId.phraseExtras =
- new PhraseRecognitionExtra[]{recognitionExtra};
- }
- callback.onPhraseRecognition(0 /* modelHandle */, successEventWithKeyphraseId,
- 0 /* captureSession */);
- }
-
@Test
public void serviceEventException_getStringContainsInfo() {
String packageName = "com.android.test";
Exception exception = new Exception("test");
Object param1 = new Object();
Object param2 = new Object();
- final var event = ServiceEvent.createForException(
- SERVICE_TYPE, packageName, exception, param1, param2);
+ final var event =
+ ServiceEvent.createForException(
+ SERVICE_TYPE, packageName, exception, param1, param2);
final var stringRep = event.eventToString();
assertThat(stringRep).contains(SERVICE_TYPE.name());
assertThat(stringRep).contains(packageName);
@@ -211,8 +52,7 @@
public void serviceEventExceptionNoArgs_getStringContainsInfo() {
String packageName = "com.android.test";
Exception exception = new Exception("test");
- final var event = ServiceEvent.createForException(
- SERVICE_TYPE, packageName, exception);
+ final var event = ServiceEvent.createForException(SERVICE_TYPE, packageName, exception);
final var stringRep = event.eventToString();
assertThat(stringRep).contains(SERVICE_TYPE.name());
assertThat(stringRep).contains(packageName);
@@ -226,8 +66,8 @@
Object param1 = new Object();
Object param2 = new Object();
Object retValue = new Object();
- final var event = ServiceEvent.createForReturn(
- SERVICE_TYPE, packageName, retValue, param1, param2);
+ final var event =
+ ServiceEvent.createForReturn(SERVICE_TYPE, packageName, retValue, param1, param2);
final var stringRep = event.eventToString();
assertThat(stringRep).contains(SERVICE_TYPE.name());
assertThat(stringRep).contains(packageName);
@@ -241,8 +81,7 @@
public void serviceEventReturnNoArgs_getStringContainsInfo() {
String packageName = "com.android.test";
Object retValue = new Object();
- final var event = ServiceEvent.createForReturn(
- SERVICE_TYPE, packageName, retValue);
+ final var event = ServiceEvent.createForReturn(SERVICE_TYPE, packageName, retValue);
final var stringRep = event.eventToString();
assertThat(stringRep).contains(SERVICE_TYPE.name());
assertThat(stringRep).contains(packageName);
@@ -255,8 +94,7 @@
Object param1 = new Object();
Object param2 = new Object();
Exception exception = new Exception("test");
- final var event = SessionEvent.createForException(
- SESSION_TYPE, exception, param1, param2);
+ final var event = SessionEvent.createForException(SESSION_TYPE, exception, param1, param2);
final var stringRep = event.eventToString();
assertThat(stringRep).contains(SESSION_TYPE.name());
assertThat(stringRep).contains(exception.toString());
@@ -268,8 +106,7 @@
@Test
public void sessionEventExceptionNoArgs_getStringContainsInfo() {
Exception exception = new Exception("test");
- final var event = SessionEvent.createForException(
- SESSION_TYPE, exception);
+ final var event = SessionEvent.createForException(SESSION_TYPE, exception);
final var stringRep = event.eventToString();
assertThat(stringRep).contains(SESSION_TYPE.name());
assertThat(stringRep).contains(exception.toString());
@@ -281,8 +118,7 @@
Object param1 = new Object();
Object param2 = new Object();
Object retValue = new Object();
- final var event = SessionEvent.createForReturn(
- SESSION_TYPE, retValue, param1, param2);
+ final var event = SessionEvent.createForReturn(SESSION_TYPE, retValue, param1, param2);
final var stringRep = event.eventToString();
assertThat(stringRep).contains(SESSION_TYPE.name());
assertThat(stringRep).contains(retValue.toString());
@@ -294,8 +130,7 @@
@Test
public void sessionEventReturnNoArgs_getStringContainsInfo() {
Object retValue = new Object();
- final var event = SessionEvent.createForReturn(
- SESSION_TYPE, retValue);
+ final var event = SessionEvent.createForReturn(SESSION_TYPE, retValue);
final var stringRep = event.eventToString();
assertThat(stringRep).contains(SESSION_TYPE.name());
assertThat(stringRep).contains(retValue.toString());
@@ -306,8 +141,7 @@
public void sessionEventVoid_getStringContainsInfo() {
Object param1 = new Object();
Object param2 = new Object();
- final var event = SessionEvent.createForVoid(
- SESSION_TYPE, param1, param2);
+ final var event = SessionEvent.createForVoid(SESSION_TYPE, param1, param2);
final var stringRep = event.eventToString();
assertThat(stringRep).contains(SESSION_TYPE.name());
assertThat(stringRep).contains(param1.toString());
@@ -317,8 +151,7 @@
@Test
public void sessionEventVoidNoArgs_getStringContainsInfo() {
- final var event = SessionEvent.createForVoid(
- SESSION_TYPE);
+ final var event = SessionEvent.createForVoid(SESSION_TYPE);
final var stringRep = event.eventToString();
assertThat(stringRep).contains(SESSION_TYPE.name());
assertThat(stringRep).ignoringCase().doesNotContain("error");
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 5282585e9..f235d15 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -36,6 +36,7 @@
import android.view.SurfaceSession;
import com.android.server.wm.SurfaceAnimator.AnimationType;
+import com.android.server.testutils.StubTransaction;
import org.junit.Before;
import org.junit.Test;
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
index d400a4c..d2494ff 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -37,6 +37,8 @@
import androidx.test.filters.SmallTest;
+import com.android.server.testutils.StubTransaction;
+
import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
diff --git a/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java b/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java
new file mode 100644
index 0000000..cc8dab9
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java
@@ -0,0 +1,273 @@
+/*
+ * 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.wm;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+
+import static com.android.server.wm.DeviceStateController.DeviceState.FOLDED;
+import static com.android.server.wm.DeviceStateController.DeviceState.HALF_FOLDED;
+import static com.android.server.wm.DeviceStateController.DeviceState.OPEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.window.TransitionRequestInfo.DisplayChange;
+
+import static com.android.internal.R.bool.config_unfoldTransitionEnabled;
+import static com.android.server.wm.DeviceStateController.DeviceState.REAR;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the {@link WindowToken} class.
+ *
+ * Build/Install/Run:
+ * atest WmTests:PhysicalDisplaySwitchTransitionLauncherTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class PhysicalDisplaySwitchTransitionLauncherTest extends WindowTestsBase {
+
+ @Mock
+ DisplayContent mDisplayContent;
+ @Mock
+ Context mContext;
+ @Mock
+ Resources mResources;
+ @Mock
+ ActivityTaskManagerService mActivityTaskManagerService;
+ @Mock
+ TransitionController mTransitionController;
+
+ private PhysicalDisplaySwitchTransitionLauncher mTarget;
+ private float mOriginalAnimationScale;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getResources()).thenReturn(mResources);
+ mTarget = new PhysicalDisplaySwitchTransitionLauncher(mDisplayContent,
+ mActivityTaskManagerService, mContext, mTransitionController);
+ mOriginalAnimationScale = ValueAnimator.getDurationScale();
+ }
+
+ @After
+ public void after() {
+ ValueAnimator.setDurationScale(mOriginalAnimationScale);
+ }
+
+ @Test
+ public void testDisplaySwitchAfterUnfoldToOpen_animationsEnabled_requestsTransition() {
+ givenAllAnimationsEnabled();
+ mTarget.foldStateChanged(FOLDED);
+
+ mTarget.foldStateChanged(OPEN);
+ mTarget.requestDisplaySwitchTransitionIfNeeded(
+ /* displayId= */ 123,
+ /* oldDisplayWidth= */ 100,
+ /* oldDisplayHeight= */ 150,
+ /* newDisplayWidth= */ 200,
+ /* newDisplayHeight= */ 250
+ );
+
+ ArgumentCaptor<DisplayChange> displayChangeArgumentCaptor =
+ ArgumentCaptor.forClass(DisplayChange.class);
+ verify(mTransitionController).requestTransitionIfNeeded(eq(TRANSIT_CHANGE), /* flags= */
+ eq(0), eq(mDisplayContent), eq(mDisplayContent), /* remoteTransition= */ isNull(),
+ displayChangeArgumentCaptor.capture());
+ assertThat(displayChangeArgumentCaptor.getValue().getDisplayId()).isEqualTo(123);
+ assertThat(displayChangeArgumentCaptor.getValue().getStartAbsBounds()).isEqualTo(
+ new Rect(0, 0, 100, 150));
+ assertThat(displayChangeArgumentCaptor.getValue().getEndAbsBounds()).isEqualTo(
+ new Rect(0, 0, 200, 250));
+ }
+
+ @Test
+ public void testDisplaySwitchAfterFolding_animationEnabled_doesNotRequestTransition() {
+ givenAllAnimationsEnabled();
+ mTarget.foldStateChanged(OPEN);
+
+ mTarget.foldStateChanged(FOLDED);
+ requestDisplaySwitch();
+
+ assertTransitionNotRequested();
+ }
+
+ @Test
+ public void testDisplaySwitchAfterUnfoldingToHalf_animationEnabled_requestsTransition() {
+ givenAllAnimationsEnabled();
+ mTarget.foldStateChanged(FOLDED);
+
+ mTarget.foldStateChanged(HALF_FOLDED);
+ requestDisplaySwitch();
+
+ assertTransitionRequested();
+ }
+
+ @Test
+ public void testDisplaySwitchSecondTimeAfterUnfolding_animationEnabled_noTransition() {
+ givenAllAnimationsEnabled();
+ mTarget.foldStateChanged(FOLDED);
+ mTarget.foldStateChanged(OPEN);
+ requestDisplaySwitch();
+ clearInvocations(mTransitionController);
+
+ requestDisplaySwitch();
+
+ assertTransitionNotRequested();
+ }
+
+
+ @Test
+ public void testDisplaySwitchAfterGoingToRearAndBack_animationEnabled_noTransition() {
+ givenAllAnimationsEnabled();
+ mTarget.foldStateChanged(OPEN);
+
+ mTarget.foldStateChanged(REAR);
+ mTarget.foldStateChanged(OPEN);
+ requestDisplaySwitch();
+
+ assertTransitionNotRequested();
+ }
+
+ @Test
+ public void testDisplaySwitchAfterUnfoldingAndFolding_animationEnabled_noTransition() {
+ givenAllAnimationsEnabled();
+ mTarget.foldStateChanged(FOLDED);
+ mTarget.foldStateChanged(OPEN);
+ // No request display switch event (simulate very fast fold after unfold, even before
+ // the displays switched)
+ mTarget.foldStateChanged(FOLDED);
+
+ requestDisplaySwitch();
+
+ assertTransitionNotRequested();
+ }
+
+ @Test
+ public void testDisplaySwitch_whenShellTransitionsNotEnabled_noTransition() {
+ givenAllAnimationsEnabled();
+ givenShellTransitionsEnabled(false);
+ mTarget.foldStateChanged(FOLDED);
+
+ mTarget.foldStateChanged(OPEN);
+ requestDisplaySwitch();
+
+ assertTransitionNotRequested();
+ }
+
+ @Test
+ public void testDisplaySwitch_whenAnimationsDisabled_noTransition() {
+ givenAllAnimationsEnabled();
+ givenAnimationsEnabled(false);
+ mTarget.foldStateChanged(FOLDED);
+
+ mTarget.foldStateChanged(OPEN);
+ requestDisplaySwitch();
+
+ assertTransitionNotRequested();
+ }
+
+ @Test
+ public void testDisplaySwitch_whenUnfoldAnimationDisabled_noTransition() {
+ givenAllAnimationsEnabled();
+ givenUnfoldTransitionEnabled(false);
+ mTarget.foldStateChanged(FOLDED);
+
+ mTarget.foldStateChanged(OPEN);
+ requestDisplaySwitch();
+
+ assertTransitionNotRequested();
+ }
+
+ @Test
+ public void testDisplaySwitch_whenNoContentInDisplayContent_noTransition() {
+ givenAllAnimationsEnabled();
+ givenDisplayContentHasContent(false);
+ mTarget.foldStateChanged(FOLDED);
+
+ mTarget.foldStateChanged(OPEN);
+ requestDisplaySwitch();
+
+ assertTransitionNotRequested();
+ }
+
+ private void assertTransitionRequested() {
+ verify(mTransitionController).requestTransitionIfNeeded(anyInt(), anyInt(), any(), any(),
+ any(), any());
+ }
+
+ private void assertTransitionNotRequested() {
+ verify(mTransitionController, never()).requestTransitionIfNeeded(anyInt(), anyInt(), any(),
+ any(), any(), any());
+ }
+
+ private void requestDisplaySwitch() {
+ mTarget.requestDisplaySwitchTransitionIfNeeded(
+ /* displayId= */ 123,
+ /* oldDisplayWidth= */ 100,
+ /* oldDisplayHeight= */ 150,
+ /* newDisplayWidth= */ 200,
+ /* newDisplayHeight= */ 250
+ );
+ }
+
+ private void givenAllAnimationsEnabled() {
+ givenAnimationsEnabled(true);
+ givenUnfoldTransitionEnabled(true);
+ givenShellTransitionsEnabled(true);
+ givenDisplayContentHasContent(true);
+ }
+
+ private void givenUnfoldTransitionEnabled(boolean enabled) {
+ when(mResources.getBoolean(config_unfoldTransitionEnabled)).thenReturn(enabled);
+ }
+
+ private void givenAnimationsEnabled(boolean enabled) {
+ ValueAnimator.setDurationScale(enabled ? 1.0f : 0.0f);
+ }
+
+ private void givenShellTransitionsEnabled(boolean enabled) {
+ when(mTransitionController.isShellTransitionsEnabled()).thenReturn(enabled);
+ }
+
+ private void givenDisplayContentHasContent(boolean hasContent) {
+ when(mDisplayContent.getLastHasContent()).thenReturn(hasContent);
+ }
+}
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 dfc453f..d173ce9 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 @@
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 @@
// 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/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
index e30206e..d84620b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
@@ -30,6 +30,9 @@
import androidx.test.filters.SmallTest;
+import com.android.server.testutils.StubTransaction;
+
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -50,6 +53,11 @@
SurfaceSyncGroup.setTransactionFactory(StubTransaction::new);
}
+ @After
+ public void tearDown() {
+ SurfaceSyncGroup.setTransactionFactory(SurfaceControl.Transaction::new);
+ }
+
@Test
public void testSyncOne() throws InterruptedException {
final CountDownLatch finishedLatch = new CountDownLatch(1);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
index ddd630e..a3a3684 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
@@ -42,6 +42,7 @@
import androidx.test.filters.SmallTest;
+import com.android.server.testutils.StubTransaction;
import com.android.server.testutils.TestHandler;
import org.junit.Before;
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 013c6d5..7edfd9a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -88,6 +88,7 @@
import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.testutils.StubTransaction;
import com.android.server.uri.UriGrantsManagerInternal;
import org.junit.rules.TestRule;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java
index e2f1334..608d7c9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java
@@ -38,6 +38,8 @@
import androidx.test.filters.SmallTest;
+import com.android.server.testutils.StubTransaction;
+
import org.junit.Test;
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index ad606cb..2d8ddfa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -1443,10 +1443,8 @@
final InsetsFrameProvider provider2 =
new InsetsFrameProvider(null, 2, WindowInsets.Type.systemOverlays())
.setArbitraryRectangle(genericOverlayInsetsRect2);
- final int sourceId1 = InsetsSource.createId(
- provider1.getOwner(), provider1.getIndex(), provider1.getType());
- final int sourceId2 = InsetsSource.createId(
- provider2.getOwner(), provider2.getIndex(), provider2.getType());
+ final int sourceId1 = provider1.getId();
+ final int sourceId2 = provider2.getId();
rootTask.addLocalInsetsFrameProvider(provider1);
container.addLocalInsetsFrameProvider(provider2);
@@ -1504,10 +1502,8 @@
final InsetsFrameProvider provider2 =
new InsetsFrameProvider(null, 1, WindowInsets.Type.systemOverlays())
.setArbitraryRectangle(genericOverlayInsetsRect2);
- final int sourceId1 = InsetsSource.createId(
- provider1.getOwner(), provider1.getIndex(), provider1.getType());
- final int sourceId2 = InsetsSource.createId(
- provider2.getOwner(), provider2.getIndex(), provider2.getType());
+ final int sourceId1 = provider1.getId();
+ final int sourceId2 = provider2.getId();
rootTask.addLocalInsetsFrameProvider(provider1);
activity0.forAllWindows(window -> {
@@ -1566,10 +1562,8 @@
final InsetsFrameProvider provider2 =
new InsetsFrameProvider(null, 2, WindowInsets.Type.systemOverlays())
.setArbitraryRectangle(navigationBarInsetsRect2);
- final int sourceId1 = InsetsSource.createId(
- provider1.getOwner(), provider1.getIndex(), provider1.getType());
- final int sourceId2 = InsetsSource.createId(
- provider2.getOwner(), provider2.getIndex(), provider2.getType());
+ final int sourceId1 = provider1.getId();
+ final int sourceId2 = provider2.getId();
rootTask.addLocalInsetsFrameProvider(provider1);
container.addLocalInsetsFrameProvider(provider2);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerThumbnailTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerThumbnailTest.java
index 2ae1172..849072e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerThumbnailTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerThumbnailTest.java
@@ -28,6 +28,8 @@
import androidx.test.filters.SmallTest;
+import com.android.server.testutils.StubTransaction;
+
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 460a603..ee1afcf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -110,6 +110,8 @@
import androidx.test.filters.SmallTest;
+import com.android.server.testutils.StubTransaction;
+
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index a63807d..a4cad5e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -359,9 +359,12 @@
new InsetsFrameProvider(owner, 0, WindowInsets.Type.tappableElement()),
new InsetsFrameProvider(owner, 0, WindowInsets.Type.mandatorySystemGestures())
};
- for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
- mNavBarWindow.mAttrs.paramsForRotation[rot] =
- getNavBarLayoutParamsForRotation(rot, owner);
+ // If the navigation bar cannot move then it is always at the bottom.
+ if (mDisplayContent.getDisplayPolicy().navigationBarCanMove()) {
+ for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
+ mNavBarWindow.mAttrs.paramsForRotation[rot] =
+ getNavBarLayoutParamsForRotation(rot, owner);
+ }
}
}
if (addAll || ArrayUtils.contains(requestedWindows, W_DOCK_DIVIDER)) {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index a98429a..ef13594 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2539,10 +2539,20 @@
}
@Override
- public void reportChooserSelection(String packageName, int userId, String contentType,
- String[] annotations, String action) {
+ public void reportChooserSelection(@NonNull String packageName, int userId,
+ @NonNull String contentType, String[] annotations, @NonNull String action) {
if (packageName == null) {
- Slog.w(TAG, "Event report user selecting a null package");
+ throw new IllegalArgumentException("Package selection must not be null.");
+ }
+ if (contentType == null) {
+ throw new IllegalArgumentException("Content type for selection must not be null.");
+ }
+ if (action == null) {
+ throw new IllegalArgumentException("Selection action must not be null.");
+ }
+ // Verify if this package exists before reporting an event for it.
+ if (mPackageManagerInternal.getPackageUid(packageName, 0, userId) < 0) {
+ Slog.w(TAG, "Event report user selecting an invalid package");
return;
}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/DeviceStateHandler.java b/services/voiceinteraction/java/com/android/server/soundtrigger/DeviceStateHandler.java
new file mode 100644
index 0000000..6605449
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/DeviceStateHandler.java
@@ -0,0 +1,279 @@
+/**
+ * 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.soundtrigger;
+
+import static android.os.PowerManager.SOUND_TRIGGER_MODE_ALL_DISABLED;
+import static android.os.PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED;
+import static android.os.PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.utils.EventLogger;
+
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Manages device state events which require pausing SoundTrigger recognition
+ *
+ * @hide
+ */
+public class DeviceStateHandler implements PhoneCallStateHandler.Callback {
+
+ public static final long CALL_INACTIVE_MSG_DELAY_MS = 1000;
+
+ public interface DeviceStateListener {
+ void onSoundTriggerDeviceStateUpdate(SoundTriggerDeviceState state);
+ }
+
+ public enum SoundTriggerDeviceState {
+ DISABLE, // The device state requires all SoundTrigger sessions are disabled
+ CRITICAL, // The device state requires all non-critical SoundTrigger sessions are disabled
+ ENABLE // The device state permits all SoundTrigger sessions
+ }
+
+ private final Object mLock = new Object();
+
+ private final EventLogger mEventLogger;
+
+ @GuardedBy("mLock")
+ SoundTriggerDeviceState mSoundTriggerDeviceState = SoundTriggerDeviceState.ENABLE;
+
+ // Individual components of the SoundTriggerDeviceState
+ @GuardedBy("mLock")
+ private int mSoundTriggerPowerSaveMode = SOUND_TRIGGER_MODE_ALL_ENABLED;
+
+ @GuardedBy("mLock")
+ private boolean mIsPhoneCallOngoing = false;
+
+ // There can only be one pending notify at any given time.
+ // If any phone state change comes in between, we will cancel the previous pending
+ // task.
+ @GuardedBy("mLock")
+ private NotificationTask mPhoneStateChangePendingNotify = null;
+
+ private Set<DeviceStateListener> mCallbackSet = ConcurrentHashMap.newKeySet(4);
+
+ private final Executor mDelayedNotificationExecutor = Executors.newSingleThreadExecutor();
+
+ private final Executor mCallbackExecutor;
+
+ public void onPowerModeChanged(int soundTriggerPowerSaveMode) {
+ mEventLogger.enqueue(new SoundTriggerPowerEvent(soundTriggerPowerSaveMode));
+ synchronized (mLock) {
+ if (soundTriggerPowerSaveMode == mSoundTriggerPowerSaveMode) {
+ // No state change, nothing to do
+ return;
+ }
+ mSoundTriggerPowerSaveMode = soundTriggerPowerSaveMode;
+ evaluateStateChange();
+ }
+ }
+
+ @Override
+ public void onPhoneCallStateChanged(boolean isInPhoneCall) {
+ mEventLogger.enqueue(new PhoneCallEvent(isInPhoneCall));
+ synchronized (mLock) {
+ if (mIsPhoneCallOngoing == isInPhoneCall) {
+ // no change, nothing to do
+ return;
+ }
+ // Clear any pending notification
+ if (mPhoneStateChangePendingNotify != null) {
+ mPhoneStateChangePendingNotify.cancel();
+ mPhoneStateChangePendingNotify = null;
+ }
+ mIsPhoneCallOngoing = isInPhoneCall;
+ if (!mIsPhoneCallOngoing) {
+ // State has changed from call to no call, delay notification
+ mPhoneStateChangePendingNotify = new NotificationTask(
+ new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ if (mPhoneStateChangePendingNotify != null &&
+ mPhoneStateChangePendingNotify.runnableEquals(this)) {
+
+ mPhoneStateChangePendingNotify = null;
+ evaluateStateChange();
+ }
+ }
+ }
+ },
+ CALL_INACTIVE_MSG_DELAY_MS);
+ mDelayedNotificationExecutor.execute(mPhoneStateChangePendingNotify);
+ } else {
+ evaluateStateChange();
+ }
+ }
+ }
+
+ /** Note, we expect initial callbacks immediately following construction */
+ public DeviceStateHandler(Executor callbackExecutor, EventLogger eventLogger) {
+ mCallbackExecutor = Objects.requireNonNull(callbackExecutor);
+ mEventLogger = Objects.requireNonNull(eventLogger);
+ }
+
+ public SoundTriggerDeviceState getDeviceState() {
+ synchronized (mLock) {
+ return mSoundTriggerDeviceState;
+ }
+ }
+
+ public void registerListener(DeviceStateListener callback) {
+ final var state = getDeviceState();
+ mCallbackExecutor.execute(
+ () -> callback.onSoundTriggerDeviceStateUpdate(state));
+ mCallbackSet.add(callback);
+ }
+
+ public void unregisterListener(DeviceStateListener callback) {
+ mCallbackSet.remove(callback);
+ }
+
+ void dump(PrintWriter pw) {
+ synchronized (mLock) {
+ pw.println("DeviceState: " + mSoundTriggerDeviceState.name());
+ pw.println("PhoneState: " + mIsPhoneCallOngoing);
+ pw.println("PowerSaveMode: " + mSoundTriggerPowerSaveMode);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void evaluateStateChange() {
+ // We should wait until any pending delays are complete to update.
+ // We will eventually get called by the notification task, or something which
+ // cancels it.
+ // Additionally, if there isn't a state change, there is nothing to update.
+ SoundTriggerDeviceState newState = computeState();
+ if (mPhoneStateChangePendingNotify != null || mSoundTriggerDeviceState == newState) {
+ return;
+ }
+
+ mSoundTriggerDeviceState = newState;
+ mEventLogger.enqueue(new DeviceStateEvent(mSoundTriggerDeviceState));
+ final var state = mSoundTriggerDeviceState;
+ for (var callback : mCallbackSet) {
+ mCallbackExecutor.execute(
+ () -> callback.onSoundTriggerDeviceStateUpdate(state));
+ }
+ }
+
+ @GuardedBy("mLock")
+ private SoundTriggerDeviceState computeState() {
+ if (mIsPhoneCallOngoing) {
+ return SoundTriggerDeviceState.DISABLE;
+ }
+ return switch (mSoundTriggerPowerSaveMode) {
+ case SOUND_TRIGGER_MODE_ALL_ENABLED -> SoundTriggerDeviceState.ENABLE;
+ case SOUND_TRIGGER_MODE_CRITICAL_ONLY -> SoundTriggerDeviceState.CRITICAL;
+ case SOUND_TRIGGER_MODE_ALL_DISABLED -> SoundTriggerDeviceState.DISABLE;
+ default -> throw new IllegalStateException(
+ "Received unexpected power state code" + mSoundTriggerPowerSaveMode);
+ };
+ }
+
+ /**
+ * One-shot, cancellable task which runs after a delay. Run must only be called once, from a
+ * single thread. Cancel can be called from any other thread.
+ */
+ private static class NotificationTask implements Runnable {
+ private final Runnable mRunnable;
+ private final long mWaitInMillis;
+
+ private final CountDownLatch mCancelLatch = new CountDownLatch(1);
+
+ NotificationTask(Runnable r, long waitInMillis) {
+ mRunnable = r;
+ mWaitInMillis = waitInMillis;
+ }
+
+ void cancel() {
+ mCancelLatch.countDown();
+ }
+
+ // Used for determining task equality.
+ boolean runnableEquals(Runnable runnable) {
+ return mRunnable == runnable;
+ }
+
+ public void run() {
+ try {
+ if (!mCancelLatch.await(mWaitInMillis, TimeUnit.MILLISECONDS)) {
+ mRunnable.run();
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new AssertionError("Unexpected InterruptedException", e);
+ }
+ }
+ }
+
+ private static class PhoneCallEvent extends EventLogger.Event {
+ final boolean mIsInPhoneCall;
+
+ PhoneCallEvent(boolean isInPhoneCall) {
+ mIsInPhoneCall = isInPhoneCall;
+ }
+
+ @Override
+ public String eventToString() {
+ return "PhoneCallChange - inPhoneCall: " + mIsInPhoneCall;
+ }
+ }
+
+ private static class SoundTriggerPowerEvent extends EventLogger.Event {
+ final int mSoundTriggerPowerState;
+
+ SoundTriggerPowerEvent(int soundTriggerPowerState) {
+ mSoundTriggerPowerState = soundTriggerPowerState;
+ }
+
+ @Override
+ public String eventToString() {
+ return "SoundTriggerPowerChange: " + stateToString();
+ }
+
+ private String stateToString() {
+ return switch (mSoundTriggerPowerState) {
+ case SOUND_TRIGGER_MODE_ALL_ENABLED -> "All enabled";
+ case SOUND_TRIGGER_MODE_CRITICAL_ONLY -> "Critical only";
+ case SOUND_TRIGGER_MODE_ALL_DISABLED -> "All disabled";
+ default -> "Unknown power state: " + mSoundTriggerPowerState;
+ };
+ }
+ }
+
+ private static class DeviceStateEvent extends EventLogger.Event {
+ final SoundTriggerDeviceState mSoundTriggerDeviceState;
+
+ DeviceStateEvent(SoundTriggerDeviceState soundTriggerDeviceState) {
+ mSoundTriggerDeviceState = soundTriggerDeviceState;
+ }
+
+ @Override
+ public String eventToString() {
+ return "DeviceStateChange: " + mSoundTriggerDeviceState.name();
+ }
+ }
+}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
new file mode 100644
index 0000000..8773cab
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
@@ -0,0 +1,158 @@
+/**
+ * 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.soundtrigger;
+
+import android.telephony.Annotation;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Handles monitoring telephony call state across active subscriptions.
+ *
+ * @hide
+ */
+public class PhoneCallStateHandler {
+
+ public interface Callback {
+ void onPhoneCallStateChanged(boolean isInPhoneCall);
+ }
+
+ private final Object mLock = new Object();
+
+ // Actually never contended due to executor.
+ @GuardedBy("mLock")
+ private final List<MyCallStateListener> mListenerList = new ArrayList<>();
+
+ private final AtomicBoolean mIsPhoneCallOngoing = new AtomicBoolean(false);
+
+ private final SubscriptionManager mSubscriptionManager;
+ private final TelephonyManager mTelephonyManager;
+ private final Callback mCallback;
+
+ private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+
+ public PhoneCallStateHandler(
+ SubscriptionManager subscriptionManager,
+ TelephonyManager telephonyManager,
+ Callback callback) {
+ mSubscriptionManager = Objects.requireNonNull(subscriptionManager);
+ mTelephonyManager = Objects.requireNonNull(telephonyManager);
+ mCallback = Objects.requireNonNull(callback);
+ mSubscriptionManager.addOnSubscriptionsChangedListener(
+ mExecutor,
+ new SubscriptionManager.OnSubscriptionsChangedListener() {
+ @Override
+ public void onSubscriptionsChanged() {
+ updateTelephonyListeners();
+ }
+
+ @Override
+ public void onAddListenerFailed() {
+ Slog.wtf(
+ "SoundTriggerPhoneCallStateHandler",
+ "Failed to add a telephony listener");
+ }
+ });
+ }
+
+ private final class MyCallStateListener extends TelephonyCallback
+ implements TelephonyCallback.CallStateListener {
+
+ final TelephonyManager mTelephonyManagerForSubId;
+
+ // Manager corresponding to the sub-id
+ MyCallStateListener(TelephonyManager telephonyManager) {
+ mTelephonyManagerForSubId = telephonyManager;
+ }
+
+ void cleanup() {
+ mExecutor.execute(() -> mTelephonyManagerForSubId.unregisterTelephonyCallback(this));
+ }
+
+ @Override
+ public void onCallStateChanged(int unused) {
+ updateCallStatus();
+ }
+ }
+
+ /** Compute the current call status, and dispatch callback if it has changed. */
+ private void updateCallStatus() {
+ boolean callStatus = checkCallStatus();
+ if (mIsPhoneCallOngoing.compareAndSet(!callStatus, callStatus)) {
+ mCallback.onPhoneCallStateChanged(callStatus);
+ }
+ }
+
+ /**
+ * Synchronously query the current telephony call state across all subscriptions
+ *
+ * @return - {@code true} if in call, {@code false} if not in call.
+ */
+ private boolean checkCallStatus() {
+ List<SubscriptionInfo> infoList = mSubscriptionManager.getActiveSubscriptionInfoList();
+ if (infoList == null) return false;
+ return infoList.stream()
+ .filter(s -> (s.getSubscriptionId() != SubscriptionManager.INVALID_SUBSCRIPTION_ID))
+ .anyMatch(s -> isCallOngoingFromState(
+ mTelephonyManager
+ .createForSubscriptionId(s.getSubscriptionId())
+ .getCallStateForSubscription()));
+ }
+
+ private void updateTelephonyListeners() {
+ synchronized (mLock) {
+ for (var listener : mListenerList) {
+ listener.cleanup();
+ }
+ mListenerList.clear();
+ List<SubscriptionInfo> infoList = mSubscriptionManager.getActiveSubscriptionInfoList();
+ if (infoList == null) return;
+ infoList.stream()
+ .filter(s -> s.getSubscriptionId()
+ != SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ .map(s -> mTelephonyManager.createForSubscriptionId(s.getSubscriptionId()))
+ .forEach(manager -> {
+ synchronized (mLock) {
+ var listener = new MyCallStateListener(manager);
+ mListenerList.add(listener);
+ manager.registerTelephonyCallback(mExecutor, listener);
+ }
+ });
+ }
+ }
+
+ private static boolean isCallOngoingFromState(@Annotation.CallState int callState) {
+ return switch (callState) {
+ case TelephonyManager.CALL_STATE_IDLE, TelephonyManager.CALL_STATE_RINGING -> false;
+ case TelephonyManager.CALL_STATE_OFFHOOK -> true;
+ default -> throw new IllegalStateException(
+ "Received unexpected call state from Telephony Manager: " + callState);
+ };
+ }
+}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 255db1e..b4066ab 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -16,15 +16,12 @@
package com.android.server.soundtrigger;
+import static com.android.server.soundtrigger.DeviceStateHandler.SoundTriggerDeviceState;
import static com.android.server.soundtrigger.SoundTriggerEvent.SessionEvent.Type;
import static com.android.server.utils.EventLogger.Event.ALOGW;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.ModelParams;
import android.hardware.soundtrigger.SoundTrigger;
@@ -45,11 +42,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.PowerManager;
-import android.os.PowerManager.SoundTriggerPowerSaveMode;
import android.os.RemoteException;
-import android.telephony.PhoneStateListener;
-import android.telephony.TelephonyManager;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -99,37 +92,20 @@
private SoundTriggerModule mModule;
private final Object mLock = new Object();
private final Context mContext;
- private final TelephonyManager mTelephonyManager;
- private final PhoneStateListener mPhoneStateListener;
- private final PowerManager mPowerManager;
// The SoundTriggerManager layer handles multiple recognition models of type generic and
// keyphrase. We store the ModelData here in a hashmap.
- private final HashMap<UUID, ModelData> mModelDataMap;
+ private final HashMap<UUID, ModelData> mModelDataMap = new HashMap<>();
// An index of keyphrase sound models so that we can reach them easily. We support indexing
// keyphrase sound models with a keyphrase ID. Sound model with the same keyphrase ID will
// replace an existing model, thus there is a 1:1 mapping from keyphrase ID to a voice
// sound model.
- private HashMap<Integer, UUID> mKeyphraseUuidMap;
-
- private boolean mCallActive = false;
- private @SoundTriggerPowerSaveMode int mSoundTriggerPowerSaveMode =
- PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED;
+ private final HashMap<Integer, UUID> mKeyphraseUuidMap = new HashMap<>();
// Whether ANY recognition (keyphrase or generic) has been requested.
private boolean mRecognitionRequested = false;
- private PowerSaveModeListener mPowerSaveModeListener;
-
-
- // Handler to process call state changes will delay to allow time for the audio
- // and sound trigger HALs to process the end of call notifications
- // before we re enable pending recognition requests.
- private final Handler mHandler;
- private static final int MSG_CALL_STATE_CHANGED = 0;
- private static final int CALL_INACTIVE_MSG_DELAY_MS = 1000;
-
// TODO(b/269366605) Temporary solution to query correct moduleProperties
private final int mModuleId;
private final Function<SoundTrigger.StatusListener, SoundTriggerModule> mModuleProvider;
@@ -139,16 +115,15 @@
@GuardedBy("mLock")
private boolean mIsDetached = false;
+ @GuardedBy("mLock")
+ private SoundTriggerDeviceState mDeviceState = SoundTriggerDeviceState.DISABLE;
+
SoundTriggerHelper(Context context, EventLogger eventLogger,
@NonNull Function<SoundTrigger.StatusListener, SoundTriggerModule> moduleProvider,
int moduleId,
@NonNull Supplier<List<ModuleProperties>> modulePropertiesProvider) {
mModuleId = moduleId;
mContext = context;
- mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- mModelDataMap = new HashMap<UUID, ModelData>();
- mKeyphraseUuidMap = new HashMap<Integer, UUID>();
mModuleProvider = moduleProvider;
mEventLogger = eventLogger;
mModulePropertiesProvider = modulePropertiesProvider;
@@ -157,31 +132,6 @@
} else {
mModule = mModuleProvider.apply(this);
}
- Looper looper = Looper.myLooper();
- if (looper == null) {
- looper = Looper.getMainLooper();
- }
- mPhoneStateListener = new MyCallStateListener(looper);
- if (looper != null) {
- mHandler = new Handler(looper) {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_CALL_STATE_CHANGED:
- synchronized (mLock) {
- onCallStateChangedLocked(
- TelephonyManager.CALL_STATE_OFFHOOK == msg.arg1);
- }
- break;
- default:
- Slog.e(TAG, "unknown message in handler:" + msg.what);
- break;
- }
- }
- };
- } else {
- mHandler = null;
- }
}
/**
@@ -373,7 +323,6 @@
modelData.setSoundModel(soundModel);
if (!isRecognitionAllowedByDeviceState(modelData)) {
- initializeDeviceStateListeners();
return STATUS_OK;
}
@@ -497,11 +446,6 @@
modelData.setLoaded();
modelData.clearCallback();
modelData.setRecognitionConfig(null);
-
- if (!computeRecognitionRequestedLocked()) {
- internalClearGlobalStateLocked();
- }
-
return status;
}
}
@@ -638,6 +582,17 @@
}
}
+ public void onDeviceStateChanged(SoundTriggerDeviceState state) {
+ synchronized (mLock) {
+ if (mIsDetached || mDeviceState == state) {
+ // Nothing to update
+ return;
+ }
+ mDeviceState = state;
+ updateAllRecognitionsLocked();
+ }
+ }
+
public int getGenericModelState(UUID modelId) {
synchronized (mLock) {
MetricsLogger.count(mContext, "sth_get_generic_model_state", 1);
@@ -880,25 +835,6 @@
}
}
- private void onCallStateChangedLocked(boolean callActive) {
- if (mCallActive == callActive) {
- // We consider multiple call states as being active
- // so we check if something really changed or not here.
- return;
- }
- mCallActive = callActive;
- updateAllRecognitionsLocked();
- }
-
- private void onPowerSaveModeChangedLocked(
- @SoundTriggerPowerSaveMode int soundTriggerPowerSaveMode) {
- if (mSoundTriggerPowerSaveMode == soundTriggerPowerSaveMode) {
- return;
- }
- mSoundTriggerPowerSaveMode = soundTriggerPowerSaveMode;
- updateAllRecognitionsLocked();
- }
-
private void onModelUnloadedLocked(int modelHandle) {
ModelData modelData = getModelDataForLocked(modelHandle);
if (modelData != null) {
@@ -1011,10 +947,6 @@
return status;
}
status = startRecognitionLocked(model, notifyClientOnError);
- // Initialize power save, call active state monitoring logic.
- if (status == STATUS_OK) {
- initializeDeviceStateListeners();
- }
return status;
} else {
return stopRecognitionLocked(model, notifyClientOnError);
@@ -1040,7 +972,6 @@
}
} finally {
internalClearModelStateLocked();
- internalClearGlobalStateLocked();
if (mModule != null) {
mModule.detach();
try {
@@ -1054,24 +985,6 @@
}
}
- // internalClearGlobalStateLocked() cleans up the telephony and power save listeners.
- private void internalClearGlobalStateLocked() {
- // Unregister from call state changes.
- final long token = Binder.clearCallingIdentity();
- try {
- mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
-
- // Unregister from power save mode changes.
- if (mPowerSaveModeListener != null) {
- mContext.unregisterReceiver(mPowerSaveModeListener);
- mPowerSaveModeListener = null;
- }
- mRecognitionRequested = false;
- }
-
// Clears state for all models (generic and keyphrase).
private void internalClearModelStateLocked() {
for (ModelData modelData : mModelDataMap.values()) {
@@ -1079,67 +992,6 @@
}
}
- class MyCallStateListener extends PhoneStateListener {
- MyCallStateListener(@NonNull Looper looper) {
- super(Objects.requireNonNull(looper));
- }
-
- @Override
- public void onCallStateChanged(int state, String arg1) {
-
- if (mHandler != null) {
- synchronized (mLock) {
- mHandler.removeMessages(MSG_CALL_STATE_CHANGED);
- Message msg = mHandler.obtainMessage(MSG_CALL_STATE_CHANGED, state, 0);
- mHandler.sendMessageDelayed(
- msg, (TelephonyManager.CALL_STATE_OFFHOOK == state) ? 0
- : CALL_INACTIVE_MSG_DELAY_MS);
- }
- }
- }
- }
-
- class PowerSaveModeListener extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (!PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) {
- return;
- }
- @SoundTriggerPowerSaveMode int soundTriggerPowerSaveMode =
- mPowerManager.getSoundTriggerPowerSaveMode();
- synchronized (mLock) {
- onPowerSaveModeChangedLocked(soundTriggerPowerSaveMode);
- }
- }
- }
-
- private void initializeDeviceStateListeners() {
- if (mRecognitionRequested) {
- return;
- }
- final long token = Binder.clearCallingIdentity();
- try {
- // Get the current call state synchronously for the first recognition.
- mCallActive = mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK;
-
- // Register for call state changes when the first call to start recognition occurs.
- mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
-
- // Register for power saver mode changes when the first call to start recognition
- // occurs.
- if (mPowerSaveModeListener == null) {
- mPowerSaveModeListener = new PowerSaveModeListener();
- mContext.registerReceiver(mPowerSaveModeListener,
- new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
- }
- mSoundTriggerPowerSaveMode = mPowerManager.getSoundTriggerPowerSaveMode();
-
- mRecognitionRequested = true;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
/**
* Stops and unloads all models. This is intended as a clean-up call with the expectation that
* this instance is not used after.
@@ -1153,7 +1005,6 @@
forceStopAndUnloadModelLocked(model, null);
}
mModelDataMap.clear();
- internalClearGlobalStateLocked();
if (mModule != null) {
mModule.detach();
mModule = null;
@@ -1305,28 +1156,14 @@
* @param modelData Model data to be used for recognition
* @return True if recognition is allowed to run at this time. False if not.
*/
+ @GuardedBy("mLock")
private boolean isRecognitionAllowedByDeviceState(ModelData modelData) {
- // if mRecognitionRequested is false, call and power state listeners are not registered so
- // we read current state directly from services
- if (!mRecognitionRequested) {
- mCallActive = mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK;
- mSoundTriggerPowerSaveMode = mPowerManager.getSoundTriggerPowerSaveMode();
- }
-
- return !mCallActive && isRecognitionAllowedByPowerState(
- modelData);
- }
-
- /**
- * Helper function to validate if a recognition should run based on the current power state
- *
- * @param modelData Model data to be used for recognition
- * @return True if device state allows recognition to run, false if not.
- */
- private boolean isRecognitionAllowedByPowerState(ModelData modelData) {
- return mSoundTriggerPowerSaveMode == PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED
- || (mSoundTriggerPowerSaveMode == PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY
- && modelData.shouldRunInBatterySaverMode());
+ return switch (mDeviceState) {
+ case DISABLE -> false;
+ case CRITICAL -> modelData.shouldRunInBatterySaverMode();
+ case ENABLE -> true;
+ default -> throw new AssertionError("Enum changed between compile and runtime");
+ };
}
// A single routine that implements the start recognition logic for both generic and keyphrase
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index b062e6b..3151781 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -35,14 +35,18 @@
import static com.android.server.utils.EventLogger.Event.ALOGW;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.server.soundtrigger.DeviceStateHandler.DeviceStateListener;
+import static com.android.server.soundtrigger.DeviceStateHandler.SoundTriggerDeviceState;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.PermissionChecker;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
@@ -85,6 +89,8 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;
@@ -112,6 +118,8 @@
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@@ -131,8 +139,8 @@
private static final boolean DEBUG = true;
private static final int SESSION_MAX_EVENT_SIZE = 128;
- final Context mContext;
- private Object mLock;
+ private final Context mContext;
+ private final Object mLock = new Object();
private final SoundTriggerServiceStub mServiceStub;
private final LocalSoundTriggerService mLocalSoundTriggerService;
@@ -140,6 +148,7 @@
private SoundTriggerDbHelper mDbHelper;
private final EventLogger mServiceEventLogger = new EventLogger(256, "Service");
+ private final EventLogger mDeviceEventLogger = new EventLogger(256, "Device Event");
private final Set<EventLogger> mSessionEventLoggers = ConcurrentHashMap.newKeySet(4);
private final Deque<EventLogger> mDetachedSessionEventLoggers = new LinkedBlockingDeque<>(4);
@@ -223,13 +232,18 @@
@GuardedBy("mLock")
private final ArrayMap<String, NumOps> mNumOpsPerPackage = new ArrayMap<>();
+ private final DeviceStateHandler mDeviceStateHandler;
+ private final Executor mDeviceStateHandlerExecutor = Executors.newSingleThreadExecutor();
+ private PhoneCallStateHandler mPhoneCallStateHandler;
+
public SoundTriggerService(Context context) {
super(context);
mContext = context;
mServiceStub = new SoundTriggerServiceStub();
mLocalSoundTriggerService = new LocalSoundTriggerService(context);
- mLock = new Object();
mSoundModelStatTracker = new SoundModelStatTracker();
+ mDeviceStateHandler = new DeviceStateHandler(mDeviceStateHandlerExecutor,
+ mDeviceEventLogger);
}
@Override
@@ -243,6 +257,29 @@
Slog.d(TAG, "onBootPhase: " + phase + " : " + isSafeMode());
if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
mDbHelper = new SoundTriggerDbHelper(mContext);
+ final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
+ // Hook up power state listener
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!PowerManager.ACTION_POWER_SAVE_MODE_CHANGED
+ .equals(intent.getAction())) {
+ return;
+ }
+ mDeviceStateHandler.onPowerModeChanged(
+ powerManager.getSoundTriggerPowerSaveMode());
+ }
+ }, new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
+ // Initialize the initial power state
+ // Do so after registering the listener so we ensure that we don't drop any events
+ mDeviceStateHandler.onPowerModeChanged(powerManager.getSoundTriggerPowerSaveMode());
+
+ // PhoneCallStateHandler initializes the original call state
+ mPhoneCallStateHandler = new PhoneCallStateHandler(
+ mContext.getSystemService(SubscriptionManager.class),
+ mContext.getSystemService(TelephonyManager.class),
+ mDeviceStateHandler);
}
mMiddlewareService = ISoundTriggerMiddlewareService.Stub.asInterface(
ServiceManager.waitForService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE));
@@ -296,7 +333,10 @@
// 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
@@ -377,6 +417,9 @@
// Event loggers
pw.println("##Service-Wide logs:");
mServiceEventLogger.dump(pw, /* indent = */ " ");
+ pw.println("\n##Device state logs:");
+ mDeviceStateHandler.dump(pw);
+ mDeviceEventLogger.dump(pw, /* indent = */ " ");
pw.println("\n##Active Session dumps:\n");
for (var sessionLogger : mSessionEventLoggers) {
@@ -400,6 +443,7 @@
class SoundTriggerSessionStub extends ISoundTriggerSession.Stub {
private final SoundTriggerHelper mSoundTriggerHelper;
+ private final DeviceStateListener mListener;
// Used to detect client death.
private final IBinder mClient;
private final Identity mOriginatorIdentity;
@@ -421,6 +465,9 @@
} catch (RemoteException e) {
clientDied();
}
+ mListener = (SoundTriggerDeviceState state)
+ -> mSoundTriggerHelper.onDeviceStateChanged(state);
+ mDeviceStateHandler.registerListener(mListener);
}
@Override
@@ -871,9 +918,9 @@
}
private void detach() {
+ mDeviceStateHandler.unregisterListener(mListener);
mSoundTriggerHelper.detach();
- mSessionEventLoggers.remove(mEventLogger);
- addDetachedSessionLogger(mEventLogger);
+ detachSessionLogger(mEventLogger);
}
private void enforceCallingPermission(String permission) {
@@ -888,7 +935,8 @@
private void enforceDetectionPermissions(ComponentName detectionService) {
PackageManager packageManager = mContext.getPackageManager();
String packageName = detectionService.getPackageName();
- if (packageManager.checkPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD, packageName)
+ if (packageManager.checkPermission(
+ Manifest.permission.CAPTURE_AUDIO_HOTWORD, packageName)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException(detectionService.getPackageName() + " does not have"
+ " permission " + Manifest.permission.CAPTURE_AUDIO_HOTWORD);
@@ -1574,6 +1622,7 @@
private final @NonNull IBinder mClient;
private final EventLogger mEventLogger;
private final Identity mOriginatorIdentity;
+ private final @NonNull DeviceStateListener mListener;
private final SparseArray<UUID> mModelUuid = new SparseArray<>(1);
@@ -1592,6 +1641,9 @@
} catch (RemoteException e) {
clientDied();
}
+ mListener = (SoundTriggerDeviceState state)
+ -> mSoundTriggerHelper.onDeviceStateChanged(state);
+ mDeviceStateHandler.registerListener(mListener);
}
@Override
@@ -1659,8 +1711,8 @@
private void detachInternal() {
mEventLogger.enqueue(new SessionEvent(Type.DETACH, null));
- mSessionEventLoggers.remove(mEventLogger);
- addDetachedSessionLogger(mEventLogger);
+ detachSessionLogger(mEventLogger);
+ mDeviceStateHandler.unregisterListener(mListener);
mSoundTriggerHelper.detach();
}
}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/AidlUtil.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/AidlUtil.java
index f3457f5..56a159e 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/AidlUtil.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/AidlUtil.java
@@ -21,6 +21,8 @@
import android.media.soundtrigger.RecognitionEvent;
import android.media.soundtrigger.RecognitionStatus;
import android.media.soundtrigger.SoundModelType;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
/**
* Utilities for working with sound trigger related AIDL generated types.
@@ -49,23 +51,29 @@
/**
* Creates a new generic abort event.
+ *
* @return The new event.
*/
- static RecognitionEvent newAbortEvent() {
- RecognitionEvent event = newEmptyRecognitionEvent();
- event.type = SoundModelType.GENERIC;
- event.status = RecognitionStatus.ABORTED;
- return event;
+ static RecognitionEventSys newAbortEvent() {
+ RecognitionEvent recognitionEvent = newEmptyRecognitionEvent();
+ recognitionEvent.type = SoundModelType.GENERIC;
+ recognitionEvent.status = RecognitionStatus.ABORTED;
+ RecognitionEventSys recognitionEventSys = new RecognitionEventSys();
+ recognitionEventSys.recognitionEvent = recognitionEvent;
+ return recognitionEventSys;
}
/**
* Creates a new generic phrase event.
+ *
* @return The new event.
*/
- static PhraseRecognitionEvent newAbortPhraseEvent() {
- PhraseRecognitionEvent event = newEmptyPhraseRecognitionEvent();
- event.common.type = SoundModelType.KEYPHRASE;
- event.common.status = RecognitionStatus.ABORTED;
- return event;
+ static PhraseRecognitionEventSys newAbortPhraseEvent() {
+ PhraseRecognitionEvent recognitionEvent = newEmptyPhraseRecognitionEvent();
+ recognitionEvent.common.type = SoundModelType.KEYPHRASE;
+ recognitionEvent.common.status = RecognitionStatus.ABORTED;
+ PhraseRecognitionEventSys phraseRecognitionEventSys = new PhraseRecognitionEventSys();
+ phraseRecognitionEventSys.phraseRecognitionEvent = recognitionEvent;
+ return phraseRecognitionEventSys;
}
}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ISoundTriggerHal.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ISoundTriggerHal.java
index 75206e6..6f4a946 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ISoundTriggerHal.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ISoundTriggerHal.java
@@ -20,12 +20,12 @@
import android.hardware.soundtrigger3.ISoundTriggerHwCallback;
import android.hardware.soundtrigger3.ISoundTriggerHwGlobalCallback;
import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
import android.media.soundtrigger.PhraseSoundModel;
import android.media.soundtrigger.Properties;
import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
import android.os.IBinder;
/**
@@ -173,14 +173,19 @@
*/
interface ModelCallback {
/**
- * @see ISoundTriggerHwCallback#recognitionCallback(int, RecognitionEvent)
+ * Decorated callback of
+ * {@link ISoundTriggerHwCallback#recognitionCallback(int, RecognitionEvent)} where
+ * {@link RecognitionEventSys} is decorating the returned {@link RecognitionEvent}
*/
- void recognitionCallback(int modelHandle, RecognitionEvent event);
+ void recognitionCallback(int modelHandle, RecognitionEventSys event);
/**
- * @see ISoundTriggerHwCallback#phraseRecognitionCallback(int, PhraseRecognitionEvent)
+ * Decorated callback of
+ * {@link ISoundTriggerHwCallback#phraseRecognitionCallback(int, PhraseRecognitionEvent)}
+ * where {@link PhraseRecognitionEventSys} is decorating the returned
+ * {@link PhraseRecognitionEvent}
*/
- void phraseRecognitionCallback(int modelHandle, PhraseRecognitionEvent event);
+ void phraseRecognitionCallback(int modelHandle, PhraseRecognitionEventSys event);
/**
* @see ISoundTriggerHwCallback#modelUnloaded(int)
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
index 8c7cabe..d8ef2b6 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
@@ -19,14 +19,14 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
import android.media.soundtrigger.PhraseSoundModel;
import android.media.soundtrigger.Properties;
import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
import android.media.soundtrigger.SoundModel;
import android.media.soundtrigger.SoundModelType;
import android.media.soundtrigger.Status;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
import android.os.IBinder;
import java.util.HashSet;
@@ -238,13 +238,13 @@
}
@Override
- public void recognitionCallback(int modelHandle, RecognitionEvent event) {
+ public void recognitionCallback(int modelHandle, RecognitionEventSys event) {
synchronized (mActiveModels) {
if (!mActiveModels.contains(modelHandle)) {
// Discard the event.
return;
}
- if (!event.recognitionStillActive) {
+ if (!event.recognitionEvent.recognitionStillActive) {
mActiveModels.remove(modelHandle);
}
// A recognition event must be the last one for its model, unless it indicates that
@@ -255,13 +255,13 @@
}
@Override
- public void phraseRecognitionCallback(int modelHandle, PhraseRecognitionEvent event) {
+ public void phraseRecognitionCallback(int modelHandle, PhraseRecognitionEventSys event) {
synchronized (mActiveModels) {
if (!mActiveModels.contains(modelHandle)) {
// Discard the event.
return;
}
- if (!event.common.recognitionStillActive) {
+ if (!event.phraseRecognitionEvent.common.recognitionStillActive) {
mActiveModels.remove(modelHandle);
}
// A recognition event must be the last one for its model, unless it indicates that
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
index 24741e1..bac2466 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
@@ -17,14 +17,14 @@
package com.android.server.soundtrigger_middleware;
import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
import android.media.soundtrigger.PhraseSoundModel;
import android.media.soundtrigger.Properties;
import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
import android.media.soundtrigger.RecognitionStatus;
import android.media.soundtrigger.SoundModel;
import android.media.soundtrigger.Status;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
import android.os.DeadObjectException;
import android.os.IBinder;
import android.util.Log;
@@ -253,7 +253,7 @@
}
@Override
- public void recognitionCallback(int model, RecognitionEvent event) {
+ public void recognitionCallback(int model, RecognitionEventSys event) {
synchronized (mModelStates) {
ModelState state = mModelStates.get(model);
if (state == null || state == ModelState.INACTIVE) {
@@ -261,15 +261,16 @@
reboot();
return;
}
- if (event.recognitionStillActive && event.status != RecognitionStatus.SUCCESS
- && event.status != RecognitionStatus.FORCED) {
+ if (event.recognitionEvent.recognitionStillActive
+ && event.recognitionEvent.status != RecognitionStatus.SUCCESS
+ && event.recognitionEvent.status != RecognitionStatus.FORCED) {
Log.wtfStack(TAG,
"recognitionStillActive is only allowed when the recognition status "
+ "is SUCCESS");
reboot();
return;
}
- if (!event.recognitionStillActive) {
+ if (!event.recognitionEvent.recognitionStillActive) {
mModelStates.replace(model, ModelState.INACTIVE);
}
}
@@ -278,7 +279,7 @@
}
@Override
- public void phraseRecognitionCallback(int model, PhraseRecognitionEvent event) {
+ public void phraseRecognitionCallback(int model, PhraseRecognitionEventSys event) {
synchronized (mModelStates) {
ModelState state = mModelStates.get(model);
if (state == null || state == ModelState.INACTIVE) {
@@ -286,16 +287,16 @@
reboot();
return;
}
- if (event.common.recognitionStillActive
- && event.common.status != RecognitionStatus.SUCCESS
- && event.common.status != RecognitionStatus.FORCED) {
+ if (event.phraseRecognitionEvent.common.recognitionStillActive
+ && event.phraseRecognitionEvent.common.status != RecognitionStatus.SUCCESS
+ && event.phraseRecognitionEvent.common.status != RecognitionStatus.FORCED) {
Log.wtfStack(TAG,
"recognitionStillActive is only allowed when the recognition status "
+ "is SUCCESS");
reboot();
return;
}
- if (!event.common.recognitionStillActive) {
+ if (!event.phraseRecognitionEvent.common.recognitionStillActive) {
mModelStates.replace(model, ModelState.INACTIVE);
}
}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
index c67bdd7..df2e9b4 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
@@ -25,9 +25,12 @@
import android.media.soundtrigger.RecognitionConfig;
import android.media.soundtrigger.SoundModel;
import android.media.soundtrigger.Status;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
import android.os.IBinder;
import android.os.IHwBinder;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.system.OsConstants;
import android.util.Log;
@@ -570,16 +573,20 @@
public void recognitionCallback_2_1(
android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event,
int cookie) {
- mDelegate.recognitionCallback(event.header.model,
- ConversionUtil.hidl2aidlRecognitionEvent(event));
+ RecognitionEventSys eventSys = new RecognitionEventSys();
+ eventSys.recognitionEvent = ConversionUtil.hidl2aidlRecognitionEvent(event);
+ eventSys.halEventReceivedMillis = SystemClock.elapsedRealtime();
+ mDelegate.recognitionCallback(event.header.model, eventSys);
}
@Override
public void phraseRecognitionCallback_2_1(
android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent event,
int cookie) {
- mDelegate.phraseRecognitionCallback(event.common.header.model,
- ConversionUtil.hidl2aidlPhraseRecognitionEvent(event));
+ PhraseRecognitionEventSys eventSys = new PhraseRecognitionEventSys();
+ eventSys.phraseRecognitionEvent = ConversionUtil.hidl2aidlPhraseRecognitionEvent(event);
+ eventSys.halEventReceivedMillis = SystemClock.elapsedRealtime();
+ mDelegate.phraseRecognitionCallback(event.common.header.model, eventSys);
}
@Override
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java
index 8bb5eb1..b1165bb 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw3Compat.java
@@ -29,9 +29,12 @@
import android.media.soundtrigger.RecognitionStatus;
import android.media.soundtrigger.SoundModel;
import android.media.soundtrigger.Status;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import android.os.SystemClock;
public class SoundTriggerHw3Compat implements ISoundTriggerHal {
private final @NonNull ISoundTriggerHw mDriver;
@@ -244,14 +247,20 @@
public void phraseRecognitionCallback(int model, PhraseRecognitionEvent event) {
// A FORCED status implies that recognition is still active after the event.
event.common.recognitionStillActive |= event.common.status == RecognitionStatus.FORCED;
- mDelegate.phraseRecognitionCallback(model, event);
+ PhraseRecognitionEventSys phraseRecognitionEventSys = new PhraseRecognitionEventSys();
+ phraseRecognitionEventSys.phraseRecognitionEvent = event;
+ phraseRecognitionEventSys.halEventReceivedMillis = SystemClock.elapsedRealtimeNanos();
+ mDelegate.phraseRecognitionCallback(model, phraseRecognitionEventSys);
}
@Override
public void recognitionCallback(int model, RecognitionEvent event) {
// A FORCED status implies that recognition is still active after the event.
event.recognitionStillActive |= event.status == RecognitionStatus.FORCED;
- mDelegate.recognitionCallback(model, event);
+ RecognitionEventSys recognitionEventSys = new RecognitionEventSys();
+ recognitionEventSys.recognitionEvent = event;
+ recognitionEventSys.halEventReceivedMillis = SystemClock.elapsedRealtimeNanos();
+ mDelegate.recognitionCallback(model, recognitionEventSys);
}
@Override
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
index 0e796d1..2ee4e3c 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
@@ -16,12 +16,24 @@
package com.android.server.soundtrigger_middleware;
-import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.*;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.DETACH;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.FORCE_RECOGNITION;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.GET_MODEL_PARAMETER;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.LOAD_MODEL;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.LOAD_PHRASE_MODEL;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.MODEL_UNLOADED;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.MODULE_DIED;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.QUERY_MODEL_PARAMETER;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.RECOGNITION;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.RESOURCES_AVAILABLE;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.SET_MODEL_PARAMETER;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.START_RECOGNITION;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.STOP_RECOGNITION;
+import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.UNLOAD_MODEL;
import static com.android.server.utils.EventLogger.Event.ALOGI;
import static com.android.server.utils.EventLogger.Event.ALOGW;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.Context;
import android.media.permission.Identity;
import android.media.permission.IdentityContext;
@@ -29,11 +41,12 @@
import android.media.soundtrigger.PhraseRecognitionEvent;
import android.media.soundtrigger.PhraseSoundModel;
import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
import android.media.soundtrigger.RecognitionStatus;
import android.media.soundtrigger.SoundModel;
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
import android.os.BatteryStatsInternal;
import android.os.IBinder;
@@ -45,19 +58,18 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.LatencyTracker;
import com.android.server.LocalServices;
-import com.android.server.utils.EventLogger.Event;
import com.android.server.utils.EventLogger;
-
+import com.android.server.utils.EventLogger.Event;
import java.io.PrintWriter;
import java.util.Arrays;
+import java.util.Deque;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
-import java.util.Deque;
/**
@@ -370,7 +382,7 @@
}
@Override
- public void onRecognition(int modelHandle, RecognitionEvent event, int captureSession)
+ public void onRecognition(int modelHandle, RecognitionEventSys event, int captureSession)
throws RemoteException {
try {
mBatteryStatsInternalSupplier.get().noteWakingSoundTrigger(
@@ -388,13 +400,13 @@
}
@Override
- public void onPhraseRecognition(int modelHandle, PhraseRecognitionEvent event,
+ public void onPhraseRecognition(int modelHandle, PhraseRecognitionEventSys event,
int captureSession)
throws RemoteException {
try {
mBatteryStatsInternalSupplier.get().noteWakingSoundTrigger(
SystemClock.elapsedRealtime(), mOriginatorIdentity.uid);
- startKeyphraseEventLatencyTracking(event);
+ startKeyphraseEventLatencyTracking(event.phraseRecognitionEvent);
mCallbackDelegate.onPhraseRecognition(modelHandle, event, captureSession);
mEventLogger.enqueue(SessionEvent.createForVoid(
RECOGNITION, modelHandle, event, captureSession)
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 13fe14c..00b894e 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
@@ -27,15 +27,15 @@
import android.media.permission.IdentityContext;
import android.media.permission.PermissionUtil;
import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
import android.media.soundtrigger.PhraseSoundModel;
import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
import android.media.soundtrigger.SoundModel;
import android.media.soundtrigger.Status;
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
import android.os.IBinder;
import android.os.RemoteException;
@@ -78,7 +78,7 @@
public @NonNull
SoundTriggerModuleDescriptor[] listModules() {
Identity identity = getIdentity();
- enforcePermissionsForPreflight(identity);
+ enforcePermissionForPreflight(mContext, identity, CAPTURE_AUDIO_HOTWORD);
return mDelegate.listModules();
}
@@ -307,16 +307,15 @@
}
@Override
- public void onRecognition(int modelHandle, RecognitionEvent event, int captureSession)
- throws RemoteException {
+ public void onRecognition(int modelHandle, RecognitionEventSys event,
+ int captureSession) throws RemoteException {
enforcePermissions("Sound trigger recognition.");
mDelegate.onRecognition(modelHandle, event, captureSession);
}
@Override
- public void onPhraseRecognition(int modelHandle, PhraseRecognitionEvent event,
- int captureSession)
- throws RemoteException {
+ public void onPhraseRecognition(int modelHandle, PhraseRecognitionEventSys event,
+ int captureSession) throws RemoteException {
enforcePermissions("Sound trigger phrase recognition.");
mDelegate.onPhraseRecognition(modelHandle, event, captureSession);
}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
index 15c9ba9..f208c03 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
@@ -21,17 +21,17 @@
import android.media.permission.Identity;
import android.media.permission.IdentityContext;
import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
import android.media.soundtrigger.PhraseSoundModel;
import android.media.soundtrigger.Properties;
import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
import android.media.soundtrigger.RecognitionStatus;
import android.media.soundtrigger.SoundModel;
import android.media.soundtrigger.Status;
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
import android.os.IBinder;
import android.os.RemoteException;
@@ -710,8 +710,7 @@
}
}
- class CallbackWrapper implements ISoundTriggerCallback,
- IBinder.DeathRecipient {
+ class CallbackWrapper implements ISoundTriggerCallback, IBinder.DeathRecipient {
private final ISoundTriggerCallback mCallback;
CallbackWrapper(ISoundTriggerCallback callback) {
@@ -728,11 +727,11 @@
}
@Override
- public void onRecognition(int modelHandle, @NonNull RecognitionEvent event,
+ public void onRecognition(int modelHandle, @NonNull RecognitionEventSys event,
int captureSession) {
synchronized (SoundTriggerMiddlewareValidation.this) {
ModelState modelState = mLoadedModels.get(modelHandle);
- if (!event.recognitionStillActive) {
+ if (!event.recognitionEvent.recognitionStillActive) {
modelState.activityState = ModelState.Activity.LOADED;
}
}
@@ -744,7 +743,7 @@
Log.w(TAG, "Client callback exception.", e);
synchronized (SoundTriggerMiddlewareValidation.this) {
ModelState modelState = mLoadedModels.get(modelHandle);
- if (event.status != RecognitionStatus.FORCED) {
+ if (event.recognitionEvent.status != RecognitionStatus.FORCED) {
modelState.activityState = ModelState.Activity.INTERCEPTED;
// If we failed to deliver an actual event to the client, they would
// never know to restart it whenever circumstances change. Thus, we
@@ -758,10 +757,10 @@
@Override
public void onPhraseRecognition(int modelHandle,
- @NonNull PhraseRecognitionEvent event, int captureSession) {
+ @NonNull PhraseRecognitionEventSys event, int captureSession) {
synchronized (SoundTriggerMiddlewareValidation.this) {
ModelState modelState = mLoadedModels.get(modelHandle);
- if (!event.common.recognitionStillActive) {
+ if (!event.phraseRecognitionEvent.common.recognitionStillActive) {
modelState.activityState = ModelState.Activity.LOADED;
}
}
@@ -773,7 +772,7 @@
Log.w(TAG, "Client callback exception.", e);
synchronized (SoundTriggerMiddlewareValidation.this) {
ModelState modelState = mLoadedModels.get(modelHandle);
- if (!event.common.recognitionStillActive) {
+ if (!event.phraseRecognitionEvent.common.recognitionStillActive) {
modelState.activityState = ModelState.Activity.INTERCEPTED;
// If we failed to deliver an actual event to the client, they would
// never know to restart it whenever circumstances change. Thus, we
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 6223b2e..84cec55 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -19,16 +19,16 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.media.soundtrigger.ModelParameterRange;
-import android.media.soundtrigger.PhraseRecognitionEvent;
import android.media.soundtrigger.PhraseSoundModel;
import android.media.soundtrigger.Properties;
import android.media.soundtrigger.RecognitionConfig;
-import android.media.soundtrigger.RecognitionEvent;
import android.media.soundtrigger.SoundModel;
import android.media.soundtrigger.SoundModelType;
import android.media.soundtrigger.Status;
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
+import android.media.soundtrigger_middleware.RecognitionEventSys;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -499,10 +499,10 @@
@Override
public void recognitionCallback(int modelHandle,
- @NonNull RecognitionEvent recognitionEvent) {
+ @NonNull RecognitionEventSys event) {
ISoundTriggerCallback callback;
synchronized (SoundTriggerModule.this) {
- if (!recognitionEvent.recognitionStillActive) {
+ if (!event.recognitionEvent.recognitionStillActive) {
setState(ModelState.LOADED);
}
callback = mCallback;
@@ -510,7 +510,7 @@
// The callback must be invoked outside of the lock.
try {
if (callback != null) {
- callback.onRecognition(mHandle, recognitionEvent, mSession.mSessionHandle);
+ callback.onRecognition(mHandle, event, mSession.mSessionHandle);
}
} catch (RemoteException e) {
// We're not expecting any exceptions here.
@@ -520,10 +520,10 @@
@Override
public void phraseRecognitionCallback(int modelHandle,
- @NonNull PhraseRecognitionEvent phraseRecognitionEvent) {
+ @NonNull PhraseRecognitionEventSys event) {
ISoundTriggerCallback callback;
synchronized (SoundTriggerModule.this) {
- if (!phraseRecognitionEvent.common.recognitionStillActive) {
+ if (!event.phraseRecognitionEvent.common.recognitionStillActive) {
setState(ModelState.LOADED);
}
callback = mCallback;
@@ -532,8 +532,7 @@
// The callback must be invoked outside of the lock.
try {
if (callback != null) {
- mCallback.onPhraseRecognition(mHandle, phraseRecognitionEvent,
- mSession.mSessionHandle);
+ mCallback.onPhraseRecognition(mHandle, event, mSession.mSessionHandle);
}
} catch (RemoteException e) {
// We're not expecting any exceptions here.
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 486945d..edaaf3f 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 @@
}
// 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();
}
}
diff --git a/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/RemoteWallpaperEffectsGenerationService.java b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/RemoteWallpaperEffectsGenerationService.java
index c228daf..61f34c2 100644
--- a/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/RemoteWallpaperEffectsGenerationService.java
+++ b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/RemoteWallpaperEffectsGenerationService.java
@@ -40,6 +40,8 @@
private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
+ private static final long TIMEOUT_IDLE_BIND_MILLIS = 120 * DateUtils.SECOND_IN_MILLIS;
+
private final RemoteWallpaperEffectsGenerationServiceCallback mCallback;
public RemoteWallpaperEffectsGenerationService(Context context,
@@ -62,7 +64,7 @@
@Override
protected long getTimeoutIdleBindMillis() {
- return PERMANENT_BOUND_TIMEOUT_MS;
+ return TIMEOUT_IDLE_BIND_MILLIS;
}
@Override
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index 2704418..6997f3c7 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -360,6 +360,11 @@
*/
public static final int INCOMING_AUTO_REJECTED = 81;
+ /**
+ * Indicates that the call was unable to be made because the satellite modem is enabled.
+ * @hide
+ */
+ public static final int SATELLITE_ENABLED = 82;
//*********************************************************************************************
// When adding a disconnect type:
@@ -379,168 +384,170 @@
@UnsupportedAppUsage
public static @NonNull String toString(int cause) {
switch (cause) {
- case NOT_DISCONNECTED:
- return "NOT_DISCONNECTED";
- case INCOMING_MISSED:
- return "INCOMING_MISSED";
- case NORMAL:
- return "NORMAL";
- case LOCAL:
- return "LOCAL";
- case BUSY:
- return "BUSY";
- case CONGESTION:
- return "CONGESTION";
- case INVALID_NUMBER:
- return "INVALID_NUMBER";
- case NUMBER_UNREACHABLE:
- return "NUMBER_UNREACHABLE";
- case SERVER_UNREACHABLE:
- return "SERVER_UNREACHABLE";
- case INVALID_CREDENTIALS:
- return "INVALID_CREDENTIALS";
- case OUT_OF_NETWORK:
- return "OUT_OF_NETWORK";
- case SERVER_ERROR:
- return "SERVER_ERROR";
- case TIMED_OUT:
- return "TIMED_OUT";
- case LOST_SIGNAL:
- return "LOST_SIGNAL";
- case LIMIT_EXCEEDED:
- return "LIMIT_EXCEEDED";
- case INCOMING_REJECTED:
- return "INCOMING_REJECTED";
- case POWER_OFF:
- return "POWER_OFF";
- case OUT_OF_SERVICE:
- return "OUT_OF_SERVICE";
- case ICC_ERROR:
- return "ICC_ERROR";
- case CALL_BARRED:
- return "CALL_BARRED";
- case FDN_BLOCKED:
- return "FDN_BLOCKED";
- case CS_RESTRICTED:
- return "CS_RESTRICTED";
- case CS_RESTRICTED_NORMAL:
- return "CS_RESTRICTED_NORMAL";
- case CS_RESTRICTED_EMERGENCY:
- return "CS_RESTRICTED_EMERGENCY";
- case UNOBTAINABLE_NUMBER:
- return "UNOBTAINABLE_NUMBER";
- case CDMA_LOCKED_UNTIL_POWER_CYCLE:
- return "CDMA_LOCKED_UNTIL_POWER_CYCLE";
- case CDMA_DROP:
- return "CDMA_DROP";
- case CDMA_INTERCEPT:
- return "CDMA_INTERCEPT";
- case CDMA_REORDER:
- return "CDMA_REORDER";
- case CDMA_SO_REJECT:
- return "CDMA_SO_REJECT";
- case CDMA_RETRY_ORDER:
- return "CDMA_RETRY_ORDER";
- case CDMA_ACCESS_FAILURE:
- return "CDMA_ACCESS_FAILURE";
- case CDMA_PREEMPTED:
- return "CDMA_PREEMPTED";
- case CDMA_NOT_EMERGENCY:
- return "CDMA_NOT_EMERGENCY";
- case CDMA_ACCESS_BLOCKED:
- return "CDMA_ACCESS_BLOCKED";
- case EMERGENCY_ONLY:
- return "EMERGENCY_ONLY";
- case NO_PHONE_NUMBER_SUPPLIED:
- return "NO_PHONE_NUMBER_SUPPLIED";
- case DIALED_MMI:
- return "DIALED_MMI";
- case VOICEMAIL_NUMBER_MISSING:
- return "VOICEMAIL_NUMBER_MISSING";
- case CDMA_CALL_LOST:
- return "CDMA_CALL_LOST";
- case EXITED_ECM:
- return "EXITED_ECM";
- case DIAL_MODIFIED_TO_USSD:
- return "DIAL_MODIFIED_TO_USSD";
- case DIAL_MODIFIED_TO_SS:
- return "DIAL_MODIFIED_TO_SS";
- case DIAL_MODIFIED_TO_DIAL:
- return "DIAL_MODIFIED_TO_DIAL";
- case DIAL_MODIFIED_TO_DIAL_VIDEO:
- return "DIAL_MODIFIED_TO_DIAL_VIDEO";
- case DIAL_VIDEO_MODIFIED_TO_SS:
- return "DIAL_VIDEO_MODIFIED_TO_SS";
- case DIAL_VIDEO_MODIFIED_TO_USSD:
- return "DIAL_VIDEO_MODIFIED_TO_USSD";
- case DIAL_VIDEO_MODIFIED_TO_DIAL:
- return "DIAL_VIDEO_MODIFIED_TO_DIAL";
- case DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO:
- return "DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO";
- case ERROR_UNSPECIFIED:
- return "ERROR_UNSPECIFIED";
- case OUTGOING_FAILURE:
- return "OUTGOING_FAILURE";
- case OUTGOING_CANCELED:
- return "OUTGOING_CANCELED";
- case IMS_MERGED_SUCCESSFULLY:
- return "IMS_MERGED_SUCCESSFULLY";
- case CDMA_ALREADY_ACTIVATED:
- return "CDMA_ALREADY_ACTIVATED";
- case VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED:
- return "VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED";
- case CALL_PULLED:
- return "CALL_PULLED";
- case ANSWERED_ELSEWHERE:
- return "ANSWERED_ELSEWHERE";
- case MAXIMUM_NUMBER_OF_CALLS_REACHED:
- return "MAXIMUM_NUMER_OF_CALLS_REACHED";
- case DATA_DISABLED:
- return "DATA_DISABLED";
- case DATA_LIMIT_REACHED:
- return "DATA_LIMIT_REACHED";
- case DIALED_CALL_FORWARDING_WHILE_ROAMING:
- return "DIALED_CALL_FORWARDING_WHILE_ROAMING";
- case IMEI_NOT_ACCEPTED:
- return "IMEI_NOT_ACCEPTED";
- case WIFI_LOST:
- return "WIFI_LOST";
- case IMS_ACCESS_BLOCKED:
- return "IMS_ACCESS_BLOCKED";
- case LOW_BATTERY:
- return "LOW_BATTERY";
- case DIAL_LOW_BATTERY:
- return "DIAL_LOW_BATTERY";
- case EMERGENCY_TEMP_FAILURE:
- return "EMERGENCY_TEMP_FAILURE";
- case EMERGENCY_PERM_FAILURE:
- return "EMERGENCY_PERM_FAILURE";
- case NORMAL_UNSPECIFIED:
- return "NORMAL_UNSPECIFIED";
- case IMS_SIP_ALTERNATE_EMERGENCY_CALL:
- return "IMS_SIP_ALTERNATE_EMERGENCY_CALL";
- case ALREADY_DIALING:
- return "ALREADY_DIALING";
- case CANT_CALL_WHILE_RINGING:
- return "CANT_CALL_WHILE_RINGING";
- case CALLING_DISABLED:
- return "CALLING_DISABLED";
- case TOO_MANY_ONGOING_CALLS:
- return "TOO_MANY_ONGOING_CALLS";
- case OTASP_PROVISIONING_IN_PROCESS:
- return "OTASP_PROVISIONING_IN_PROCESS";
- case MEDIA_TIMEOUT:
- return "MEDIA_TIMEOUT";
- case EMERGENCY_CALL_OVER_WFC_NOT_AVAILABLE:
- return "EMERGENCY_CALL_OVER_WFC_NOT_AVAILABLE";
- case WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION:
- return "WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION";
- case OUTGOING_EMERGENCY_CALL_PLACED:
- return "OUTGOING_EMERGENCY_CALL_PLACED";
+ case NOT_DISCONNECTED:
+ return "NOT_DISCONNECTED";
+ case INCOMING_MISSED:
+ return "INCOMING_MISSED";
+ case NORMAL:
+ return "NORMAL";
+ case LOCAL:
+ return "LOCAL";
+ case BUSY:
+ return "BUSY";
+ case CONGESTION:
+ return "CONGESTION";
+ case INVALID_NUMBER:
+ return "INVALID_NUMBER";
+ case NUMBER_UNREACHABLE:
+ return "NUMBER_UNREACHABLE";
+ case SERVER_UNREACHABLE:
+ return "SERVER_UNREACHABLE";
+ case INVALID_CREDENTIALS:
+ return "INVALID_CREDENTIALS";
+ case OUT_OF_NETWORK:
+ return "OUT_OF_NETWORK";
+ case SERVER_ERROR:
+ return "SERVER_ERROR";
+ case TIMED_OUT:
+ return "TIMED_OUT";
+ case LOST_SIGNAL:
+ return "LOST_SIGNAL";
+ case LIMIT_EXCEEDED:
+ return "LIMIT_EXCEEDED";
+ case INCOMING_REJECTED:
+ return "INCOMING_REJECTED";
+ case POWER_OFF:
+ return "POWER_OFF";
+ case OUT_OF_SERVICE:
+ return "OUT_OF_SERVICE";
+ case ICC_ERROR:
+ return "ICC_ERROR";
+ case CALL_BARRED:
+ return "CALL_BARRED";
+ case FDN_BLOCKED:
+ return "FDN_BLOCKED";
+ case CS_RESTRICTED:
+ return "CS_RESTRICTED";
+ case CS_RESTRICTED_NORMAL:
+ return "CS_RESTRICTED_NORMAL";
+ case CS_RESTRICTED_EMERGENCY:
+ return "CS_RESTRICTED_EMERGENCY";
+ case UNOBTAINABLE_NUMBER:
+ return "UNOBTAINABLE_NUMBER";
+ case CDMA_LOCKED_UNTIL_POWER_CYCLE:
+ return "CDMA_LOCKED_UNTIL_POWER_CYCLE";
+ case CDMA_DROP:
+ return "CDMA_DROP";
+ case CDMA_INTERCEPT:
+ return "CDMA_INTERCEPT";
+ case CDMA_REORDER:
+ return "CDMA_REORDER";
+ case CDMA_SO_REJECT:
+ return "CDMA_SO_REJECT";
+ case CDMA_RETRY_ORDER:
+ return "CDMA_RETRY_ORDER";
+ case CDMA_ACCESS_FAILURE:
+ return "CDMA_ACCESS_FAILURE";
+ case CDMA_PREEMPTED:
+ return "CDMA_PREEMPTED";
+ case CDMA_NOT_EMERGENCY:
+ return "CDMA_NOT_EMERGENCY";
+ case CDMA_ACCESS_BLOCKED:
+ return "CDMA_ACCESS_BLOCKED";
+ case EMERGENCY_ONLY:
+ return "EMERGENCY_ONLY";
+ case NO_PHONE_NUMBER_SUPPLIED:
+ return "NO_PHONE_NUMBER_SUPPLIED";
+ case DIALED_MMI:
+ return "DIALED_MMI";
+ case VOICEMAIL_NUMBER_MISSING:
+ return "VOICEMAIL_NUMBER_MISSING";
+ case CDMA_CALL_LOST:
+ return "CDMA_CALL_LOST";
+ case EXITED_ECM:
+ return "EXITED_ECM";
+ case DIAL_MODIFIED_TO_USSD:
+ return "DIAL_MODIFIED_TO_USSD";
+ case DIAL_MODIFIED_TO_SS:
+ return "DIAL_MODIFIED_TO_SS";
+ case DIAL_MODIFIED_TO_DIAL:
+ return "DIAL_MODIFIED_TO_DIAL";
+ case DIAL_MODIFIED_TO_DIAL_VIDEO:
+ return "DIAL_MODIFIED_TO_DIAL_VIDEO";
+ case DIAL_VIDEO_MODIFIED_TO_SS:
+ return "DIAL_VIDEO_MODIFIED_TO_SS";
+ case DIAL_VIDEO_MODIFIED_TO_USSD:
+ return "DIAL_VIDEO_MODIFIED_TO_USSD";
+ case DIAL_VIDEO_MODIFIED_TO_DIAL:
+ return "DIAL_VIDEO_MODIFIED_TO_DIAL";
+ case DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO:
+ return "DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO";
+ case ERROR_UNSPECIFIED:
+ return "ERROR_UNSPECIFIED";
+ case OUTGOING_FAILURE:
+ return "OUTGOING_FAILURE";
+ case OUTGOING_CANCELED:
+ return "OUTGOING_CANCELED";
+ case IMS_MERGED_SUCCESSFULLY:
+ return "IMS_MERGED_SUCCESSFULLY";
+ case CDMA_ALREADY_ACTIVATED:
+ return "CDMA_ALREADY_ACTIVATED";
+ case VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED:
+ return "VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED";
+ case CALL_PULLED:
+ return "CALL_PULLED";
+ case ANSWERED_ELSEWHERE:
+ return "ANSWERED_ELSEWHERE";
+ case MAXIMUM_NUMBER_OF_CALLS_REACHED:
+ return "MAXIMUM_NUMER_OF_CALLS_REACHED";
+ case DATA_DISABLED:
+ return "DATA_DISABLED";
+ case DATA_LIMIT_REACHED:
+ return "DATA_LIMIT_REACHED";
+ case DIALED_CALL_FORWARDING_WHILE_ROAMING:
+ return "DIALED_CALL_FORWARDING_WHILE_ROAMING";
+ case IMEI_NOT_ACCEPTED:
+ return "IMEI_NOT_ACCEPTED";
+ case WIFI_LOST:
+ return "WIFI_LOST";
+ case IMS_ACCESS_BLOCKED:
+ return "IMS_ACCESS_BLOCKED";
+ case LOW_BATTERY:
+ return "LOW_BATTERY";
+ case DIAL_LOW_BATTERY:
+ return "DIAL_LOW_BATTERY";
+ case EMERGENCY_TEMP_FAILURE:
+ return "EMERGENCY_TEMP_FAILURE";
+ case EMERGENCY_PERM_FAILURE:
+ return "EMERGENCY_PERM_FAILURE";
+ case NORMAL_UNSPECIFIED:
+ return "NORMAL_UNSPECIFIED";
+ case IMS_SIP_ALTERNATE_EMERGENCY_CALL:
+ return "IMS_SIP_ALTERNATE_EMERGENCY_CALL";
+ case ALREADY_DIALING:
+ return "ALREADY_DIALING";
+ case CANT_CALL_WHILE_RINGING:
+ return "CANT_CALL_WHILE_RINGING";
+ case CALLING_DISABLED:
+ return "CALLING_DISABLED";
+ case TOO_MANY_ONGOING_CALLS:
+ return "TOO_MANY_ONGOING_CALLS";
+ case OTASP_PROVISIONING_IN_PROCESS:
+ return "OTASP_PROVISIONING_IN_PROCESS";
+ case MEDIA_TIMEOUT:
+ return "MEDIA_TIMEOUT";
+ case EMERGENCY_CALL_OVER_WFC_NOT_AVAILABLE:
+ return "EMERGENCY_CALL_OVER_WFC_NOT_AVAILABLE";
+ case WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION:
+ return "WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION";
+ case OUTGOING_EMERGENCY_CALL_PLACED:
+ return "OUTGOING_EMERGENCY_CALL_PLACED";
case INCOMING_AUTO_REJECTED:
return "INCOMING_AUTO_REJECTED";
- default:
- return "INVALID: " + cause;
+ case SATELLITE_ENABLED:
+ return "SATELLITE_ENABLED";
+ default:
+ return "INVALID: " + cause;
}
}
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index c5830b8..431e9e6 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -1503,6 +1503,31 @@
}
}
+ /**
+ * Inform whether the device is aligned with the satellite for demo mode.
+ *
+ * @param isAligned {@true} Device is aligned with the satellite for demo mode
+ * {@false} Device is not aligned with the satellite for demo mode
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+
+ public void onDeviceAlignedWithSatellite(boolean isAligned) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ telephony.onDeviceAlignedWithSatellite(mSubId, isAligned);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("informDeviceAlignedToSatellite() RemoteException:" + ex);
+ ex.rethrowFromSystemServer();
+ }
+ }
+
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
.getTelephonyServiceManager()
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 18e4c37..21aad73 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2982,6 +2982,16 @@
void requestTimeForNextSatelliteVisibility(int subId, in ResultReceiver receiver);
/**
+ * Inform whether the device is aligned with the satellite within in margin for demo mode.
+ *
+ * @param isAligned {@true} Device is aligned with the satellite for demo mode
+ * {@false} Device is not aligned with the satellite for demo mode
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void onDeviceAlignedWithSatellite(int subId, in boolean isAligned);
+
+ /**
* This API can be used by only CTS to update satellite vendor service package name.
*
* @param servicePackageName The package name of the satellite vendor service.
@@ -3018,4 +3028,13 @@
* {@code false} otherwise.
*/
boolean setSatellitePointingUiClassName(in String packageName, in String className);
+
+ /**
+ * This API can be used by only CTS to update the timeout duration in milliseconds whether
+ * the device is aligned with the satellite for demo mode
+ *
+ * @param timeoutMillis The timeout duration in millisecond.
+ * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
+ */
+ boolean setSatelliteDeviceAlignedTimeoutDuration(long timeoutMillis);
}
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml
index 7272abb..32ff243 100644
--- a/tests/FlickerTests/AndroidTest.xml
+++ b/tests/FlickerTests/AndroidTest.xml
@@ -21,6 +21,9 @@
<option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
<!-- Ensure output directory is empty at the start -->
<option name="run-command" value="rm -rf /sdcard/flicker" />
+ <!-- Increase trace size: 20mb for WM and 80mb for SF -->
+ <option name="run-command" value="cmd window tracing size 20480" />
+ <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index 6053f1d..daecfe7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -17,8 +17,8 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
+import android.tools.common.PlatformConsts
import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.common.traces.wm.WindowManagerState.Companion.STATE_RESUMED
import android.tools.device.apphelpers.StandardAppHelper
import android.tools.device.helpers.FIND_TIMEOUT
import android.tools.device.traces.parsers.WindowManagerStateHelper
@@ -54,8 +54,8 @@
launchButton.click()
wmHelper
.StateSyncBuilder()
- .withActivityState(SECONDARY_ACTIVITY_COMPONENT, STATE_RESUMED)
- .withActivityState(MAIN_ACTIVITY_COMPONENT, STATE_RESUMED)
+ .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+ .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
.waitForAndVerify()
}
@@ -73,8 +73,8 @@
launchButton.click()
wmHelper
.StateSyncBuilder()
- .withActivityState(PLACEHOLDER_PRIMARY_COMPONENT, STATE_RESUMED)
- .withActivityState(PLACEHOLDER_SECONDARY_COMPONENT, STATE_RESUMED)
+ .withActivityState(PLACEHOLDER_PRIMARY_COMPONENT, PlatformConsts.STATE_RESUMED)
+ .withActivityState(PLACEHOLDER_SECONDARY_COMPONENT, PlatformConsts.STATE_RESUMED)
.waitForAndVerify()
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
index a3fb73b..496165a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
@@ -18,6 +18,11 @@
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.Timestamp
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.common.flicker.subject.exceptions.ExceptionMessageBuilder
+import android.tools.common.flicker.subject.exceptions.InvalidPropertyException
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -36,7 +41,7 @@
/**
* Test IME window layer will become visible when switching from the fixed orientation activity
* (e.g. Launcher activity). To run this test: `atest
- * FlickerTests:OpenImeWindowFromFixedOrientationAppTest`
+ * FlickerTests:ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@@ -77,6 +82,49 @@
flicker.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp)
}
+ @Postsubmit
+ @Test
+ fun imeLayerAlphaOneAfterSnapshotStartingWindowRemoval() {
+ // Check if the snapshot appeared during the trace
+ var imeSnapshotRemovedTimestamp: Timestamp? = null
+
+ val layerTrace = flicker.reader.readLayersTrace()
+ val layerTraceEntries = layerTrace?.entries?.toList() ?: emptyList()
+
+ layerTraceEntries.zipWithNext { prev, next ->
+ val prevSnapshotLayerVisible =
+ ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(prev.visibleLayers)
+ val nextSnapshotLayerVisible =
+ ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(next.visibleLayers)
+
+ if (imeSnapshotRemovedTimestamp == null &&
+ (prevSnapshotLayerVisible && !nextSnapshotLayerVisible)) {
+ imeSnapshotRemovedTimestamp = next.timestamp
+ }
+ }
+
+ // if so, make an assertion
+ imeSnapshotRemovedTimestamp?.let { timestamp ->
+ val stateAfterSnapshot = layerTrace?.getEntryAt(timestamp)
+ ?: error("State not found for $timestamp")
+
+ val imeLayers = ComponentNameMatcher.IME
+ .filterLayers(stateAfterSnapshot.visibleLayers.toList())
+
+ require(imeLayers.isNotEmpty()) { "IME layer not found" }
+ if (imeLayers.any { it.color.a != 1.0f }) {
+ val errorMsgBuilder = ExceptionMessageBuilder()
+ .setTimestamp(timestamp)
+ .forInvalidProperty("IME layer alpha")
+ .setExpected("is 1.0")
+ .setActual("not 1.0")
+ .addExtraDescription("Filter",
+ ComponentNameMatcher.IME.toLayerIdentifier())
+ throw InvalidPropertyException(errorMsgBuilder)
+ }
+ }
+ }
+
companion object {
/**
* Creates the test configurations.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
index a658293..3a80c66 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
@@ -16,6 +16,7 @@
package com.android.server.wm.flicker.launch
+import android.platform.test.annotations.FlakyTest
import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
@@ -24,6 +25,7 @@
import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
import androidx.test.filters.RequiresDevice
import org.junit.FixMethodOrder
+import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -76,6 +78,131 @@
teardown { testApp.exit(wmHelper) }
}
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun focusChanges() {
+ super.focusChanges()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowReplacesLauncherAsTopWindow() {
+ super.appWindowReplacesLauncherAsTopWindow()
+ }
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowAsTopWindowAtEnd() {
+ super.appWindowAsTopWindowAtEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowBecomesTopWindow() {
+ super.appWindowBecomesTopWindow()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowBecomesVisible() {
+ super.appWindowBecomesVisible()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowIsTopWindowAtEnd() {
+ super.appWindowIsTopWindowAtEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appLayerBecomesVisible() {
+ super.appLayerBecomesVisible()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appLayerReplacesLauncher() {
+ super.appLayerReplacesLauncher()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun cujCompleted() {
+ super.cujCompleted()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun entireScreenCovered() {
+ super.entireScreenCovered()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() {
+ super.navBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() {
+ super.navBarLayerPositionAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun navBarWindowIsAlwaysVisible() {
+ super.navBarWindowIsAlwaysVisible()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun navBarWindowIsVisibleAtStartAndEnd() {
+ super.navBarWindowIsVisibleAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() {
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() {
+ super.statusBarLayerPositionAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() {
+ super.statusBarWindowIsAlwaysVisible()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() {
+ super.taskBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() {
+ super.taskBarWindowIsAlwaysVisible()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
+
companion object {
/**
* Creates the test configurations.
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java
index 017de60..e2d17cd 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java
@@ -26,39 +26,57 @@
import android.graphics.PixelFormat;
import android.graphics.RenderNode;
import android.graphics.Shader;
+import android.graphics.SurfaceTexture;
import android.hardware.HardwareBuffer;
import android.media.Image;
import android.media.ImageWriter;
import android.os.Bundle;
+import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
+import android.view.TextureView;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Spinner;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
@SuppressWarnings({"UnusedDeclaration"})
public class ColorBitmapActivity extends Activity implements SurfaceHolder.Callback,
- AdapterView.OnItemSelectedListener {
+ TextureView.SurfaceTextureListener {
private static final int WIDTH = 512;
private static final int HEIGHT = 512;
private ImageView mImageView;
private SurfaceView mSurfaceView;
+ private TextureView mTextureView;
private HardwareBuffer mGradientBuffer;
- private ImageWriter mImageWriter;
+ private Map<View, ImageWriter> mImageWriters = new HashMap<>();
private ColorSpace mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
private String[] mColorNames = {"sRGB", "BT2020_HLG", "BT2020_PQ"};
- private String mCurrentColorName = "sRGB";
- private FutureTask<HardwareBuffer> authorGradientBuffer(HardwareBuffer buffer) {
+ private int mGradientEndColor = 0xFFFFFFFF;
+
+ private int[] mGradientEndColors = {0xFFFFFFFF, 0xFFFF0000, 0xFF00FF00, 0xFF0000FF};
+ private String[] mGradientColorNames = {"Grayscale", "Red", "Green", "Blue"};
+
+ private final ExecutorService mBufferFenceExecutor = Executors.newFixedThreadPool(1);
+ private final ExecutorService mBufferExecutor = Executors.newFixedThreadPool(1);
+
+ private FutureTask<HardwareBuffer> authorGradientBuffer(
+ HardwareBuffer buffer, int gradentEndColor) {
HardwareBufferRenderer renderer = new HardwareBufferRenderer(buffer);
RenderNode node = new RenderNode("content");
node.setPosition(0, 0, buffer.getWidth(), buffer.getHeight());
@@ -66,9 +84,10 @@
Canvas canvas = node.beginRecording();
LinearGradient gradient = new LinearGradient(
0, 0, buffer.getWidth(), buffer.getHeight(), 0xFF000000,
- 0xFFFFFFFF, Shader.TileMode.CLAMP);
+ gradentEndColor, Shader.TileMode.CLAMP);
Paint paint = new Paint();
paint.setShader(gradient);
+ paint.setDither(true);
canvas.drawRect(0f, 0f, buffer.getWidth(), buffer.getHeight(), paint);
node.endRecording();
@@ -78,7 +97,7 @@
FutureTask<HardwareBuffer> resolvedBuffer = new FutureTask<>(() -> buffer);
renderer.obtainRenderRequest()
.setColorSpace(colorSpace)
- .draw(Executors.newSingleThreadExecutor(), result -> {
+ .draw(mBufferFenceExecutor, result -> {
result.getFence().await(Duration.ofSeconds(3));
resolvedBuffer.run();
});
@@ -90,7 +109,7 @@
WIDTH, HEIGHT, PixelFormat.RGBA_8888, 1,
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
| HardwareBuffer.USAGE_GPU_COLOR_OUTPUT);
- return authorGradientBuffer(buffer);
+ return authorGradientBuffer(buffer, mGradientEndColor);
}
@Override
@@ -101,29 +120,70 @@
mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
- ArrayAdapter<String> adapter = new ArrayAdapter<>(
+ ArrayAdapter<String> colorSpaceAdapter = new ArrayAdapter<>(
this, android.R.layout.simple_spinner_item, mColorNames);
- adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- Spinner spinner = new Spinner(this);
- spinner.setAdapter(adapter);
- spinner.setOnItemSelectedListener(this);
+ colorSpaceAdapter
+ .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ Spinner colorSpaceSpinner = new Spinner(this);
+ colorSpaceSpinner.setAdapter(colorSpaceAdapter);
+ colorSpaceSpinner.setOnItemSelectedListener(new ColorSpaceOnItemSelectedListener());
+
+ ArrayAdapter<String> gradientColorAdapter = new ArrayAdapter<>(
+ this, android.R.layout.simple_spinner_item, mGradientColorNames);
+
+ gradientColorAdapter
+ .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ Spinner gradientColorSpinner = new Spinner(this);
+ gradientColorSpinner.setAdapter(gradientColorAdapter);
+ gradientColorSpinner
+ .setOnItemSelectedListener(new GradientColorOnItemSelectedListener());
mGradientBuffer = getGradientBuffer().get();
LinearLayout linearLayout = new LinearLayout(this);
linearLayout.setOrientation(LinearLayout.VERTICAL);
+ TextView imageViewText = new TextView(this);
+ imageViewText.setText("ImageView");
mImageView = new ImageView(this);
+ TextView textureViewText = new TextView(this);
+ textureViewText.setText("TextureView");
+ mTextureView = new TextureView(this);
+ mTextureView.setSurfaceTextureListener(this);
+
+ TextView surfaceViewText = new TextView(this);
+ surfaceViewText.setText("SurfaceView");
mSurfaceView = new SurfaceView(this);
mSurfaceView.getHolder().addCallback(this);
- linearLayout.addView(spinner, new LinearLayout.LayoutParams(
+ LinearLayout spinnerLayout = new LinearLayout(this);
+ spinnerLayout.setOrientation(LinearLayout.HORIZONTAL);
+
+ spinnerLayout.addView(colorSpaceSpinner, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
+ spinnerLayout.addView(gradientColorSpinner, new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT));
+
+ linearLayout.addView(spinnerLayout, new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT));
+
+ linearLayout.addView(imageViewText, new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT));
linearLayout.addView(mImageView, new LinearLayout.LayoutParams(WIDTH, HEIGHT));
+ linearLayout.addView(textureViewText, new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT));
+ linearLayout.addView(mTextureView, new LinearLayout.LayoutParams(WIDTH, HEIGHT));
+ linearLayout.addView(surfaceViewText, new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT));
linearLayout.addView(mSurfaceView, new LinearLayout.LayoutParams(WIDTH, HEIGHT));
setContentView(linearLayout);
@@ -145,16 +205,24 @@
}
private void populateBuffers() {
- Bitmap bitmap = Bitmap.wrapHardwareBuffer(
- mGradientBuffer, ColorSpace.get(ColorSpace.Named.SRGB));
- Bitmap copy = bitmap.copy(Bitmap.Config.ARGB_8888, false);
- copy.setColorSpace(mColorSpace);
- mImageView.setImageBitmap(copy);
+ try {
+ Bitmap bitmap = Bitmap.wrapHardwareBuffer(
+ getGradientBuffer().get(), ColorSpace.get(ColorSpace.Named.SRGB));
+ Bitmap copy = bitmap.copy(Bitmap.Config.ARGB_8888, false);
+ copy.setColorSpace(mColorSpace);
+ mImageView.setImageBitmap(copy);
- try (Image image = mImageWriter.dequeueInputImage()) {
- authorGradientBuffer(image.getHardwareBuffer()).get();
- image.setDataSpace(mColorSpace.getDataSpace());
- mImageWriter.queueInputImage(image);
+ for (ImageWriter writer : mImageWriters.values()) {
+ mBufferExecutor.execute(() -> {
+ try (Image image = writer.dequeueInputImage()) {
+ authorGradientBuffer(image.getHardwareBuffer(), mGradientEndColor).get();
+ image.setDataSpace(mColorSpace.getDataSpace());
+ writer.queueInputImage(image);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -167,30 +235,81 @@
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
- mImageWriter = new ImageWriter.Builder(holder.getSurface())
+ mImageWriters.put(mSurfaceView, new ImageWriter.Builder(holder.getSurface())
.setUsage(HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
| HardwareBuffer.USAGE_GPU_COLOR_OUTPUT
| HardwareBuffer.USAGE_COMPOSER_OVERLAY)
- .build();
+ .build());
populateBuffers();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
- mImageWriter.close();
- mImageWriter = null;
+ if (mImageWriters.containsKey(mSurfaceView)) {
+ mImageWriters.remove(mSurfaceView);
+ }
}
-
@Override
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- mCurrentColorName = mColorNames[position];
- mColorSpace = getFromName(mCurrentColorName);
+ public void onSurfaceTextureAvailable(
+ @NonNull SurfaceTexture surface, int width, int height) {
+ mImageWriters.put(mTextureView, new ImageWriter.Builder(new Surface(surface))
+ .setUsage(HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
+ | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT)
+ .build());
populateBuffers();
}
@Override
- public void onNothingSelected(AdapterView<?> parent) {
+ public void onSurfaceTextureSizeChanged(
+ @NonNull SurfaceTexture surface, int width, int height) {
}
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
+ if (mImageWriters.containsKey(mTextureView)) {
+ mImageWriters.remove(mTextureView);
+ }
+ return false;
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
+
+ }
+
+ private final class ColorSpaceOnItemSelectedListener
+ implements AdapterView.OnItemSelectedListener {
+
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ ColorBitmapActivity.this.mColorSpace =
+ getFromName(ColorBitmapActivity.this.mColorNames[position]);
+ ColorBitmapActivity.this.getMainExecutor()
+ .execute(ColorBitmapActivity.this::populateBuffers);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+
+ }
+ }
+
+ private final class GradientColorOnItemSelectedListener
+ implements AdapterView.OnItemSelectedListener {
+
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ ColorBitmapActivity.this.mGradientEndColor =
+ ColorBitmapActivity.this.mGradientEndColors[position];
+ ColorBitmapActivity.this.getMainExecutor()
+ .execute(ColorBitmapActivity.this::populateBuffers);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+
+ }
+ }
}
diff --git a/tests/SilkFX/res/layout/gainmap_image.xml b/tests/SilkFX/res/layout/gainmap_image.xml
index 89bbb70..b0ed914 100644
--- a/tests/SilkFX/res/layout/gainmap_image.xml
+++ b/tests/SilkFX/res/layout/gainmap_image.xml
@@ -34,7 +34,7 @@
android:layout_width="wrap_content"
android:layout_weight="1"
android:layout_height="wrap_content"
- android:text="SDR original" />
+ android:text="SDR" />
<RadioButton android:id="@+id/output_gainmap"
android:layout_width="wrap_content"
@@ -46,13 +46,34 @@
android:layout_width="wrap_content"
android:layout_weight="1"
android:layout_height="wrap_content"
- android:text="HDR (sdr+gainmap)" />
+ android:text="HDR" />
+
+ <RadioButton android:id="@+id/output_hdr_test"
+ android:layout_width="wrap_content"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:text="HDR (test)" />
</RadioGroup>
- <Spinner
- android:id="@+id/image_selection"
+ <LinearLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content" />
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <Spinner
+ android:id="@+id/image_selection"
+ android:layout_width="match_parent"
+ android:layout_weight="1"
+ android:layout_height="wrap_content" />
+
+ <Button
+ android:id="@+id/gainmap_metadata"
+ android:layout_width="match_parent"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:text="Gainmap Metadata..." />
+
+ </LinearLayout>
<TextView
android:id="@+id/error_msg"
@@ -67,4 +88,4 @@
</LinearLayout>
-</com.android.test.silkfx.hdr.GainmapImage>
\ No newline at end of file
+</com.android.test.silkfx.hdr.GainmapImage>
diff --git a/tests/SilkFX/res/layout/gainmap_metadata.xml b/tests/SilkFX/res/layout/gainmap_metadata.xml
new file mode 100644
index 0000000..0dabaca
--- /dev/null
+++ b/tests/SilkFX/res/layout/gainmap_metadata.xml
@@ -0,0 +1,264 @@
+<?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.
+ -->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:layout_width="350dp"
+ android:layout_height="300dp"
+ android:layout_centerHorizontal="true"
+ android:padding="8dp"
+ android:orientation="vertical"
+ android:background="#444444">
+
+ <TextView
+ android:id="@+id/gainmap_metadata_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="10dp"
+ android:text="Metadata for "HDR (test)" (values in linear space):" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="10dp"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/gainmap_metadata_gainmapmin_text"
+ android:layout_width="match_parent"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:text="Gain Map Min:" />
+
+ <TextView
+ android:id="@+id/gainmap_metadata_gainmapmin_val"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ems="4"
+ android:text="TODO" />
+
+ <SeekBar
+ android:id="@+id/gainmap_metadata_gainmapmin"
+ android:min="0"
+ android:max="100"
+ android:layout_width="150dp"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="10dp"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/gainmap_metadata_gainmapmax_text"
+ android:layout_width="match_parent"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:text="Gain Map Max:" />
+
+ <TextView
+ android:id="@+id/gainmap_metadata_gainmapmax_val"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ems="4"
+ android:text="TODO" />
+
+ <SeekBar
+ android:id="@+id/gainmap_metadata_gainmapmax"
+ android:min="0"
+ android:max="100"
+ android:layout_width="150dp"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="10dp"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/gainmap_metadata_capacitymin_text"
+ android:layout_width="match_parent"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:text="Capacity Min:" />
+
+ <TextView
+ android:id="@+id/gainmap_metadata_capacitymin_val"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ems="4"
+ android:text="TODO" />
+
+ <SeekBar
+ android:id="@+id/gainmap_metadata_capacitymin"
+ android:min="0"
+ android:max="100"
+ android:layout_width="150dp"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="10dp"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/gainmap_metadata_capacitymax_text"
+ android:layout_width="match_parent"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:text="Capacity Max:" />
+
+ <TextView
+ android:id="@+id/gainmap_metadata_capacitymax_val"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ems="4"
+ android:text="TODO" />
+
+ <SeekBar
+ android:id="@+id/gainmap_metadata_capacitymax"
+ android:min="0"
+ android:max="100"
+ android:layout_width="150dp"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="10dp"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/gainmap_metadata_gamma_text"
+ android:layout_width="match_parent"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:text="Gamma:" />
+
+ <TextView
+ android:id="@+id/gainmap_metadata_gamma_val"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ems="4"
+ android:text="TODO" />
+
+ <SeekBar
+ android:id="@+id/gainmap_metadata_gamma"
+ android:min="0"
+ android:max="100"
+ android:layout_width="150dp"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="10dp"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/gainmap_metadata_offsetsdr_text"
+ android:layout_width="match_parent"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:text="Offset SDR:" />
+
+ <TextView
+ android:id="@+id/gainmap_metadata_offsetsdr_val"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ems="4"
+ android:text="TODO" />
+
+ <SeekBar
+ android:id="@+id/gainmap_metadata_offsetsdr"
+ android:min="0"
+ android:max="100"
+ android:layout_width="150dp"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="10dp"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/gainmap_metadata_offsethdr_text"
+ android:layout_width="match_parent"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:text="Offset HDR:" />
+
+ <TextView
+ android:id="@+id/gainmap_metadata_offsethdr_val"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ems="4"
+ android:text="TODO" />
+
+ <SeekBar
+ android:id="@+id/gainmap_metadata_offsethdr"
+ android:min="0"
+ android:max="100"
+ android:layout_width="150dp"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/gainmap_metadata_reset"
+ android:layout_width="match_parent"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:text="Reset" />
+
+ <Button
+ android:id="@+id/gainmap_metadata_done"
+ android:layout_width="match_parent"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:text="Done" />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+</RelativeLayout>
diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt
index 78bc4c4..7cf69b7 100644
--- a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt
+++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt
@@ -27,6 +27,7 @@
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
+import android.widget.Button
import android.widget.FrameLayout
import android.widget.RadioGroup
import android.widget.Spinner
@@ -44,6 +45,7 @@
private var gainmap: Gainmap? = null
private var gainmapVisualizer: Bitmap? = null
private lateinit var imageView: SubsamplingScaleImageView
+ private lateinit var gainmapMetadataEditor: GainmapMetadataEditor
init {
gainmapImages = context.assets.list("gainmaps")!!
@@ -58,6 +60,7 @@
super.onFinishInflate()
imageView = findViewById(R.id.image)!!
+ gainmapMetadataEditor = GainmapMetadataEditor(this, imageView)
findViewById<RadioGroup>(R.id.output_mode)!!.also {
it.check(outputMode)
@@ -92,6 +95,10 @@
}
}
+ findViewById<Button>(R.id.gainmap_metadata)!!.setOnClickListener {
+ gainmapMetadataEditor.openEditor()
+ }
+
setImage(0)
imageView.apply {
@@ -132,6 +139,7 @@
findViewById<RadioGroup>(R.id.output_mode)!!.visibility = View.VISIBLE
gainmap = bitmap!!.gainmap
+ gainmapMetadataEditor.setGainmap(gainmap)
val map = gainmap!!.gainmapContents
if (map.config != Bitmap.Config.ALPHA_8) {
gainmapVisualizer = map
@@ -175,7 +183,15 @@
imageView.setImage(ImageSource.cachedBitmap(when (outputMode) {
R.id.output_hdr -> {
- bitmap!!.gainmap = gainmap; bitmap!!
+ gainmapMetadataEditor.useOriginalMetadata()
+ bitmap!!.gainmap = gainmap
+ bitmap!!
+ }
+
+ R.id.output_hdr_test -> {
+ gainmapMetadataEditor.useEditMetadata()
+ bitmap!!.gainmap = gainmap
+ bitmap!!
}
R.id.output_sdr -> {
@@ -186,4 +202,4 @@
else -> throw IllegalStateException()
}))
}
-}
\ No newline at end of file
+}
diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt
new file mode 100644
index 0000000..8a65304
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt
@@ -0,0 +1,284 @@
+/*
+ * 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.test.silkfx.hdr
+
+import android.graphics.Gainmap
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.PopupWindow
+import android.widget.SeekBar
+import android.widget.TextView
+import com.android.test.silkfx.R
+
+data class GainmapMetadata(
+ var ratioMin: Float,
+ var ratioMax: Float,
+ var capacityMin: Float,
+ var capacityMax: Float,
+ var gamma: Float,
+ var offsetSdr: Float,
+ var offsetHdr: Float
+)
+
+class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) {
+ private var gainmap: Gainmap? = null
+ private var showingEdits = false
+
+ private var metadataPopup: PopupWindow? = null
+
+ private var originalMetadata: GainmapMetadata = GainmapMetadata(
+ 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f)
+ private var currentMetadata: GainmapMetadata = originalMetadata.copy()
+
+ private val maxProgress = 100.0f
+
+ private val minRatioMin = .001f
+ private val maxRatioMin = 1.0f
+ private val minRatioMax = 1.0f
+ private val maxRatioMax = 16.0f
+ private val minCapacityMin = 1.0f
+ private val maxCapacityMin = maxRatioMax
+ private val minCapacityMax = 1.001f
+ private val maxCapacityMax = maxRatioMax
+ private val minGamma = 0.1f
+ private val maxGamma = 3.0f
+ // Min and max offsets are 0.0 and 1.0 respectively
+
+ fun setGainmap(newGainmap: Gainmap?) {
+ gainmap = newGainmap
+ originalMetadata = GainmapMetadata(gainmap!!.getRatioMin()[0],
+ gainmap!!.getRatioMax()[0], gainmap!!.getMinDisplayRatioForHdrTransition(),
+ gainmap!!.getDisplayRatioForFullHdr(), gainmap!!.getGamma()[0],
+ gainmap!!.getEpsilonSdr()[0], gainmap!!.getEpsilonHdr()[0])
+ currentMetadata = originalMetadata.copy()
+ }
+
+ fun useOriginalMetadata() {
+ showingEdits = false
+ applyMetadata(originalMetadata)
+ }
+
+ fun useEditMetadata() {
+ showingEdits = true
+ applyMetadata(currentMetadata)
+ }
+
+ fun closeEditor() {
+ metadataPopup?.let {
+ it.dismiss()
+ metadataPopup = null
+ }
+ }
+
+ fun openEditor() {
+ if (metadataPopup != null) return
+
+ val view = LayoutInflater.from(parent.getContext()).inflate(R.layout.gainmap_metadata, null)
+
+ metadataPopup = PopupWindow(view, ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT)
+ metadataPopup!!.showAtLocation(view, Gravity.CENTER, 0, 0)
+
+ (view.getParent() as ViewGroup).removeView(view)
+ parent.addView(view)
+
+ view.findViewById<Button>(R.id.gainmap_metadata_done)!!.setOnClickListener {
+ closeEditor()
+ }
+
+ view.findViewById<Button>(R.id.gainmap_metadata_reset)!!.setOnClickListener {
+ resetGainmapMetadata()
+ }
+
+ updateMetadataUi()
+
+ val gainmapMinSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_gainmapmin)
+ val gainmapMaxSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_gainmapmax)
+ val capacityMinSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_capacitymin)
+ val capacityMaxSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_capacitymax)
+ val gammaSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_gamma)
+ val offsetSdrSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_offsetsdr)
+ val offsetHdrSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_offsethdr)
+ arrayOf(gainmapMinSeek, gainmapMaxSeek, capacityMinSeek, capacityMaxSeek, gammaSeek,
+ offsetSdrSeek, offsetHdrSeek).forEach {
+ it.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener{
+ override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
+ if (!fromUser) return
+ val normalized = progress.toFloat() / maxProgress
+ when (seekBar) {
+ gainmapMinSeek -> updateGainmapMin(normalized)
+ gainmapMaxSeek -> updateGainmapMax(normalized)
+ capacityMinSeek -> updateCapacityMin(normalized)
+ capacityMaxSeek -> updateCapacityMax(normalized)
+ gammaSeek -> updateGamma(normalized)
+ offsetSdrSeek -> updateOffsetSdr(normalized)
+ offsetHdrSeek -> updateOffsetHdr(normalized)
+ }
+ }
+
+ override fun onStartTrackingTouch(seekBar: SeekBar) {}
+ override fun onStopTrackingTouch(seekBar: SeekBar) {}
+ })
+ }
+ }
+
+ private fun updateMetadataUi() {
+ val gainmapMinSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_gainmapmin)
+ val gainmapMaxSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_gainmapmax)
+ val capacityMinSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_capacitymin)
+ val capacityMaxSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_capacitymax)
+ val gammaSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_gamma)
+ val offsetSdrSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_offsetsdr)
+ val offsetHdrSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_offsethdr)
+
+ gainmapMinSeek.setProgress(
+ ((currentMetadata.ratioMin - minRatioMin) / maxRatioMin * maxProgress).toInt())
+ gainmapMaxSeek.setProgress(
+ ((currentMetadata.ratioMax - minRatioMax) / maxRatioMax * maxProgress).toInt())
+ capacityMinSeek.setProgress(
+ ((currentMetadata.capacityMin - minCapacityMin) / maxCapacityMin * maxProgress).toInt())
+ capacityMaxSeek.setProgress(
+ ((currentMetadata.capacityMax - minCapacityMax) / maxCapacityMax * maxProgress).toInt())
+ gammaSeek.setProgress(
+ ((currentMetadata.gamma - minGamma) / maxGamma * maxProgress).toInt())
+ // Log base 3 via: log_b(x) = log_y(x) / log_y(b)
+ offsetSdrSeek.setProgress(
+ ((1.0 - Math.log(currentMetadata.offsetSdr.toDouble() / Math.log(3.0)) / -11.0)
+ .toFloat() * maxProgress).toInt())
+ offsetHdrSeek.setProgress(
+ ((1.0 - Math.log(currentMetadata.offsetHdr.toDouble() / Math.log(3.0)) / -11.0)
+ .toFloat() * maxProgress).toInt())
+
+ parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmin_val)!!.setText(
+ "%.3f".format(currentMetadata.ratioMin))
+ parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmax_val)!!.setText(
+ "%.3f".format(currentMetadata.ratioMax))
+ parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymin_val)!!.setText(
+ "%.3f".format(currentMetadata.capacityMin))
+ parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymax_val)!!.setText(
+ "%.3f".format(currentMetadata.capacityMax))
+ parent.findViewById<TextView>(R.id.gainmap_metadata_gamma_val)!!.setText(
+ "%.3f".format(currentMetadata.gamma))
+ parent.findViewById<TextView>(R.id.gainmap_metadata_offsetsdr_val)!!.setText(
+ "%.5f".format(currentMetadata.offsetSdr))
+ parent.findViewById<TextView>(R.id.gainmap_metadata_offsethdr_val)!!.setText(
+ "%.5f".format(currentMetadata.offsetHdr))
+ }
+
+ private fun resetGainmapMetadata() {
+ currentMetadata = originalMetadata.copy()
+ applyMetadata(currentMetadata)
+ updateMetadataUi()
+ }
+
+ private fun applyMetadata(newMetadata: GainmapMetadata) {
+ gainmap!!.setRatioMin(newMetadata.ratioMin, newMetadata.ratioMin, newMetadata.ratioMin)
+ gainmap!!.setRatioMax(newMetadata.ratioMax, newMetadata.ratioMax, newMetadata.ratioMax)
+ gainmap!!.setMinDisplayRatioForHdrTransition(newMetadata.capacityMin)
+ gainmap!!.setDisplayRatioForFullHdr(newMetadata.capacityMax)
+ gainmap!!.setGamma(newMetadata.gamma, newMetadata.gamma, newMetadata.gamma)
+ gainmap!!.setEpsilonSdr(newMetadata.offsetSdr, newMetadata.offsetSdr, newMetadata.offsetSdr)
+ gainmap!!.setEpsilonHdr(newMetadata.offsetHdr, newMetadata.offsetHdr, newMetadata.offsetHdr)
+ renderView.invalidate()
+ }
+
+ private fun updateGainmapMin(normalized: Float) {
+ val newValue = minRatioMin + normalized * (maxRatioMin - minRatioMin)
+ parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmin_val)!!.setText(
+ "%.3f".format(newValue))
+ currentMetadata.ratioMin = newValue
+ if (showingEdits) {
+ gainmap!!.setRatioMin(newValue, newValue, newValue)
+ renderView.invalidate()
+ }
+ }
+
+ private fun updateGainmapMax(normalized: Float) {
+ val newValue = minRatioMax + normalized * (maxRatioMax - minRatioMax)
+ parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmax_val)!!.setText(
+ "%.3f".format(newValue))
+ currentMetadata.ratioMax = newValue
+ if (showingEdits) {
+ gainmap!!.setRatioMax(newValue, newValue, newValue)
+ renderView.invalidate()
+ }
+ }
+
+ private fun updateCapacityMin(normalized: Float) {
+ val newValue = minCapacityMin + normalized * (maxCapacityMin - minCapacityMin)
+ parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymin_val)!!.setText(
+ "%.3f".format(newValue))
+ currentMetadata.capacityMin = newValue
+ if (showingEdits) {
+ gainmap!!.setMinDisplayRatioForHdrTransition(newValue)
+ renderView.invalidate()
+ }
+ }
+
+ private fun updateCapacityMax(normalized: Float) {
+ val newValue = minCapacityMax + normalized * (maxCapacityMax - minCapacityMax)
+ parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymax_val)!!.setText(
+ "%.3f".format(newValue))
+ currentMetadata.capacityMax = newValue
+ if (showingEdits) {
+ gainmap!!.setDisplayRatioForFullHdr(newValue)
+ renderView.invalidate()
+ }
+ }
+
+ private fun updateGamma(normalized: Float) {
+ val newValue = minGamma + normalized * (maxGamma - minGamma)
+ parent.findViewById<TextView>(R.id.gainmap_metadata_gamma_val)!!.setText(
+ "%.3f".format(newValue))
+ currentMetadata.gamma = newValue
+ if (showingEdits) {
+ gainmap!!.setGamma(newValue, newValue, newValue)
+ renderView.invalidate()
+ }
+ }
+
+ private fun updateOffsetSdr(normalized: Float) {
+ var newValue = 0.0f
+ if (normalized > 0.0f ) {
+ newValue = Math.pow(3.0, (1.0 - normalized.toDouble()) * -11.0).toFloat()
+ }
+ parent.findViewById<TextView>(R.id.gainmap_metadata_offsetsdr_val)!!.setText(
+ "%.5f".format(newValue))
+ currentMetadata.offsetSdr = newValue
+ if (showingEdits) {
+ gainmap!!.setEpsilonSdr(newValue, newValue, newValue)
+ renderView.invalidate()
+ }
+ }
+
+ private fun updateOffsetHdr(normalized: Float) {
+ var newValue = 0.0f
+ if (normalized > 0.0f ) {
+ newValue = Math.pow(3.0, (1.0 - normalized.toDouble()) * -11.0).toFloat()
+ }
+ parent.findViewById<TextView>(R.id.gainmap_metadata_offsethdr_val)!!.setText(
+ "%.5f".format(newValue))
+ currentMetadata.offsetHdr = newValue
+ if (showingEdits) {
+ gainmap!!.setEpsilonHdr(newValue, newValue, newValue)
+ renderView.invalidate()
+ }
+ }
+}