diff options
215 files changed, 4918 insertions, 3927 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 855366abae4e..81c3e8957cd1 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -87,10 +87,12 @@ import android.os.Parcelable; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.os.StrictMode; import android.os.Trace; import android.os.UserHandle; +import android.service.voice.VoiceInteractionSession; import android.text.Selection; import android.text.SpannableStringBuilder; import android.text.TextUtils; @@ -154,6 +156,7 @@ import android.window.WindowOnBackInvokedDispatcher; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.IVoiceInteractionManagerService; import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.ToolbarActionBar; import com.android.internal.app.WindowDecorActionBar; @@ -1601,6 +1604,25 @@ public class Activity extends ContextThemeWrapper return callbacks; } + private void notifyVoiceInteractionManagerServiceActivityEvent( + @VoiceInteractionSession.VoiceInteractionActivityEventType int type) { + + final IVoiceInteractionManagerService service = + IVoiceInteractionManagerService.Stub.asInterface( + ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE)); + if (service == null) { + Log.w(TAG, "notifyVoiceInteractionManagerServiceActivityEvent: Can not get " + + "VoiceInteractionManagerService"); + return; + } + + try { + service.notifyActivityEventChanged(mToken, type); + } catch (RemoteException e) { + // Empty + } + } + /** * Called when the activity is starting. This is where most initialization * should go: calling {@link #setContentView(int)} to inflate the @@ -1876,6 +1898,9 @@ public class Activity extends ContextThemeWrapper mCalled = true; notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_START); + + notifyVoiceInteractionManagerServiceActivityEvent( + VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_START); } /** @@ -2019,6 +2044,12 @@ public class Activity extends ContextThemeWrapper final Window win = getWindow(); if (win != null) win.makeActive(); if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true); + + // Because the test case "com.android.launcher3.jank.BinderTests#testPressHome" doesn't + // allow any binder call in onResume, we call this method in onPostResume. + notifyVoiceInteractionManagerServiceActivityEvent( + VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_RESUME); + mCalled = true; } @@ -2394,6 +2425,10 @@ public class Activity extends ContextThemeWrapper getAutofillClientController().onActivityPaused(); notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_PAUSE); + + notifyVoiceInteractionManagerServiceActivityEvent( + VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE); + mCalled = true; } @@ -2623,6 +2658,9 @@ public class Activity extends ContextThemeWrapper getAutofillClientController().onActivityStopped(mIntent, mChangingConfigurations); mEnterAnimationComplete = false; + + notifyVoiceInteractionManagerServiceActivityEvent( + VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_STOP); } /** diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index f2ea060b1694..c95a7deba61f 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -731,10 +731,9 @@ public abstract class ActivityManagerInternal { */ public interface VoiceInteractionManagerProvider { /** - * Notifies the service when a high-level activity event has been changed, for example, - * an activity was resumed or stopped. + * Notifies the service when an activity is destroyed. */ - void notifyActivityEventChanged(); + void notifyActivityDestroyed(IBinder activityToken); } /** diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index a432b8dec2cb..fd9496947628 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -42,12 +42,15 @@ import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; import android.util.DisplayMetrics; +import android.util.Log; import android.widget.RemoteViews; import com.android.internal.appwidget.IAppWidgetService; +import com.android.internal.os.BackgroundThread; import java.util.Collections; import java.util.List; +import java.util.Objects; /** * Updates AppWidget state; gets information about installed AppWidget providers and other @@ -63,6 +66,7 @@ import java.util.List; @RequiresFeature(PackageManager.FEATURE_APP_WIDGETS) public class AppWidgetManager { + /** * Activity action to launch from your {@link AppWidgetHost} activity when you want to * pick an AppWidget to display. The AppWidget picker activity will be launched. @@ -332,6 +336,17 @@ public class AppWidgetManager { public static final String ACTION_APPWIDGET_UPDATE = "android.appwidget.action.APPWIDGET_UPDATE"; /** + * A combination broadcast of APPWIDGET_ENABLED and APPWIDGET_UPDATE. + * Sent during boot time and when the host is binding the widget for the very first time + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) + public static final String ACTION_APPWIDGET_ENABLE_AND_UPDATE = "android.appwidget.action" + + ".APPWIDGET_ENABLE_AND_UPDATE"; + + /** * Sent when the custom extras for an AppWidget change. * * <p class="note">This is a protected intent that can only be sent @@ -456,6 +471,8 @@ public class AppWidgetManager { public static final String ACTION_APPWIDGET_HOST_RESTORED = "android.appwidget.action.APPWIDGET_HOST_RESTORED"; + private static final String TAG = "AppWidgetManager"; + /** * An intent extra that contains multiple appWidgetIds. These are id values as * they were provided to the application during a recent restore from backup. It is @@ -511,6 +528,26 @@ public class AppWidgetManager { mPackageName = context.getOpPackageName(); mService = service; mDisplayMetrics = context.getResources().getDisplayMetrics(); + if (mService == null) { + return; + } + BackgroundThread.getExecutor().execute(() -> { + try { + mService.notifyProviderInheritance(getInstalledProvidersForPackage(mPackageName, + null) + .stream().filter(Objects::nonNull) + .map(info -> info.provider).filter(p -> { + try { + Class clazz = Class.forName(p.getClassName()); + return AppWidgetProvider.class.isAssignableFrom(clazz); + } catch (Exception e) { + return false; + } + }).toArray(ComponentName[]::new)); + } catch (Exception e) { + Log.e(TAG, "Nofity service of inheritance info", e); + } + }); } /** diff --git a/core/java/android/appwidget/AppWidgetProvider.java b/core/java/android/appwidget/AppWidgetProvider.java index a5d2198a6e17..3344ebc083a2 100644 --- a/core/java/android/appwidget/AppWidgetProvider.java +++ b/core/java/android/appwidget/AppWidgetProvider.java @@ -58,7 +58,12 @@ public class AppWidgetProvider extends BroadcastReceiver { // Protect against rogue update broadcasts (not really a security issue, // just filter bad broacasts out so subclasses are less likely to crash). String action = intent.getAction(); - if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) { + if (AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE.equals(action)) { + this.onReceive(context, new Intent(intent) + .setAction(AppWidgetManager.ACTION_APPWIDGET_ENABLED)); + this.onReceive(context, new Intent(intent) + .setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE)); + } else if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) { Bundle extras = intent.getExtras(); if (extras != null) { int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS); diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 0fd164de8ffb..085bfca158c1 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -918,7 +918,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } } - /** * Forwards BiometricStateListener to FingerprintService * @param listener new BiometricStateListener being added diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java index 16f9a12953f8..36e0dc35cb8e 100644 --- a/core/java/android/preference/SeekBarVolumizer.java +++ b/core/java/android/preference/SeekBarVolumizer.java @@ -16,7 +16,9 @@ package android.preference; +import android.Manifest; import android.annotation.NonNull; +import android.annotation.RequiresPermission; import android.app.NotificationManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.BroadcastReceiver; @@ -35,6 +37,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Message; import android.preference.VolumePreference.VolumeStore; +import android.provider.DeviceConfig; import android.provider.Settings; import android.provider.Settings.Global; import android.provider.Settings.System; @@ -44,6 +47,7 @@ import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import com.android.internal.annotations.GuardedBy; +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.os.SomeArgs; import java.util.concurrent.TimeUnit; @@ -115,7 +119,6 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba private final int mMaxStreamVolume; private boolean mAffectedByRingerMode; private boolean mNotificationOrRing; - private final boolean mNotifAliasRing; private final Receiver mReceiver = new Receiver(); private Handler mHandler; @@ -158,6 +161,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba this(context, streamType, defaultUri, callback, true /* playSample */); } + @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) public SeekBarVolumizer( Context context, int streamType, @@ -180,8 +184,6 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba if (mNotificationOrRing) { mRingerMode = mAudioManager.getRingerModeInternal(); } - mNotifAliasRing = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_alias_ring_notif_stream_types); mZenMode = mNotificationManager.getZenMode(); if (hasAudioProductStrategies()) { @@ -288,7 +290,9 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba * so that when user attempts to slide the notification seekbar out of vibrate the * seekbar doesn't wrongly snap back to 0 when the streams aren't aliased */ - if (mNotifAliasRing || mStreamType == AudioManager.STREAM_RING + if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false) + || mStreamType == AudioManager.STREAM_RING || (mStreamType == AudioManager.STREAM_NOTIFICATION && mMuted)) { mSeekBar.setProgress(0, true); } @@ -365,7 +369,9 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba // set the time of stop volume if ((mStreamType == AudioManager.STREAM_VOICE_CALL || mStreamType == AudioManager.STREAM_RING - || (!mNotifAliasRing && mStreamType == AudioManager.STREAM_NOTIFICATION) + || (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false) + && mStreamType == AudioManager.STREAM_NOTIFICATION) || mStreamType == AudioManager.STREAM_ALARM)) { sStopVolumeTime = java.lang.System.currentTimeMillis(); } @@ -643,8 +649,10 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba } private void updateVolumeSlider(int streamType, int streamValue) { - final boolean streamMatch = mNotifAliasRing && mNotificationOrRing - ? isNotificationOrRing(streamType) : streamType == mStreamType; + final boolean streamMatch = !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false) + && mNotificationOrRing ? isNotificationOrRing(streamType) : + streamType == mStreamType; if (mSeekBar != null && streamMatch && streamValue != -1) { final boolean muted = mAudioManager.isStreamMute(mStreamType) || streamValue == 0; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index c9fd1295a6e1..a955dbba97e7 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9793,6 +9793,13 @@ public final class Settings { "fingerprint_side_fps_auth_downtime"; /** + * Whether or not a SFPS device is required to be interactive for auth to unlock the device. + * @hide + */ + public static final String SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED = + "sfps_require_screen_on_to_auth_enabled"; + + /** * Whether or not debugging is enabled. * @hide */ diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index df727e9b7d3d..48f732ec9f12 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -19,6 +19,7 @@ package android.service.voice; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import android.annotation.CallbackExecutor; +import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -71,6 +72,8 @@ import com.android.internal.util.function.pooled.PooledLambda; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; @@ -143,6 +146,25 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall */ public static final int SHOW_SOURCE_AUTOMOTIVE_SYSTEM_UI = 1 << 7; + /** @hide */ + public static final int VOICE_INTERACTION_ACTIVITY_EVENT_START = 1; + /** @hide */ + public static final int VOICE_INTERACTION_ACTIVITY_EVENT_RESUME = 2; + /** @hide */ + public static final int VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE = 3; + /** @hide */ + public static final int VOICE_INTERACTION_ACTIVITY_EVENT_STOP = 4; + + /** @hide */ + @IntDef(prefix = { "VOICE_INTERACTION_ACTIVITY_EVENT_" }, value = { + VOICE_INTERACTION_ACTIVITY_EVENT_START, + VOICE_INTERACTION_ACTIVITY_EVENT_RESUME, + VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE, + VOICE_INTERACTION_ACTIVITY_EVENT_STOP + }) + @Retention(RetentionPolicy.SOURCE) + public @interface VoiceInteractionActivityEventType{} + final Context mContext; final HandlerCaller mHandlerCaller; diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 1664637eac56..d067d4bc366b 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -378,7 +378,7 @@ public final class ContentCaptureManager { private final Object mLock = new Object(); @NonNull - private final Context mContext; + private final StrippedContext mContext; @NonNull private final IContentCaptureManager mService; @@ -414,9 +414,37 @@ public final class ContentCaptureManager { } /** @hide */ + static class StrippedContext { + final String mPackageName; + final String mContext; + final @UserIdInt int mUserId; + + private StrippedContext(Context context) { + mPackageName = context.getPackageName(); + mContext = context.toString(); + mUserId = context.getUserId(); + } + + @Override + public String toString() { + return mContext; + } + + public String getPackageName() { + return mPackageName; + } + + @UserIdInt + public int getUserId() { + return mUserId; + } + } + + /** @hide */ public ContentCaptureManager(@NonNull Context context, @NonNull IContentCaptureManager service, @NonNull ContentCaptureOptions options) { - mContext = Objects.requireNonNull(context, "context cannot be null"); + Objects.requireNonNull(context, "context cannot be null"); + mContext = new StrippedContext(context); mService = Objects.requireNonNull(service, "service cannot be null"); mOptions = Objects.requireNonNull(options, "options cannot be null"); diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index 90384b520315..1f5e462d71fd 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -36,7 +36,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UiThread; import android.content.ComponentName; -import android.content.Context; import android.content.pm.ParceledListSlice; import android.graphics.Insets; import android.graphics.Rect; @@ -103,7 +102,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { private final AtomicBoolean mDisabled = new AtomicBoolean(false); @NonNull - private final Context mContext; + private final ContentCaptureManager.StrippedContext mContext; @NonNull private final ContentCaptureManager mManager; @@ -197,7 +196,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } } - protected MainContentCaptureSession(@NonNull Context context, + protected MainContentCaptureSession(@NonNull ContentCaptureManager.StrippedContext context, @NonNull ContentCaptureManager manager, @NonNull Handler handler, @NonNull IContentCaptureManager systemServerInterface) { mContext = context; diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 681693b1dbad..bd51f12539e8 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -18,6 +18,8 @@ package com.android.internal.app; import android.content.ComponentName; import android.content.Intent; +import android.hardware.soundtrigger.KeyphraseMetadata; +import android.hardware.soundtrigger.SoundTrigger; import android.media.AudioFormat; import android.media.permission.Identity; import android.os.Bundle; @@ -25,18 +27,17 @@ import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.SharedMemory; +import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback; +import android.service.voice.IVoiceInteractionService; +import android.service.voice.IVoiceInteractionSession; +import android.service.voice.VisibleActivityInfo; import com.android.internal.app.IHotwordRecognitionStatusCallback; import com.android.internal.app.IVoiceActionCheckCallback; -import com.android.internal.app.IVoiceInteractionSessionShowCallback; -import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.IVoiceInteractionSessionListener; +import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.internal.app.IVoiceInteractionSoundTriggerSession; -import android.hardware.soundtrigger.KeyphraseMetadata; -import android.hardware.soundtrigger.SoundTrigger; -import android.service.voice.IVoiceInteractionService; -import android.service.voice.IVoiceInteractionSession; -import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback; +import com.android.internal.app.IVoiceInteractor; interface IVoiceInteractionManagerService { void showSession(in Bundle sessionArgs, int flags); @@ -289,4 +290,14 @@ interface IVoiceInteractionManagerService { * Notifies when the session window is shown or hidden. */ void setSessionWindowVisible(in IBinder token, boolean visible); + + /** + * Notifies when the Activity lifecycle event changed. + * + * @param activityToken The token of activity. + * @param type The type of lifecycle event of the activity lifecycle. + */ + oneway void notifyActivityEventChanged( + in IBinder activityToken, + int type); } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index c70e26f65829..47400deac34b 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -1098,6 +1098,9 @@ public class ResolverActivity extends Activity implements @Override // ResolverListCommunicator public final void onPostListReady(ResolverListAdapter listAdapter, boolean doPostProcessing, boolean rebuildCompleted) { + if (isDestroyed()) { + return; + } if (isAutolaunching()) { return; } diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 6c689ff2b725..0f64f6da63f7 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -556,6 +556,11 @@ public final class SystemUiDeviceConfigFlags { "show_stop_button_for_user_allowlisted_apps"; /** + * (boolean) Whether to show notification volume control slider separate from ring. + */ + public static final String VOLUME_SEPARATE_NOTIFICATION = "volume_separate_notification"; + + /** * (boolean) Whether the clipboard overlay is enabled. */ public static final String CLIPBOARD_OVERLAY_ENABLED = "clipboard_overlay_enabled"; @@ -578,6 +583,11 @@ public final class SystemUiDeviceConfigFlags { */ public static final String CLIPBOARD_OVERLAY_SHOW_ACTIONS = "clipboard_overlay_show_actions"; + /** + * (boolean) Whether to combine the broadcasts APPWIDGET_ENABLED and APPWIDGET_UPDATE + */ + public static final String COMBINED_BROADCAST_ENABLED = "combined_broadcast_enabled"; + private SystemUiDeviceConfigFlags() { } } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index f7467b5a9f86..7787b7c61593 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -142,6 +142,7 @@ <protected-broadcast android:name="android.appwidget.action.APPWIDGET_ENABLED" /> <protected-broadcast android:name="android.appwidget.action.APPWIDGET_HOST_RESTORED" /> <protected-broadcast android:name="android.appwidget.action.APPWIDGET_RESTORED" /> + <protected-broadcast android:name="android.appwidget.action.APPWIDGET_ENABLE_AND_UPDATE" /> <protected-broadcast android:name="android.os.action.SETTING_RESTORED" /> diff --git a/core/res/res/values/bools.xml b/core/res/res/values/bools.xml index 988303ea7bc5..4b27bf2849fb 100644 --- a/core/res/res/values/bools.xml +++ b/core/res/res/values/bools.xml @@ -31,5 +31,4 @@ lockscreen, setting this to true should come with customized drawables. --> <bool name="use_lock_pattern_drawable">false</bool> <bool name="resolver_landscape_phone">true</bool> - <bool name="system_server_plays_face_haptics">true</bool> </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index aa9a9499ed97..07e05c6a830e 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2070,10 +2070,6 @@ STREAM_MUSIC as if it's on TV platform. --> <bool name="config_single_volume">false</bool> - <!-- Flag indicating whether notification and ringtone volumes - are controlled together (aliasing is true) or not. --> - <bool name="config_alias_ring_notif_stream_types">true</bool> - <!-- The number of volume steps for the notification stream --> <integer name="config_audio_notif_vol_steps">7</integer> @@ -2491,8 +2487,10 @@ assistant activities (ACTIVITY_TYPE_ASSISTANT) --> <bool name="config_dismissDreamOnActivityStart">false</bool> - <!-- The prefix of dream component names that are loggable. If empty, logs "other" for all. --> - <string name="config_loggable_dream_prefix" translatable="false"></string> + <!-- The prefixes of dream component names that are loggable. + Matched against ComponentName#flattenToString() for dream components. + If empty, logs "other" for all. --> + <string-array name="config_loggable_dream_prefixes"></string-array> <!-- ComponentName of a dream to show whenever the system would otherwise have gone to sleep. When the PowerManager is asked to go to sleep, it will instead @@ -4967,6 +4965,10 @@ <!-- If face auth sends the user directly to home/last open app, or stays on keyguard --> <bool name="config_faceAuthDismissesKeyguard">true</bool> + <!-- Default value for whether a SFPS device is required to be interactive for fingerprint auth + to unlock the device. --> + <bool name="config_requireScreenOnToAuthEnabled">false</bool> + <!-- The component name for the default profile supervisor, which can be set as a profile owner even after user setup is complete. The defined component should be used for supervision purposes only. The component must be part of a system app. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 7a7b43a3632a..8e7da4a5eac3 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -278,7 +278,6 @@ <java-symbol type="attr" name="autofillSaveCustomSubtitleMaxHeight"/> <java-symbol type="bool" name="action_bar_embed_tabs" /> <java-symbol type="bool" name="action_bar_expanded_action_views_exclusive" /> - <java-symbol type="bool" name="config_alias_ring_notif_stream_types" /> <java-symbol type="integer" name="config_audio_notif_vol_default" /> <java-symbol type="integer" name="config_audio_notif_vol_steps" /> <java-symbol type="integer" name="config_audio_ring_vol_default" /> @@ -2245,7 +2244,7 @@ <java-symbol type="integer" name="config_dreamOverlayReconnectTimeoutMs" /> <java-symbol type="integer" name="config_dreamOverlayMaxReconnectAttempts" /> <java-symbol type="integer" name="config_minDreamOverlayDurationMs" /> - <java-symbol type="string" name="config_loggable_dream_prefix" /> + <java-symbol type="array" name="config_loggable_dream_prefixes" /> <java-symbol type="string" name="config_dozeComponent" /> <java-symbol type="string" name="enable_explore_by_touch_warning_title" /> <java-symbol type="string" name="enable_explore_by_touch_warning_message" /> @@ -2723,6 +2722,7 @@ <java-symbol type="array" name="config_face_acquire_vendor_biometricprompt_ignorelist" /> <java-symbol type="bool" name="config_faceAuthSupportsSelfIllumination" /> <java-symbol type="bool" name="config_faceAuthDismissesKeyguard" /> + <java-symbol type="bool" name="config_requireScreenOnToAuthEnabled" /> <!-- Face config --> <java-symbol type="integer" name="config_faceMaxTemplatesPerUser" /> @@ -4861,6 +4861,4 @@ <java-symbol type="id" name="language_picker_header" /> <java-symbol type="dimen" name="status_bar_height_default" /> - - <java-symbol type="bool" name="system_server_plays_face_haptics" /> </resources> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 31e2abe0bb85..f047905f18b6 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -1921,6 +1921,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/TaskFragment.java" }, + "-240296576": { + "message": "handleAppTransitionReady: displayId=%d appTransition={%s} openingApps=[%s] closingApps=[%s] transit=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_APP_TRANSITIONS", + "at": "com\/android\/server\/wm\/AppTransitionController.java" + }, "-237664290": { "message": "Pause the recording session on display %s", "level": "VERBOSE", @@ -2023,12 +2029,6 @@ "group": "WM_DEBUG_CONTENT_RECORDING", "at": "com\/android\/server\/wm\/ContentRecorder.java" }, - "-134793542": { - "message": "handleAppTransitionReady: displayId=%d appTransition={%s} excludeLauncherFromAnimation=%b openingApps=[%s] closingApps=[%s] transit=%s", - "level": "VERBOSE", - "group": "WM_DEBUG_APP_TRANSITIONS", - "at": "com\/android\/server\/wm\/AppTransitionController.java" - }, "-134091882": { "message": "Screenshotting Activity %s", "level": "VERBOSE", @@ -2569,6 +2569,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "323235828": { + "message": "Delaying app transition for recents animation to finish", + "level": "VERBOSE", + "group": "WM_DEBUG_APP_TRANSITIONS", + "at": "com\/android\/server\/wm\/AppTransitionController.java" + }, "327461496": { "message": "Complete pause: %s", "level": "VERBOSE", diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 1d513e444050..16760e26b3f1 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -1873,6 +1873,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Override public void onActivityPreCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) { + if (activity.isChild()) { + // Skip Activity that is child of another Activity (ActivityGroup) because it's + // window will just be a child of the parent Activity window. + return; + } synchronized (mLock) { final IBinder activityToken = activity.getActivityToken(); final IBinder initialTaskFragmentToken = @@ -1904,6 +1909,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Override public void onActivityPostCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) { + if (activity.isChild()) { + // Skip Activity that is child of another Activity (ActivityGroup) because it's + // window will just be a child of the parent Activity window. + return; + } // Calling after Activity#onCreate is complete to allow the app launch something // first. In case of a configured placeholder activity we want to make sure // that we don't launch it if an activity itself already requested something to be @@ -1921,6 +1931,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Override public void onActivityConfigurationChanged(@NonNull Activity activity) { + if (activity.isChild()) { + // Skip Activity that is child of another Activity (ActivityGroup) because it's + // window will just be a child of the parent Activity window. + return; + } synchronized (mLock) { final TransactionRecord transactionRecord = mTransactionManager .startNewTransaction(); @@ -1934,6 +1949,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Override public void onActivityPostDestroyed(@NonNull Activity activity) { + if (activity.isChild()) { + // Skip Activity that is child of another Activity (ActivityGroup) because it's + // window will just be a child of the parent Activity window. + return; + } synchronized (mLock) { SplitController.this.onActivityDestroyed(activity); } @@ -1969,7 +1989,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (who instanceof Activity) { // We will check if the new activity should be split with the activity that launched // it. - launchingActivity = (Activity) who; + final Activity activity = (Activity) who; + // For Activity that is child of another Activity (ActivityGroup), treat the parent + // Activity as the launching one because it's window will just be a child of the + // parent Activity window. + launchingActivity = activity.isChild() ? activity.getParent() : activity; if (isInPictureInPicture(launchingActivity)) { // We don't embed activity when it is in PIP. return super.onStartActivity(who, intent, options); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 79603233ae14..362f1fa096cc 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -227,7 +227,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity( secondaryActivity); TaskFragmentContainer containerToAvoid = primaryContainer; - if (curSecondaryContainer != null + if (curSecondaryContainer != null && curSecondaryContainer != primaryContainer && (rule.shouldClearTop() || primaryContainer.isAbove(curSecondaryContainer))) { // Do not reuse the current TaskFragment if the rule is to clear top, or if it is below // the primary TaskFragment. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 725b20525bf7..bec6844ae863 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -24,6 +24,7 @@ import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE; import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER; import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; @@ -37,7 +38,6 @@ import static com.android.wm.shell.bubbles.Bubbles.DISMISS_NO_LONGER_BUBBLE; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED; -import static com.android.wm.shell.floating.FloatingTasksController.SHOW_FLOATING_TASKS_AS_BUBBLES; import android.annotation.NonNull; import android.annotation.UserIdInt; @@ -150,6 +150,9 @@ public class BubbleController implements ConfigurationChangeListener { private final ShellExecutor mBackgroundExecutor; + // Whether or not we should show bubbles pinned at the bottom of the screen. + private boolean mIsBubbleBarEnabled; + private BubbleLogger mLogger; private BubbleData mBubbleData; @Nullable private BubbleStackView mStackView; @@ -210,7 +213,6 @@ public class BubbleController implements ConfigurationChangeListener { /** Drag and drop controller to register listener for onDragStarted. */ private DragAndDropController mDragAndDropController; - public BubbleController(Context context, ShellInit shellInit, ShellCommandHandler shellCommandHandler, @@ -526,6 +528,12 @@ public class BubbleController implements ConfigurationChangeListener { mDataRepository.removeBubblesForUser(removedUserId, parentUserId); } + // TODO(b/256873975): Should pass this into the constructor once flags are available to shell. + /** Sets whether the bubble bar is enabled (i.e. bubbles pinned to bottom on large screens). */ + public void setBubbleBarEnabled(boolean enabled) { + mIsBubbleBarEnabled = enabled; + } + /** Whether this userId belongs to the current user. */ private boolean isCurrentProfile(int userId) { return userId == UserHandle.USER_ALL @@ -591,7 +599,8 @@ public class BubbleController implements ConfigurationChangeListener { } mStackView.setUnbubbleConversationCallback(mSysuiProxy::onUnbubbleConversation); } - if (SHOW_FLOATING_TASKS_AS_BUBBLES && mBubblePositioner.isLargeScreen()) { + + if (mIsBubbleBarEnabled && mBubblePositioner.isLargeScreen()) { mBubblePositioner.setUsePinnedLocation(true); } else { mBubblePositioner.setUsePinnedLocation(false); @@ -959,14 +968,18 @@ public class BubbleController implements ConfigurationChangeListener { } /** - * Adds a bubble for a specific intent. These bubbles are <b>not</b> backed by a notification - * and remain until the user dismisses the bubble or bubble stack. Only one intent bubble - * is supported at a time. + * Adds and expands bubble for a specific intent. These bubbles are <b>not</b> backed by a n + * otification and remain until the user dismisses the bubble or bubble stack. Only one intent + * bubble is supported at a time. * * @param intent the intent to display in the bubble expanded view. */ - public void addAppBubble(Intent intent) { + public void showAppBubble(Intent intent) { if (intent == null || intent.getPackage() == null) return; + + PackageManager packageManager = getPackageManagerForUser(mContext, mCurrentUserId); + if (!isResizableActivity(intent, packageManager, KEY_APP_BUBBLE)) return; + Bubble b = new Bubble(intent, UserHandle.of(mCurrentUserId), mMainExecutor); b.setShouldAutoExpand(true); inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false); @@ -1489,18 +1502,23 @@ public class BubbleController implements ConfigurationChangeListener { } PackageManager packageManager = getPackageManagerForUser( context, entry.getStatusBarNotification().getUser().getIdentifier()); - ActivityInfo info = - intent.getIntent().resolveActivityInfo(packageManager, 0); + return isResizableActivity(intent.getIntent(), packageManager, entry.getKey()); + } + + static boolean isResizableActivity(Intent intent, PackageManager packageManager, String key) { + if (intent == null) { + Log.w(TAG, "Unable to send as bubble: " + key + " null intent"); + return false; + } + ActivityInfo info = intent.resolveActivityInfo(packageManager, 0); if (info == null) { - Log.w(TAG, "Unable to send as bubble, " - + entry.getKey() + " couldn't find activity info for intent: " - + intent); + Log.w(TAG, "Unable to send as bubble: " + key + + " couldn't find activity info for intent: " + intent); return false; } if (!ActivityInfo.isResizeableMode(info.resizeMode)) { - Log.w(TAG, "Unable to send as bubble, " - + entry.getKey() + " activity is not resizable for intent: " - + intent); + Log.w(TAG, "Unable to send as bubble: " + key + + " activity is not resizable for intent: " + intent); return false; } return true; @@ -1674,6 +1692,13 @@ public class BubbleController implements ConfigurationChangeListener { } @Override + public void showAppBubble(Intent intent) { + mMainExecutor.execute(() -> { + BubbleController.this.showAppBubble(intent); + }); + } + + @Override public boolean handleDismissalInterception(BubbleEntry entry, @Nullable List<BubbleEntry> children, IntConsumer removeCallback, Executor callbackExecutor) { @@ -1784,6 +1809,13 @@ public class BubbleController implements ConfigurationChangeListener { } @Override + public void setBubbleBarEnabled(boolean enabled) { + mMainExecutor.execute(() -> { + BubbleController.this.setBubbleBarEnabled(enabled); + }); + } + + @Override public void onNotificationPanelExpandedChanged(boolean expanded) { mMainExecutor.execute( () -> BubbleController.this.onNotificationPanelExpandedChanged(expanded)); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 7f891ec6d215..465d1abe0a3d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -22,6 +22,7 @@ import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.app.NotificationChannel; +import android.content.Intent; import android.content.pm.UserInfo; import android.os.UserHandle; import android.service.notification.NotificationListenerService; @@ -108,6 +109,15 @@ public interface Bubbles { void expandStackAndSelectBubble(Bubble bubble); /** + * Adds and expands bubble that is not notification based, but instead based on an intent from + * the app. The intent must be explicit (i.e. include a package name or fully qualified + * component class name) and the activity for it should be resizable. + * + * @param intent the intent to populate the bubble. + */ + void showAppBubble(Intent intent); + + /** * @return a bubble that matches the provided shortcutId, if one exists. */ @Nullable @@ -232,6 +242,11 @@ public interface Bubbles { */ void onUserRemoved(int removedUserId); + /** + * Sets whether bubble bar should be enabled or not. + */ + void setBubbleBarEnabled(boolean enabled); + /** Listener to find out about stack expansion / collapse events. */ interface BubbleExpandListener { /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS new file mode 100644 index 000000000000..7237d2bde39f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS @@ -0,0 +1,2 @@ +# WM shell sub-modules splitscreen owner +chenghsiuchang@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 625d8a83736d..962be9da2111 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 @@ -24,7 +24,6 @@ import android.content.pm.PackageManager; import android.os.Handler; import android.os.SystemProperties; import android.view.IWindowManager; -import android.view.WindowManager; import com.android.internal.logging.UiEventLogger; import com.android.launcher3.icons.IconProvider; @@ -65,8 +64,6 @@ import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.displayareahelper.DisplayAreaHelperController; import com.android.wm.shell.draganddrop.DragAndDropController; -import com.android.wm.shell.floating.FloatingTasks; -import com.android.wm.shell.floating.FloatingTasksController; import com.android.wm.shell.freeform.FreeformComponents; import com.android.wm.shell.fullscreen.FullscreenTaskListener; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController; @@ -575,47 +572,6 @@ public abstract class WMShellBaseModule { } // - // Floating tasks - // - - @WMSingleton - @Provides - static Optional<FloatingTasks> provideFloatingTasks( - Optional<FloatingTasksController> floatingTaskController) { - return floatingTaskController.map((controller) -> controller.asFloatingTasks()); - } - - @WMSingleton - @Provides - static Optional<FloatingTasksController> provideFloatingTasksController(Context context, - ShellInit shellInit, - ShellController shellController, - ShellCommandHandler shellCommandHandler, - Optional<BubbleController> bubbleController, - WindowManager windowManager, - ShellTaskOrganizer organizer, - TaskViewTransitions taskViewTransitions, - @ShellMainThread ShellExecutor mainExecutor, - @ShellBackgroundThread ShellExecutor bgExecutor, - SyncTransactionQueue syncQueue) { - if (FloatingTasksController.FLOATING_TASKS_ENABLED) { - return Optional.of(new FloatingTasksController(context, - shellInit, - shellController, - shellCommandHandler, - bubbleController, - windowManager, - organizer, - taskViewTransitions, - mainExecutor, - bgExecutor, - syncQueue)); - } else { - return Optional.empty(); - } - } - - // // Starting window // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java deleted file mode 100644 index 83a1734dc71a..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java +++ /dev/null @@ -1,259 +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.wm.shell.floating; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.Resources; -import android.view.MotionEvent; -import android.view.View; - -import androidx.annotation.NonNull; -import androidx.dynamicanimation.animation.DynamicAnimation; - -import com.android.wm.shell.R; -import com.android.wm.shell.bubbles.DismissView; -import com.android.wm.shell.common.magnetictarget.MagnetizedObject; -import com.android.wm.shell.floating.views.FloatingTaskLayer; -import com.android.wm.shell.floating.views.FloatingTaskView; - -import java.util.Objects; - -/** - * Controls a floating dismiss circle that has a 'magnetic' field around it, causing views moved - * close to the target to be stuck to it unless moved out again. - */ -public class FloatingDismissController { - - /** Velocity required to dismiss the view without dragging it into the dismiss target. */ - private static final float FLING_TO_DISMISS_MIN_VELOCITY = 4000f; - /** - * Max velocity that the view can be moving through the target with to stick (i.e. if it's - * more than this velocity, it will pass through the target. - */ - private static final float STICK_TO_TARGET_MAX_X_VELOCITY = 2000f; - /** - * Percentage of the target width to use to determine if an object flung towards the target - * should dismiss (e.g. if target is 100px and this is set ot 2f, anything flung within a - * 200px-wide area around the target will be considered 'near' enough get dismissed). - */ - private static final float FLING_TO_TARGET_WIDTH_PERCENT = 2f; - /** Minimum alpha to apply to the view being dismissed when it is in the target. */ - private static final float DISMISS_VIEW_MIN_ALPHA = 0.6f; - /** Amount to scale down the view being dismissed when it is in the target. */ - private static final float DISMISS_VIEW_SCALE_DOWN_PERCENT = 0.15f; - - private Context mContext; - private FloatingTasksController mController; - private FloatingTaskLayer mParent; - - private DismissView mDismissView; - private ValueAnimator mDismissAnimator; - private View mViewBeingDismissed; - private float mDismissSizePercent; - private float mDismissSize; - - /** - * The currently magnetized object, which is being dragged and will be attracted to the magnetic - * dismiss target. - */ - private MagnetizedObject<View> mMagnetizedObject; - /** - * The MagneticTarget instance for our circular dismiss view. This is added to the - * MagnetizedObject instances for the view being dragged. - */ - private MagnetizedObject.MagneticTarget mMagneticTarget; - /** Magnet listener that handles animating and dismissing the view. */ - private MagnetizedObject.MagnetListener mFloatingViewMagnetListener; - - public FloatingDismissController(Context context, FloatingTasksController controller, - FloatingTaskLayer parent) { - mContext = context; - mController = controller; - mParent = parent; - updateSizes(); - createAndAddDismissView(); - - mDismissAnimator = ValueAnimator.ofFloat(1f, 0f); - mDismissAnimator.addUpdateListener(animation -> { - final float value = (float) animation.getAnimatedValue(); - if (mDismissView != null) { - mDismissView.setPivotX((mDismissView.getRight() - mDismissView.getLeft()) / 2f); - mDismissView.setPivotY((mDismissView.getBottom() - mDismissView.getTop()) / 2f); - final float scaleValue = Math.max(value, mDismissSizePercent); - mDismissView.getCircle().setScaleX(scaleValue); - mDismissView.getCircle().setScaleY(scaleValue); - } - if (mViewBeingDismissed != null) { - // TODO: alpha doesn't actually apply to taskView currently. - mViewBeingDismissed.setAlpha(Math.max(value, DISMISS_VIEW_MIN_ALPHA)); - mViewBeingDismissed.setScaleX(Math.max(value, DISMISS_VIEW_SCALE_DOWN_PERCENT)); - mViewBeingDismissed.setScaleY(Math.max(value, DISMISS_VIEW_SCALE_DOWN_PERCENT)); - } - }); - - mFloatingViewMagnetListener = new MagnetizedObject.MagnetListener() { - @Override - public void onStuckToTarget( - @NonNull MagnetizedObject.MagneticTarget target) { - animateDismissing(/* dismissing= */ true); - } - - @Override - public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target, - float velX, float velY, boolean wasFlungOut) { - animateDismissing(/* dismissing= */ false); - mParent.onUnstuckFromTarget((FloatingTaskView) mViewBeingDismissed, velX, velY, - wasFlungOut); - } - - @Override - public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { - doDismiss(); - } - }; - } - - /** Updates all the sizes used and applies them to the {@link DismissView}. */ - public void updateSizes() { - Resources res = mContext.getResources(); - mDismissSize = res.getDimensionPixelSize( - R.dimen.floating_task_dismiss_circle_size); - final float minDismissSize = res.getDimensionPixelSize( - R.dimen.floating_dismiss_circle_small); - mDismissSizePercent = minDismissSize / mDismissSize; - - if (mDismissView != null) { - mDismissView.updateResources(); - } - } - - /** Prepares the view being dragged to be magnetic. */ - public void setUpMagneticObject(View viewBeingDragged) { - mViewBeingDismissed = viewBeingDragged; - mMagnetizedObject = getMagnetizedView(viewBeingDragged); - mMagnetizedObject.clearAllTargets(); - mMagnetizedObject.addTarget(mMagneticTarget); - mMagnetizedObject.setMagnetListener(mFloatingViewMagnetListener); - } - - /** Shows or hides the dismiss target. */ - public void showDismiss(boolean show) { - if (show) { - mDismissView.show(); - } else { - mDismissView.hide(); - } - } - - /** Passes the MotionEvent to the magnetized object and returns true if it was consumed. */ - public boolean passEventToMagnetizedObject(MotionEvent event) { - return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event); - } - - private void createAndAddDismissView() { - if (mDismissView != null) { - mParent.removeView(mDismissView); - } - mDismissView = new DismissView(mContext); - mDismissView.setTargetSizeResId(R.dimen.floating_task_dismiss_circle_size); - mDismissView.updateResources(); - mParent.addView(mDismissView); - - final float dismissRadius = mDismissSize; - // Save the MagneticTarget instance for the newly set up view - we'll add this to the - // MagnetizedObjects when the dismiss view gets shown. - mMagneticTarget = new MagnetizedObject.MagneticTarget( - mDismissView.getCircle(), (int) dismissRadius); - } - - private MagnetizedObject<View> getMagnetizedView(View v) { - if (mMagnetizedObject != null - && Objects.equals(mMagnetizedObject.getUnderlyingObject(), v)) { - // Same view being dragged, we can reuse the magnetic object. - return mMagnetizedObject; - } - MagnetizedObject<View> magnetizedView = new MagnetizedObject<View>( - mContext, - v, - DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y - ) { - @Override - public float getWidth(@NonNull View underlyingObject) { - return underlyingObject.getWidth(); - } - - @Override - public float getHeight(@NonNull View underlyingObject) { - return underlyingObject.getHeight(); - } - - @Override - public void getLocationOnScreen(@NonNull View underlyingObject, - @NonNull int[] loc) { - loc[0] = (int) underlyingObject.getTranslationX(); - loc[1] = (int) underlyingObject.getTranslationY(); - } - }; - magnetizedView.setHapticsEnabled(true); - magnetizedView.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY); - magnetizedView.setStickToTargetMaxXVelocity(STICK_TO_TARGET_MAX_X_VELOCITY); - magnetizedView.setFlingToTargetWidthPercent(FLING_TO_TARGET_WIDTH_PERCENT); - return magnetizedView; - } - - /** Animates the dismiss treatment on the view being dismissed. */ - private void animateDismissing(boolean shouldDismiss) { - if (mViewBeingDismissed == null) { - return; - } - if (shouldDismiss) { - mDismissAnimator.removeAllListeners(); - mDismissAnimator.start(); - } else { - mDismissAnimator.removeAllListeners(); - mDismissAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - resetDismissAnimator(); - } - }); - mDismissAnimator.reverse(); - } - } - - /** Actually dismisses the view. */ - private void doDismiss() { - mDismissView.hide(); - mController.removeTask(); - resetDismissAnimator(); - mViewBeingDismissed = null; - } - - private void resetDismissAnimator() { - mDismissAnimator.removeAllListeners(); - mDismissAnimator.cancel(); - if (mDismissView != null) { - mDismissView.cancelAnimators(); - mDismissView.getCircle().setScaleX(1f); - mDismissView.getCircle().setScaleY(1f); - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java deleted file mode 100644 index f86d467360f9..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java +++ /dev/null @@ -1,36 +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.wm.shell.floating; - -import android.content.Intent; - -import com.android.wm.shell.common.annotations.ExternalThread; - -/** - * Interface to interact with floating tasks. - */ -@ExternalThread -public interface FloatingTasks { - - /** - * Shows, stashes, or un-stashes the floating task depending on state: - * - If there is no floating task for this intent, it shows the task for the provided intent. - * - If there is a floating task for this intent, but it's stashed, this un-stashes it. - * - If there is a floating task for this intent, and it's not stashed, this stashes it. - */ - void showOrSetStashed(Intent intent); -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java deleted file mode 100644 index b3c09d32055b..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java +++ /dev/null @@ -1,454 +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.wm.shell.floating; - -import static android.app.ActivityTaskManager.INVALID_TASK_ID; -import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - -import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; -import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS; -import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS; - -import android.annotation.Nullable; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ShortcutInfo; -import android.content.res.Configuration; -import android.graphics.PixelFormat; -import android.graphics.Point; -import android.os.SystemProperties; -import android.view.ViewGroup; -import android.view.WindowManager; - -import androidx.annotation.BinderThread; -import androidx.annotation.VisibleForTesting; - -import com.android.internal.protolog.common.ProtoLog; -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.TaskViewTransitions; -import com.android.wm.shell.bubbles.BubbleController; -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.SyncTransactionQueue; -import com.android.wm.shell.common.annotations.ExternalThread; -import com.android.wm.shell.common.annotations.ShellBackgroundThread; -import com.android.wm.shell.common.annotations.ShellMainThread; -import com.android.wm.shell.floating.views.FloatingTaskLayer; -import com.android.wm.shell.floating.views.FloatingTaskView; -import com.android.wm.shell.sysui.ConfigurationChangeListener; -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.Objects; -import java.util.Optional; - -/** - * Entry point for creating and managing floating tasks. - * - * A single window layer is added and the task(s) are displayed using a {@link FloatingTaskView} - * within that window. - * - * Currently optimized for a single task. Multiple tasks are not supported. - */ -public class FloatingTasksController implements RemoteCallable<FloatingTasksController>, - ConfigurationChangeListener { - - private static final String TAG = FloatingTasksController.class.getSimpleName(); - - public static final boolean FLOATING_TASKS_ENABLED = - SystemProperties.getBoolean("persist.wm.debug.floating_tasks", false); - public static final boolean SHOW_FLOATING_TASKS_AS_BUBBLES = - SystemProperties.getBoolean("persist.wm.debug.floating_tasks_as_bubbles", false); - - @VisibleForTesting - static final int SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET = 600; - - // Only used for testing - private Configuration mConfig; - private boolean mFloatingTasksEnabledForTests; - - private FloatingTaskImpl mImpl = new FloatingTaskImpl(); - private Context mContext; - private ShellController mShellController; - private ShellCommandHandler mShellCommandHandler; - private @Nullable BubbleController mBubbleController; - private WindowManager mWindowManager; - private ShellTaskOrganizer mTaskOrganizer; - private TaskViewTransitions mTaskViewTransitions; - private @ShellMainThread ShellExecutor mMainExecutor; - // TODO: mBackgroundThread is not used but we'll probs need it eventually? - private @ShellBackgroundThread ShellExecutor mBackgroundThread; - private SyncTransactionQueue mSyncQueue; - - private boolean mIsFloatingLayerAdded; - private FloatingTaskLayer mFloatingTaskLayer; - private final Point mLastPosition = new Point(-1, -1); - - private Task mTask; - - // Simple class to hold onto info for intent or shortcut based tasks. - public static class Task { - public int taskId = INVALID_TASK_ID; - @Nullable - public Intent intent; - @Nullable - public ShortcutInfo info; - @Nullable - public FloatingTaskView floatingView; - } - - public FloatingTasksController(Context context, - ShellInit shellInit, - ShellController shellController, - ShellCommandHandler shellCommandHandler, - Optional<BubbleController> bubbleController, - WindowManager windowManager, - ShellTaskOrganizer organizer, - TaskViewTransitions transitions, - @ShellMainThread ShellExecutor mainExecutor, - @ShellBackgroundThread ShellExecutor bgExceutor, - SyncTransactionQueue syncTransactionQueue) { - mContext = context; - mShellController = shellController; - mShellCommandHandler = shellCommandHandler; - mBubbleController = bubbleController.get(); - mWindowManager = windowManager; - mTaskOrganizer = organizer; - mTaskViewTransitions = transitions; - mMainExecutor = mainExecutor; - mBackgroundThread = bgExceutor; - mSyncQueue = syncTransactionQueue; - if (isFloatingTasksEnabled()) { - shellInit.addInitCallback(this::onInit, this); - } - } - - protected void onInit() { - mShellController.addConfigurationChangeListener(this); - mShellController.addExternalInterface(KEY_EXTRA_SHELL_FLOATING_TASKS, - this::createExternalInterface, this); - mShellCommandHandler.addDumpCallback(this::dump, this); - } - - /** Only used for testing. */ - @VisibleForTesting - void setConfig(Configuration config) { - mConfig = config; - } - - /** Only used for testing. */ - @VisibleForTesting - void setFloatingTasksEnabled(boolean enabled) { - mFloatingTasksEnabledForTests = enabled; - } - - /** Whether the floating layer is available. */ - boolean isFloatingLayerAvailable() { - Configuration config = mConfig == null - ? mContext.getResources().getConfiguration() - : mConfig; - return config.smallestScreenWidthDp >= SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET; - } - - /** Whether floating tasks are enabled. */ - boolean isFloatingTasksEnabled() { - return FLOATING_TASKS_ENABLED || mFloatingTasksEnabledForTests; - } - - private ExternalInterfaceBinder createExternalInterface() { - return new IFloatingTasksImpl(this); - } - - @Override - public void onThemeChanged() { - if (mIsFloatingLayerAdded) { - mFloatingTaskLayer.updateSizes(); - } - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - // TODO: probably other stuff here to do (e.g. handle rotation) - if (mIsFloatingLayerAdded) { - mFloatingTaskLayer.updateSizes(); - } - } - - /** Returns false if the task shouldn't be shown. */ - private boolean canShowTask(Intent intent) { - ProtoLog.d(WM_SHELL_FLOATING_APPS, "canShowTask -- %s", intent); - if (!isFloatingTasksEnabled() || !isFloatingLayerAvailable()) return false; - if (intent == null) { - ProtoLog.e(WM_SHELL_FLOATING_APPS, "canShowTask given null intent, doing nothing"); - return false; - } - return true; - } - - /** Returns true if the task was or should be shown as a bubble. */ - private boolean maybeShowTaskAsBubble(Intent intent) { - if (SHOW_FLOATING_TASKS_AS_BUBBLES && mBubbleController != null) { - removeFloatingLayer(); - if (intent.getPackage() != null) { - mBubbleController.addAppBubble(intent); - ProtoLog.d(WM_SHELL_FLOATING_APPS, "showing floating task as bubble: %s", intent); - } else { - ProtoLog.d(WM_SHELL_FLOATING_APPS, - "failed to show floating task as bubble: %s; unknown package", intent); - } - return true; - } - return false; - } - - /** - * Shows, stashes, or un-stashes the floating task depending on state: - * - If there is no floating task for this intent, it shows this the provided task. - * - If there is a floating task for this intent, but it's stashed, this un-stashes it. - * - If there is a floating task for this intent, and it's not stashed, this stashes it. - */ - public void showOrSetStashed(Intent intent) { - if (!canShowTask(intent)) return; - if (maybeShowTaskAsBubble(intent)) return; - - addFloatingLayer(); - - if (isTaskAttached(mTask) && intent.filterEquals(mTask.intent)) { - // The task is already added, toggle the stash state. - mFloatingTaskLayer.setStashed(mTask, !mTask.floatingView.isStashed()); - return; - } - - // If we're here it's either a new or different task - showNewTask(intent); - } - - /** - * Shows a floating task with the provided intent. - * If the same task is present it will un-stash it or do nothing if it is already un-stashed. - * Removes any other floating tasks that might exist. - */ - public void showTask(Intent intent) { - if (!canShowTask(intent)) return; - if (maybeShowTaskAsBubble(intent)) return; - - addFloatingLayer(); - - if (isTaskAttached(mTask) && intent.filterEquals(mTask.intent)) { - // The task is already added, show it if it's stashed. - if (mTask.floatingView.isStashed()) { - mFloatingTaskLayer.setStashed(mTask, false); - } - return; - } - showNewTask(intent); - } - - private void showNewTask(Intent intent) { - if (mTask != null && !intent.filterEquals(mTask.intent)) { - mFloatingTaskLayer.removeAllTaskViews(); - mTask.floatingView.cleanUpTaskView(); - mTask = null; - } - - FloatingTaskView ftv = new FloatingTaskView(mContext, this); - ftv.createTaskView(mContext, mTaskOrganizer, mTaskViewTransitions, mSyncQueue); - - mTask = new Task(); - mTask.floatingView = ftv; - mTask.intent = intent; - - // Add & start the task. - mFloatingTaskLayer.addTask(mTask); - ProtoLog.d(WM_SHELL_FLOATING_APPS, "showNewTask, startingIntent: %s", intent); - mTask.floatingView.startTask(mMainExecutor, mTask); - } - - /** - * Removes the task and cleans up the view. - */ - public void removeTask() { - if (mTask != null) { - ProtoLog.d(WM_SHELL_FLOATING_APPS, "Removing task with id=%d", mTask.taskId); - - if (mTask.floatingView != null) { - // TODO: animate it - mFloatingTaskLayer.removeView(mTask.floatingView); - mTask.floatingView.cleanUpTaskView(); - } - removeFloatingLayer(); - } - } - - /** - * Whether there is a floating task and if it is stashed. - */ - public boolean isStashed() { - return isTaskAttached(mTask) && mTask.floatingView.isStashed(); - } - - /** - * If a floating task exists, this sets whether it is stashed and animates if needed. - */ - public void setStashed(boolean shouldStash) { - if (mTask != null && mTask.floatingView != null && mIsFloatingLayerAdded) { - mFloatingTaskLayer.setStashed(mTask, shouldStash); - } - } - - /** - * Saves the last position the floating task was in so that it can be put there again. - */ - public void setLastPosition(int x, int y) { - mLastPosition.set(x, y); - } - - /** - * Returns the last position the floating task was in. - */ - public Point getLastPosition() { - return mLastPosition; - } - - /** - * Whether the provided task has a view that's attached to the floating layer. - */ - private boolean isTaskAttached(Task t) { - return t != null && t.floatingView != null - && mIsFloatingLayerAdded - && mFloatingTaskLayer.getTaskViewCount() > 0 - && Objects.equals(mFloatingTaskLayer.getFirstTaskView(), t.floatingView); - } - - // TODO: when this is added, if there are bubbles, they get hidden? Is only one layer of this - // type allowed? Bubbles & floating tasks should probably be in the same layer to reduce - // # of windows. - private void addFloatingLayer() { - if (mIsFloatingLayerAdded) { - return; - } - - mFloatingTaskLayer = new FloatingTaskLayer(mContext, this, mWindowManager); - - WindowManager.LayoutParams params = new WindowManager.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, - PixelFormat.TRANSLUCENT - ); - params.setTrustedOverlay(); - params.setFitInsetsTypes(0); - params.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; - params.setTitle("FloatingTaskLayer"); - params.packageName = mContext.getPackageName(); - params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; - - try { - mIsFloatingLayerAdded = true; - mWindowManager.addView(mFloatingTaskLayer, params); - } catch (IllegalStateException e) { - // This means the floating layer has already been added which shouldn't happen. - e.printStackTrace(); - } - } - - private void removeFloatingLayer() { - if (!mIsFloatingLayerAdded) { - return; - } - try { - mIsFloatingLayerAdded = false; - if (mFloatingTaskLayer != null) { - mWindowManager.removeView(mFloatingTaskLayer); - } - } catch (IllegalArgumentException e) { - // This means the floating layer has already been removed which shouldn't happen. - e.printStackTrace(); - } - } - - /** - * Description of current floating task state. - */ - private void dump(PrintWriter pw, String prefix) { - pw.println("FloatingTaskController state:"); - pw.print(" isFloatingLayerAvailable= "); pw.println(isFloatingLayerAvailable()); - pw.print(" isFloatingTasksEnabled= "); pw.println(isFloatingTasksEnabled()); - pw.print(" mIsFloatingLayerAdded= "); pw.println(mIsFloatingLayerAdded); - pw.print(" mLastPosition= "); pw.println(mLastPosition); - pw.println(); - } - - /** Returns the {@link FloatingTasks} implementation. */ - public FloatingTasks asFloatingTasks() { - return mImpl; - } - - @Override - public Context getContext() { - return mContext; - } - - @Override - public ShellExecutor getRemoteCallExecutor() { - return mMainExecutor; - } - - /** - * The interface for calls from outside the shell, within the host process. - */ - @ExternalThread - private class FloatingTaskImpl implements FloatingTasks { - @Override - public void showOrSetStashed(Intent intent) { - mMainExecutor.execute(() -> FloatingTasksController.this.showOrSetStashed(intent)); - } - } - - /** - * The interface for calls from outside the host process. - */ - @BinderThread - private static class IFloatingTasksImpl extends IFloatingTasks.Stub - implements ExternalInterfaceBinder { - private FloatingTasksController mController; - - IFloatingTasksImpl(FloatingTasksController controller) { - mController = controller; - } - - /** - * Invalidates this instance, preventing future calls from updating the controller. - */ - @Override - public void invalidate() { - mController = null; - } - - public void showTask(Intent intent) { - executeRemoteCallWithTaskPermission(mController, "showTask", - (controller) -> controller.showTask(intent)); - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java deleted file mode 100644 index c922109751ba..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java +++ /dev/null @@ -1,73 +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.wm.shell.floating.views; - -import android.annotation.Nullable; -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.LinearLayout; - -import com.android.wm.shell.R; - -/** - * Displays the menu items for a floating task view (e.g. close). - */ -public class FloatingMenuView extends LinearLayout { - - private int mItemSize; - private int mItemMargin; - - public FloatingMenuView(Context context) { - super(context); - setOrientation(LinearLayout.HORIZONTAL); - setGravity(Gravity.CENTER); - - mItemSize = context.getResources().getDimensionPixelSize( - R.dimen.floating_task_menu_item_size); - mItemMargin = context.getResources().getDimensionPixelSize( - R.dimen.floating_task_menu_item_padding); - } - - /** Adds a clickable item to the menu bar. Items are ordered as added. */ - public void addMenuItem(@Nullable Drawable drawable, View.OnClickListener listener) { - ImageView itemView = new ImageView(getContext()); - itemView.setScaleType(ImageView.ScaleType.CENTER); - if (drawable != null) { - itemView.setImageDrawable(drawable); - } - LinearLayout.LayoutParams lp = new LayoutParams(mItemSize, - ViewGroup.LayoutParams.MATCH_PARENT); - lp.setMarginStart(mItemMargin); - lp.setMarginEnd(mItemMargin); - addView(itemView, lp); - - itemView.setOnClickListener(listener); - } - - /** - * The menu extends past the top of the TaskView because of the rounded corners. This means - * to center content in the menu we must subtract the radius (i.e. the amount of space covered - * by TaskView). - */ - public void setCornerRadius(float radius) { - setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), (int) radius); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java deleted file mode 100644 index 16dab2415bf2..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java +++ /dev/null @@ -1,687 +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.wm.shell.floating.views; - -import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.annotation.Nullable; -import android.content.Context; -import android.graphics.Color; -import android.graphics.Insets; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.Region; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewPropertyAnimator; -import android.view.ViewTreeObserver; -import android.view.WindowInsets; -import android.view.WindowManager; -import android.view.WindowMetrics; -import android.widget.FrameLayout; - -import androidx.annotation.NonNull; -import androidx.dynamicanimation.animation.DynamicAnimation; -import androidx.dynamicanimation.animation.FlingAnimation; - -import com.android.internal.protolog.common.ProtoLog; -import com.android.wm.shell.R; -import com.android.wm.shell.floating.FloatingDismissController; -import com.android.wm.shell.floating.FloatingTasksController; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * This is the layout that {@link FloatingTaskView}s are contained in. It handles input and - * movement of the task views. - */ -public class FloatingTaskLayer extends FrameLayout - implements ViewTreeObserver.OnComputeInternalInsetsListener { - - private static final String TAG = FloatingTaskLayer.class.getSimpleName(); - - /** How big to make the task view based on screen width of the largest size. */ - private static final float START_SIZE_WIDTH_PERCENT = 0.33f; - /** Min fling velocity required to move the view from one side of the screen to the other. */ - private static final float ESCAPE_VELOCITY = 750f; - /** Amount of friction to apply to fling animations. */ - private static final float FLING_FRICTION = 1.9f; - - private final FloatingTasksController mController; - private final FloatingDismissController mDismissController; - private final WindowManager mWindowManager; - private final TouchHandlerImpl mTouchHandler; - - private final Region mTouchableRegion = new Region(); - private final Rect mPositionRect = new Rect(); - private final Point mDefaultStartPosition = new Point(); - private final Point mTaskViewSize = new Point(); - private WindowInsets mWindowInsets; - private int mVerticalPadding; - private int mOverhangWhenStashed; - - private final List<Rect> mSystemGestureExclusionRects = Collections.singletonList(new Rect()); - private ViewTreeObserver.OnDrawListener mSystemGestureExclusionListener = - this::updateSystemGestureExclusion; - - /** Interface allowing something to handle the touch events going to a task. */ - interface FloatingTaskTouchHandler { - void onDown(@NonNull FloatingTaskView v, @NonNull MotionEvent ev, - float viewInitialX, float viewInitialY); - - void onMove(@NonNull FloatingTaskView v, @NonNull MotionEvent ev, - float dx, float dy); - - void onUp(@NonNull FloatingTaskView v, @NonNull MotionEvent ev, - float dx, float dy, float velX, float velY); - - void onClick(@NonNull FloatingTaskView v); - } - - public FloatingTaskLayer(Context context, - FloatingTasksController controller, - WindowManager windowManager) { - super(context); - // TODO: Why is this necessary? Without it FloatingTaskView does not render correctly. - setBackgroundColor(Color.argb(0, 0, 0, 0)); - - mController = controller; - mWindowManager = windowManager; - updateSizes(); - - // TODO: Might make sense to put dismiss controller in the touch handler since that's the - // main user of dismiss controller. - mDismissController = new FloatingDismissController(context, mController, this); - mTouchHandler = new TouchHandlerImpl(); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - getViewTreeObserver().addOnComputeInternalInsetsListener(this); - getViewTreeObserver().addOnDrawListener(mSystemGestureExclusionListener); - setOnApplyWindowInsetsListener((view, windowInsets) -> { - if (!windowInsets.equals(mWindowInsets)) { - mWindowInsets = windowInsets; - updateSizes(); - } - return windowInsets; - }); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - getViewTreeObserver().removeOnComputeInternalInsetsListener(this); - getViewTreeObserver().removeOnDrawListener(mSystemGestureExclusionListener); - } - - @Override - public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) { - inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); - mTouchableRegion.setEmpty(); - getTouchableRegion(mTouchableRegion); - inoutInfo.touchableRegion.set(mTouchableRegion); - } - - /** Adds a floating task to the layout. */ - public void addTask(FloatingTasksController.Task task) { - if (task.floatingView == null) return; - - task.floatingView.setTouchHandler(mTouchHandler); - addView(task.floatingView, new LayoutParams(mTaskViewSize.x, mTaskViewSize.y)); - updateTaskViewPosition(task.floatingView); - } - - /** Animates the stashed state of the provided task, if it's part of the floating layer. */ - public void setStashed(FloatingTasksController.Task task, boolean shouldStash) { - if (task.floatingView != null && task.floatingView.getParent() == this) { - mTouchHandler.stashTaskView(task.floatingView, shouldStash); - } - } - - /** Removes all {@link FloatingTaskView} from the layout. */ - public void removeAllTaskViews() { - int childCount = getChildCount(); - ArrayList<View> viewsToRemove = new ArrayList<>(); - for (int i = 0; i < childCount; i++) { - if (getChildAt(i) instanceof FloatingTaskView) { - viewsToRemove.add(getChildAt(i)); - } - } - for (View v : viewsToRemove) { - removeView(v); - } - } - - /** Returns the number of task views in the layout. */ - public int getTaskViewCount() { - int taskViewCount = 0; - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - if (getChildAt(i) instanceof FloatingTaskView) { - taskViewCount++; - } - } - return taskViewCount; - } - - /** - * Called when the task view is un-stuck from the dismiss target. - * @param v the task view being moved. - * @param velX the x velocity of the motion event. - * @param velY the y velocity of the motion event. - * @param wasFlungOut true if the user flung the task view out of the dismiss target (i.e. there - * was an 'up' event), otherwise the user is still dragging. - */ - public void onUnstuckFromTarget(FloatingTaskView v, float velX, float velY, - boolean wasFlungOut) { - mTouchHandler.onUnstuckFromTarget(v, velX, velY, wasFlungOut); - } - - /** - * Updates dimensions and applies them to any task views. - */ - public void updateSizes() { - if (mDismissController != null) { - mDismissController.updateSizes(); - } - - mOverhangWhenStashed = getResources().getDimensionPixelSize( - R.dimen.floating_task_stash_offset); - mVerticalPadding = getResources().getDimensionPixelSize( - R.dimen.floating_task_vertical_padding); - - WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics(); - WindowInsets windowInsets = windowMetrics.getWindowInsets(); - Insets insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars() - | WindowInsets.Type.statusBars() - | WindowInsets.Type.displayCutout()); - Rect bounds = windowMetrics.getBounds(); - mPositionRect.set(bounds.left + insets.left, - bounds.top + insets.top + mVerticalPadding, - bounds.right - insets.right, - bounds.bottom - insets.bottom - mVerticalPadding); - - int taskViewWidth = Math.max(bounds.height(), bounds.width()); - int taskViewHeight = Math.min(bounds.height(), bounds.width()); - taskViewHeight = taskViewHeight - (insets.top + insets.bottom + (mVerticalPadding * 2)); - mTaskViewSize.set((int) (taskViewWidth * START_SIZE_WIDTH_PERCENT), taskViewHeight); - mDefaultStartPosition.set(mPositionRect.left, mPositionRect.top); - - // Update existing views - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - if (getChildAt(i) instanceof FloatingTaskView) { - FloatingTaskView child = (FloatingTaskView) getChildAt(i); - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - lp.width = mTaskViewSize.x; - lp.height = mTaskViewSize.y; - child.setLayoutParams(lp); - updateTaskViewPosition(child); - } - } - } - - /** Returns the first floating task view in the layout. (Currently only ever 1 view). */ - @Nullable - public FloatingTaskView getFirstTaskView() { - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child instanceof FloatingTaskView) { - return (FloatingTaskView) child; - } - } - return null; - } - - private void updateTaskViewPosition(FloatingTaskView floatingView) { - Point lastPosition = mController.getLastPosition(); - if (lastPosition.x == -1 && lastPosition.y == -1) { - floatingView.setX(mDefaultStartPosition.x); - floatingView.setY(mDefaultStartPosition.y); - } else { - floatingView.setX(lastPosition.x); - floatingView.setY(lastPosition.y); - } - if (mTouchHandler.isStashedPosition(floatingView)) { - floatingView.setStashed(true); - } - floatingView.updateLocation(); - } - - /** - * Updates the area of the screen that shouldn't allow the back gesture due to the placement - * of task view (i.e. when task view is stashed on an edge, tapping or swiping that edge would - * un-stash the task view instead of performing the back gesture). - */ - private void updateSystemGestureExclusion() { - Rect excludeZone = mSystemGestureExclusionRects.get(0); - FloatingTaskView floatingTaskView = getFirstTaskView(); - if (floatingTaskView != null && floatingTaskView.isStashed()) { - excludeZone.set(floatingTaskView.getLeft(), - floatingTaskView.getTop(), - floatingTaskView.getRight(), - floatingTaskView.getBottom()); - excludeZone.offset((int) (floatingTaskView.getTranslationX()), - (int) (floatingTaskView.getTranslationY())); - setSystemGestureExclusionRects(mSystemGestureExclusionRects); - } else { - excludeZone.setEmpty(); - setSystemGestureExclusionRects(Collections.emptyList()); - } - } - - /** - * Fills in the touchable region for floating windows. This is used by WindowManager to - * decide which touch events go to the floating windows. - */ - private void getTouchableRegion(Region outRegion) { - int childCount = getChildCount(); - Rect temp = new Rect(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child instanceof FloatingTaskView) { - child.getBoundsOnScreen(temp); - outRegion.op(temp, Region.Op.UNION); - } - } - } - - /** - * Implementation of the touch handler. Animates the task view based on touch events. - */ - private class TouchHandlerImpl implements FloatingTaskTouchHandler { - /** - * The view can be stashed by swiping it towards the current edge or moving it there. If - * the view gets moved in a way that is not one of these gestures, this is flipped to false. - */ - private boolean mCanStash = true; - /** - * This is used to indicate that the view has been un-stuck from the dismiss target and - * needs to spring to the current touch location. - */ - // TODO: implement this behavior - private boolean mSpringToTouchOnNextMotionEvent = false; - - private ArrayList<FlingAnimation> mFlingAnimations; - private ViewPropertyAnimator mViewPropertyAnimation; - - private float mViewInitialX; - private float mViewInitialY; - - private float[] mMinMax = new float[2]; - - @Override - public void onDown(@NonNull FloatingTaskView v, @NonNull MotionEvent ev, float viewInitialX, - float viewInitialY) { - mCanStash = true; - mViewInitialX = viewInitialX; - mViewInitialY = viewInitialY; - mDismissController.setUpMagneticObject(v); - mDismissController.passEventToMagnetizedObject(ev); - } - - @Override - public void onMove(@NonNull FloatingTaskView v, @NonNull MotionEvent ev, - float dx, float dy) { - // Shows the magnetic dismiss target if needed. - mDismissController.showDismiss(/* show= */ true); - - // Send it to magnetic target first. - if (mDismissController.passEventToMagnetizedObject(ev)) { - v.setStashed(false); - mCanStash = true; - - return; - } - - // If we're here magnetic target didn't want it so move as per normal. - - v.setTranslationX(capX(v, mViewInitialX + dx, /* isMoving= */ true)); - v.setTranslationY(capY(v, mViewInitialY + dy)); - if (v.isStashed()) { - // Check if we've moved far enough to be not stashed. - final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f); - final boolean viewInitiallyOnLeftSide = mViewInitialX < centerX; - if (viewInitiallyOnLeftSide) { - if (v.getTranslationX() > mPositionRect.left) { - v.setStashed(false); - mCanStash = true; - } - } else if (v.getTranslationX() + v.getWidth() < mPositionRect.right) { - v.setStashed(false); - mCanStash = true; - } - } - } - - // Reference for math / values: StackAnimationController#flingStackThenSpringToEdge. - // TODO clean up the code here, pretty hard to comprehend - // TODO code here doesn't work the best when in portrait (e.g. can't fling up/down on edges) - @Override - public void onUp(@NonNull FloatingTaskView v, @NonNull MotionEvent ev, - float dx, float dy, float velX, float velY) { - - // Send it to magnetic target first. - if (mDismissController.passEventToMagnetizedObject(ev)) { - v.setStashed(false); - return; - } - mDismissController.showDismiss(/* show= */ false); - - // If we're here magnetic target didn't want it so handle up as per normal. - - final float x = capX(v, mViewInitialX + dx, /* isMoving= */ false); - final float centerX = mPositionRect.centerX(); - final boolean viewInitiallyOnLeftSide = mViewInitialX + v.getWidth() < centerX; - final boolean viewOnLeftSide = x + v.getWidth() < centerX; - final boolean isFling = Math.abs(velX) > ESCAPE_VELOCITY; - final boolean isFlingLeft = isFling && velX < ESCAPE_VELOCITY; - // TODO: check velX here sometimes it doesn't stash on move when I think it should - final boolean shouldStashFromMove = - (velX < 0 && v.getTranslationX() < mPositionRect.left) - || (velX > 0 - && v.getTranslationX() + v.getWidth() > mPositionRect.right); - final boolean shouldStashFromFling = viewInitiallyOnLeftSide == viewOnLeftSide - && isFling - && ((viewOnLeftSide && velX < ESCAPE_VELOCITY) - || (!viewOnLeftSide && velX > ESCAPE_VELOCITY)); - final boolean shouldStash = mCanStash && (shouldStashFromFling || shouldStashFromMove); - - ProtoLog.d(WM_SHELL_FLOATING_APPS, - "shouldStash=%s shouldStashFromFling=%s shouldStashFromMove=%s" - + " viewInitiallyOnLeftSide=%s viewOnLeftSide=%s isFling=%s velX=%f" - + " isStashed=%s", shouldStash, shouldStashFromFling, shouldStashFromMove, - viewInitiallyOnLeftSide, viewOnLeftSide, isFling, velX, v.isStashed()); - - if (v.isStashed()) { - mMinMax[0] = viewOnLeftSide - ? mPositionRect.left - v.getWidth() + mOverhangWhenStashed - : mPositionRect.right - v.getWidth(); - mMinMax[1] = viewOnLeftSide - ? mPositionRect.left - : mPositionRect.right - mOverhangWhenStashed; - } else { - populateMinMax(v, viewOnLeftSide, shouldStash, mMinMax); - } - - boolean movingLeft = isFling ? isFlingLeft : viewOnLeftSide; - float destinationRelativeX = movingLeft - ? mMinMax[0] - : mMinMax[1]; - - // TODO: why is this necessary / when does this happen? - if (mMinMax[1] < v.getTranslationX()) { - mMinMax[1] = v.getTranslationX(); - } - if (v.getTranslationX() < mMinMax[0]) { - mMinMax[0] = v.getTranslationX(); - } - - // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity - // so that it'll make it all the way to the side of the screen. - final float minimumVelocityToReachEdge = - getMinimumVelocityToReachEdge(v, destinationRelativeX); - final float startXVelocity = movingLeft - ? Math.min(minimumVelocityToReachEdge, velX) - : Math.max(minimumVelocityToReachEdge, velX); - - cancelAnyAnimations(v); - - mFlingAnimations = getAnimationForUpEvent(v, shouldStash, - startXVelocity, mMinMax[0], mMinMax[1], destinationRelativeX); - for (int i = 0; i < mFlingAnimations.size(); i++) { - mFlingAnimations.get(i).start(); - } - } - - @Override - public void onClick(@NonNull FloatingTaskView v) { - if (v.isStashed()) { - final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f); - final boolean viewOnLeftSide = v.getTranslationX() < centerX; - final float destinationRelativeX = viewOnLeftSide - ? mPositionRect.left - : mPositionRect.right - v.getWidth(); - final float minimumVelocityToReachEdge = - getMinimumVelocityToReachEdge(v, destinationRelativeX); - populateMinMax(v, viewOnLeftSide, /* stashed= */ true, mMinMax); - - cancelAnyAnimations(v); - - FlingAnimation flingAnimation = new FlingAnimation(v, - DynamicAnimation.TRANSLATION_X); - flingAnimation.setFriction(FLING_FRICTION) - .setStartVelocity(minimumVelocityToReachEdge) - .setMinValue(mMinMax[0]) - .setMaxValue(mMinMax[1]) - .addEndListener((animation, canceled, value, velocity) -> { - if (canceled) return; - mController.setLastPosition((int) v.getTranslationX(), - (int) v.getTranslationY()); - v.setStashed(false); - v.updateLocation(); - }); - mFlingAnimations = new ArrayList<>(); - mFlingAnimations.add(flingAnimation); - flingAnimation.start(); - } - } - - public void onUnstuckFromTarget(FloatingTaskView v, float velX, float velY, - boolean wasFlungOut) { - if (wasFlungOut) { - snapTaskViewToEdge(v, velX, /* shouldStash= */ false); - } else { - // TODO: use this for something / to spring the view to the touch location - mSpringToTouchOnNextMotionEvent = true; - } - } - - public void stashTaskView(FloatingTaskView v, boolean shouldStash) { - if (v.isStashed() == shouldStash) { - return; - } - final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f); - final boolean viewOnLeftSide = v.getTranslationX() < centerX; - snapTaskViewToEdge(v, viewOnLeftSide ? -ESCAPE_VELOCITY : ESCAPE_VELOCITY, shouldStash); - } - - public boolean isStashedPosition(View v) { - return v.getTranslationX() < mPositionRect.left - || v.getTranslationX() + v.getWidth() > mPositionRect.right; - } - - // TODO: a lot of this is duplicated in onUp -- can it be unified? - private void snapTaskViewToEdge(FloatingTaskView v, float velX, boolean shouldStash) { - final boolean movingLeft = velX < ESCAPE_VELOCITY; - populateMinMax(v, movingLeft, shouldStash, mMinMax); - float destinationRelativeX = movingLeft - ? mMinMax[0] - : mMinMax[1]; - - // TODO: why is this necessary / when does this happen? - if (mMinMax[1] < v.getTranslationX()) { - mMinMax[1] = v.getTranslationX(); - } - if (v.getTranslationX() < mMinMax[0]) { - mMinMax[0] = v.getTranslationX(); - } - - // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity - // so that it'll make it all the way to the side of the screen. - final float minimumVelocityToReachEdge = - getMinimumVelocityToReachEdge(v, destinationRelativeX); - final float startXVelocity = movingLeft - ? Math.min(minimumVelocityToReachEdge, velX) - : Math.max(minimumVelocityToReachEdge, velX); - - cancelAnyAnimations(v); - - mFlingAnimations = getAnimationForUpEvent(v, - shouldStash, startXVelocity, mMinMax[0], mMinMax[1], - destinationRelativeX); - for (int i = 0; i < mFlingAnimations.size(); i++) { - mFlingAnimations.get(i).start(); - } - } - - private void cancelAnyAnimations(FloatingTaskView v) { - if (mFlingAnimations != null) { - for (int i = 0; i < mFlingAnimations.size(); i++) { - if (mFlingAnimations.get(i).isRunning()) { - mFlingAnimations.get(i).cancel(); - } - } - } - if (mViewPropertyAnimation != null) { - mViewPropertyAnimation.cancel(); - mViewPropertyAnimation = null; - } - } - - private ArrayList<FlingAnimation> getAnimationForUpEvent(FloatingTaskView v, - boolean shouldStash, float startVelX, float minValue, float maxValue, - float destinationRelativeX) { - final float ty = v.getTranslationY(); - final ArrayList<FlingAnimation> animations = new ArrayList<>(); - if (ty != capY(v, ty)) { - // The view was being dismissed so the Y is out of bounds, need to animate that. - FlingAnimation yFlingAnimation = new FlingAnimation(v, - DynamicAnimation.TRANSLATION_Y); - yFlingAnimation.setFriction(FLING_FRICTION) - .setStartVelocity(startVelX) - .setMinValue(mPositionRect.top) - .setMaxValue(mPositionRect.bottom - mTaskViewSize.y); - animations.add(yFlingAnimation); - } - FlingAnimation flingAnimation = new FlingAnimation(v, DynamicAnimation.TRANSLATION_X); - flingAnimation.setFriction(FLING_FRICTION) - .setStartVelocity(startVelX) - .setMinValue(minValue) - .setMaxValue(maxValue) - .addEndListener((animation, canceled, value, velocity) -> { - if (canceled) return; - Runnable endAction = () -> { - v.setStashed(shouldStash); - v.updateLocation(); - if (!v.isStashed()) { - mController.setLastPosition((int) v.getTranslationX(), - (int) v.getTranslationY()); - } - }; - if (!shouldStash) { - final int xTranslation = (int) v.getTranslationX(); - if (xTranslation != destinationRelativeX) { - // TODO: this animation doesn't feel great, should figure out - // a better way to do this or remove the need for it all together. - mViewPropertyAnimation = v.animate() - .translationX(destinationRelativeX) - .setListener(getAnimationListener(endAction)); - mViewPropertyAnimation.start(); - } else { - endAction.run(); - } - } else { - endAction.run(); - } - }); - animations.add(flingAnimation); - return animations; - } - - private AnimatorListenerAdapter getAnimationListener(Runnable endAction) { - return new AnimatorListenerAdapter() { - boolean translationCanceled = false; - @Override - public void onAnimationCancel(Animator animation) { - super.onAnimationCancel(animation); - translationCanceled = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - if (!translationCanceled) { - endAction.run(); - } - } - }; - } - - private void populateMinMax(FloatingTaskView v, boolean onLeft, boolean shouldStash, - float[] out) { - if (shouldStash) { - out[0] = onLeft - ? mPositionRect.left - v.getWidth() + mOverhangWhenStashed - : mPositionRect.right - v.getWidth(); - out[1] = onLeft - ? mPositionRect.left - : mPositionRect.right - mOverhangWhenStashed; - } else { - out[0] = mPositionRect.left; - out[1] = mPositionRect.right - mTaskViewSize.x; - } - } - - private float getMinimumVelocityToReachEdge(FloatingTaskView v, - float destinationRelativeX) { - // Minimum velocity required for the view to make it to the targeted side of the screen, - // taking friction into account (4.2f is the number that friction scalars are multiplied - // by in DynamicAnimation.DragForce). This is an estimate and could be slightly off, the - // animation at the end will ensure that it reaches the destination X regardless. - return (destinationRelativeX - v.getTranslationX()) * (FLING_FRICTION * 4.2f); - } - - private float capX(FloatingTaskView v, float x, boolean isMoving) { - final int width = v.getWidth(); - if (v.isStashed() || isMoving) { - if (x < mPositionRect.left - v.getWidth() + mOverhangWhenStashed) { - return mPositionRect.left - v.getWidth() + mOverhangWhenStashed; - } - if (x > mPositionRect.right - mOverhangWhenStashed) { - return mPositionRect.right - mOverhangWhenStashed; - } - } else { - if (x < mPositionRect.left) { - return mPositionRect.left; - } - if (x > mPositionRect.right - width) { - return mPositionRect.right - width; - } - } - return x; - } - - private float capY(FloatingTaskView v, float y) { - final int height = v.getHeight(); - if (y < mPositionRect.top) { - return mPositionRect.top; - } - if (y > mPositionRect.bottom - height) { - return mPositionRect.bottom - height; - } - return y; - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java deleted file mode 100644 index 581204a82ec7..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java +++ /dev/null @@ -1,385 +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.wm.shell.floating.views; - -import static android.app.ActivityTaskManager.INVALID_TASK_ID; -import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; -import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; - -import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS; - -import android.app.ActivityManager; -import android.app.ActivityOptions; -import android.app.ActivityTaskManager; -import android.app.PendingIntent; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.graphics.Outline; -import android.graphics.Rect; -import android.os.RemoteException; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewOutlineProvider; -import android.widget.FrameLayout; - -import androidx.annotation.NonNull; - -import com.android.internal.policy.ScreenDecorationsUtils; -import com.android.internal.protolog.common.ProtoLog; -import com.android.wm.shell.R; -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.TaskView; -import com.android.wm.shell.TaskViewTransitions; -import com.android.wm.shell.bubbles.RelativeTouchListener; -import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.common.annotations.ShellMainThread; -import com.android.wm.shell.floating.FloatingTasksController; - -/** - * A view that holds a floating task using {@link TaskView} along with additional UI to manage - * the task. - */ -public class FloatingTaskView extends FrameLayout { - - private static final String TAG = FloatingTaskView.class.getSimpleName(); - - private FloatingTasksController mController; - - private FloatingMenuView mMenuView; - private int mMenuHeight; - private TaskView mTaskView; - - private float mCornerRadius = 0f; - private int mBackgroundColor; - - private FloatingTasksController.Task mTask; - - private boolean mIsStashed; - - /** - * Creates a floating task view. - * - * @param context the context to use. - * @param controller the controller to notify about changes in the floating task (e.g. removal). - */ - public FloatingTaskView(Context context, FloatingTasksController controller) { - super(context); - mController = controller; - setElevation(getResources().getDimensionPixelSize(R.dimen.floating_task_elevation)); - mMenuHeight = context.getResources().getDimensionPixelSize(R.dimen.floating_task_menu_size); - mMenuView = new FloatingMenuView(context); - addView(mMenuView); - - applyThemeAttrs(); - - setClipToOutline(true); - setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius); - } - }); - } - - // TODO: call this when theme/config changes - void applyThemeAttrs() { - boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows( - mContext.getResources()); - final TypedArray ta = mContext.obtainStyledAttributes(new int[] { - android.R.attr.dialogCornerRadius, - android.R.attr.colorBackgroundFloating}); - mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0; - mCornerRadius = mCornerRadius / 2f; - mBackgroundColor = ta.getColor(1, Color.WHITE); - - ta.recycle(); - - mMenuView.setCornerRadius(mCornerRadius); - mMenuHeight = getResources().getDimensionPixelSize( - R.dimen.floating_task_menu_size); - - if (mTaskView != null) { - mTaskView.setCornerRadius(mCornerRadius); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - int height = MeasureSpec.getSize(heightMeasureSpec); - - // Add corner radius here so that the menu extends behind the rounded corners of TaskView. - int menuViewHeight = Math.min((int) (mMenuHeight + mCornerRadius), height); - measureChild(mMenuView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight, - MeasureSpec.getMode(heightMeasureSpec))); - - if (mTaskView != null) { - int taskViewHeight = height - menuViewHeight; - measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(taskViewHeight, - MeasureSpec.getMode(heightMeasureSpec))); - } - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - // Drag handle above - final int dragHandleBottom = t + mMenuView.getMeasuredHeight(); - mMenuView.layout(l, t, r, dragHandleBottom); - if (mTaskView != null) { - // Subtract radius so that the menu extends behind the rounded corners of TaskView. - mTaskView.layout(l, (int) (dragHandleBottom - mCornerRadius), r, - dragHandleBottom + mTaskView.getMeasuredHeight()); - } - } - - /** - * Constructs the TaskView to display the task. Must be called for {@link #startTask} to work. - */ - public void createTaskView(Context context, ShellTaskOrganizer organizer, - TaskViewTransitions transitions, SyncTransactionQueue syncQueue) { - mTaskView = new TaskView(context, organizer, transitions, syncQueue); - addView(mTaskView); - mTaskView.setEnableSurfaceClipping(true); - mTaskView.setCornerRadius(mCornerRadius); - } - - /** - * Starts the provided task in the TaskView, if the TaskView exists. This should be called after - * {@link #createTaskView}. - */ - public void startTask(@ShellMainThread ShellExecutor executor, - FloatingTasksController.Task task) { - if (mTaskView == null) { - Log.e(TAG, "starting task before creating the view!"); - return; - } - mTask = task; - mTaskView.setListener(executor, mTaskViewListener); - } - - /** - * Sets the touch handler for the view. - * - * @param handler the touch handler for the view. - */ - public void setTouchHandler(FloatingTaskLayer.FloatingTaskTouchHandler handler) { - setOnTouchListener(new RelativeTouchListener() { - @Override - public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) { - handler.onDown(FloatingTaskView.this, ev, v.getTranslationX(), v.getTranslationY()); - return true; - } - - @Override - public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX, - float viewInitialY, float dx, float dy) { - handler.onMove(FloatingTaskView.this, ev, dx, dy); - } - - @Override - public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX, - float viewInitialY, float dx, float dy, float velX, float velY) { - handler.onUp(FloatingTaskView.this, ev, dx, dy, velX, velY); - } - }); - setOnClickListener(view -> { - handler.onClick(FloatingTaskView.this); - }); - - mMenuView.addMenuItem(null, view -> { - if (mIsStashed) { - // If we're stashed all clicks un-stash. - handler.onClick(FloatingTaskView.this); - } - }); - } - - private void setContentVisibility(boolean visible) { - if (mTaskView == null) return; - mTaskView.setAlpha(visible ? 1f : 0f); - } - - /** - * Sets the alpha of both this view and the TaskView. - */ - public void setTaskViewAlpha(float alpha) { - if (mTaskView != null) { - mTaskView.setAlpha(alpha); - } - setAlpha(alpha); - } - - /** - * Call when the location or size of the view has changed to update TaskView. - */ - public void updateLocation() { - if (mTaskView == null) return; - mTaskView.onLocationChanged(); - } - - private void updateMenuColor() { - ActivityManager.RunningTaskInfo info = mTaskView.getTaskInfo(); - int color = info != null ? info.taskDescription.getBackgroundColor() : -1; - if (color != -1) { - mMenuView.setBackgroundColor(color); - } else { - mMenuView.setBackgroundColor(mBackgroundColor); - } - } - - /** - * Sets whether the view is stashed or not. - * - * Also updates the touchable area based on this. If the view is stashed we don't direct taps - * on the activity to the activity, instead a tap will un-stash the view. - */ - public void setStashed(boolean isStashed) { - if (mIsStashed != isStashed) { - mIsStashed = isStashed; - if (mTaskView == null) { - return; - } - updateObscuredTouchRect(); - } - } - - /** Whether the view is stashed at the edge of the screen or not. **/ - public boolean isStashed() { - return mIsStashed; - } - - private void updateObscuredTouchRect() { - if (mIsStashed) { - Rect tmpRect = new Rect(); - getBoundsOnScreen(tmpRect); - mTaskView.setObscuredTouchRect(tmpRect); - } else { - mTaskView.setObscuredTouchRect(null); - } - } - - /** - * Whether the task needs to be restarted, this can happen when {@link #cleanUpTaskView()} has - * been called on this view or if - * {@link #startTask(ShellExecutor, FloatingTasksController.Task)} was never called. - */ - public boolean needsTaskStarted() { - // If the task needs to be restarted then TaskView would have been cleaned up. - return mTaskView == null; - } - - /** Call this when the floating task activity is no longer in use. */ - public void cleanUpTaskView() { - if (mTask != null && mTask.taskId != INVALID_TASK_ID) { - try { - ActivityTaskManager.getService().removeTask(mTask.taskId); - } catch (RemoteException e) { - Log.e(TAG, e.getMessage()); - } - } - if (mTaskView != null) { - mTaskView.release(); - removeView(mTaskView); - mTaskView = null; - } - } - - // TODO: use task background colour / how to get the taskInfo ? - private static int getDragBarColor(ActivityManager.RunningTaskInfo taskInfo) { - final int taskBgColor = taskInfo.taskDescription.getStatusBarColor(); - return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb(); - } - - private final TaskView.Listener mTaskViewListener = new TaskView.Listener() { - private boolean mInitialized = false; - private boolean mDestroyed = false; - - @Override - public void onInitialized() { - if (mDestroyed || mInitialized) { - return; - } - // Custom options so there is no activity transition animation - ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(), - /* enterResId= */ 0, /* exitResId= */ 0); - - Rect launchBounds = new Rect(); - mTaskView.getBoundsOnScreen(launchBounds); - - try { - options.setTaskAlwaysOnTop(true); - if (mTask.intent != null) { - Intent fillInIntent = new Intent(); - // Apply flags to make behaviour match documentLaunchMode=always. - fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); - fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); - - PendingIntent pi = PendingIntent.getActivity(mContext, 0, mTask.intent, - PendingIntent.FLAG_MUTABLE, - null); - mTaskView.startActivity(pi, fillInIntent, options, launchBounds); - } else { - ProtoLog.e(WM_SHELL_FLOATING_APPS, "Tried to start a task with null intent"); - } - } catch (RuntimeException e) { - ProtoLog.e(WM_SHELL_FLOATING_APPS, "Exception while starting task: %s", - e.getMessage()); - mController.removeTask(); - } - mInitialized = true; - } - - @Override - public void onReleased() { - mDestroyed = true; - } - - @Override - public void onTaskCreated(int taskId, ComponentName name) { - mTask.taskId = taskId; - updateMenuColor(); - setContentVisibility(true); - } - - @Override - public void onTaskVisibilityChanged(int taskId, boolean visible) { - setContentVisibility(visible); - } - - @Override - public void onTaskRemovalStarted(int taskId) { - // Must post because this is called from a binder thread. - post(() -> { - mController.removeTask(); - cleanUpTaskView(); - }); - } - - @Override - public void onBackPressedOnTaskRoot(int taskId) { - if (mTask.taskId == taskId && !mIsStashed) { - // TODO: is removing the window the desired behavior? - post(() -> mController.removeTask()); - } - } - }; -} diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS index f4efc374ecc2..1c28c3d58ccb 100644 --- a/libs/WindowManager/Shell/tests/OWNERS +++ b/libs/WindowManager/Shell/tests/OWNERS @@ -6,3 +6,4 @@ pablogamito@google.com lbill@google.com madym@google.com hwwang@google.com +chenghsiuchang@google.com diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java deleted file mode 100644 index d378a177650a..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java +++ /dev/null @@ -1,261 +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.wm.shell.floating; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static com.android.wm.shell.floating.FloatingTasksController.SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.Intent; -import android.content.res.Configuration; -import android.graphics.Insets; -import android.graphics.Rect; -import android.os.RemoteException; -import android.os.SystemProperties; -import android.view.WindowInsets; -import android.view.WindowManager; -import android.view.WindowMetrics; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.TaskViewTransitions; -import com.android.wm.shell.TestShellExecutor; -import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.floating.views.FloatingTaskLayer; -import com.android.wm.shell.sysui.ShellCommandHandler; -import com.android.wm.shell.sysui.ShellController; -import com.android.wm.shell.sysui.ShellInit; -import com.android.wm.shell.sysui.ShellSharedConstants; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Optional; - -/** - * Tests for the floating tasks controller. - */ -@SmallTest -@RunWith(AndroidJUnit4.class) -public class FloatingTasksControllerTest extends ShellTestCase { - // Some behavior in the controller constructor is dependent on this so we can only - // validate if it's working for the real value for those things. - private static final boolean FLOATING_TASKS_ACTUALLY_ENABLED = - SystemProperties.getBoolean("persist.wm.debug.floating_tasks", false); - - @Mock private ShellInit mShellInit; - @Mock private ShellController mShellController; - @Mock private WindowManager mWindowManager; - @Mock private ShellTaskOrganizer mTaskOrganizer; - @Captor private ArgumentCaptor<FloatingTaskLayer> mFloatingTaskLayerCaptor; - - private FloatingTasksController mController; - - @Before - public void setUp() throws RemoteException { - MockitoAnnotations.initMocks(this); - - WindowMetrics windowMetrics = mock(WindowMetrics.class); - WindowInsets windowInsets = mock(WindowInsets.class); - Insets insets = Insets.of(0, 0, 0, 0); - when(mWindowManager.getCurrentWindowMetrics()).thenReturn(windowMetrics); - when(windowMetrics.getWindowInsets()).thenReturn(windowInsets); - when(windowMetrics.getBounds()).thenReturn(new Rect(0, 0, 1000, 1000)); - when(windowInsets.getInsetsIgnoringVisibility(anyInt())).thenReturn(insets); - - // For the purposes of this test, just run everything synchronously - ShellExecutor shellExecutor = new TestShellExecutor(); - when(mTaskOrganizer.getExecutor()).thenReturn(shellExecutor); - } - - @After - public void tearDown() { - if (mController != null) { - mController.removeTask(); - mController = null; - } - } - - private void setUpTabletConfig() { - Configuration config = mock(Configuration.class); - config.smallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET; - mController.setConfig(config); - } - - private void setUpPhoneConfig() { - Configuration config = mock(Configuration.class); - config.smallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET - 1; - mController.setConfig(config); - } - - private void createController() { - mController = new FloatingTasksController(mContext, - mShellInit, - mShellController, - mock(ShellCommandHandler.class), - Optional.empty(), - mWindowManager, - mTaskOrganizer, - mock(TaskViewTransitions.class), - mock(ShellExecutor.class), - mock(ShellExecutor.class), - mock(SyncTransactionQueue.class)); - spyOn(mController); - } - - // - // Shell specific - // - @Test - public void instantiateController_addInitCallback() { - if (FLOATING_TASKS_ACTUALLY_ENABLED) { - createController(); - setUpTabletConfig(); - - verify(mShellInit, times(1)).addInitCallback(any(), any()); - } - } - - @Test - public void instantiateController_doesntAddInitCallback() { - if (!FLOATING_TASKS_ACTUALLY_ENABLED) { - createController(); - - verify(mShellInit, never()).addInitCallback(any(), any()); - } - } - - @Test - public void onInit_registerConfigChangeListener() { - if (FLOATING_TASKS_ACTUALLY_ENABLED) { - createController(); - setUpTabletConfig(); - mController.onInit(); - - verify(mShellController, times(1)).addConfigurationChangeListener(any()); - } - } - - @Test - public void onInit_addExternalInterface() { - if (FLOATING_TASKS_ACTUALLY_ENABLED) { - createController(); - setUpTabletConfig(); - mController.onInit(); - - verify(mShellController, times(1)).addExternalInterface( - ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS, any(), any()); - } - } - - // - // Tests for floating layer, which is only available for tablets. - // - - @Test - public void testIsFloatingLayerAvailable_true() { - createController(); - setUpTabletConfig(); - assertThat(mController.isFloatingLayerAvailable()).isTrue(); - } - - @Test - public void testIsFloatingLayerAvailable_false() { - createController(); - setUpPhoneConfig(); - assertThat(mController.isFloatingLayerAvailable()).isFalse(); - } - - // - // Tests for floating tasks being enabled, guarded by sysprop flag. - // - - @Test - public void testIsFloatingTasksEnabled_true() { - createController(); - mController.setFloatingTasksEnabled(true); - setUpTabletConfig(); - assertThat(mController.isFloatingTasksEnabled()).isTrue(); - } - - @Test - public void testIsFloatingTasksEnabled_false() { - createController(); - mController.setFloatingTasksEnabled(false); - setUpTabletConfig(); - assertThat(mController.isFloatingTasksEnabled()).isFalse(); - } - - // - // Tests for behavior depending on flags - // - - @Test - public void testShowTaskIntent_enabled() { - createController(); - mController.setFloatingTasksEnabled(true); - setUpTabletConfig(); - - mController.showTask(mock(Intent.class)); - verify(mWindowManager).addView(mFloatingTaskLayerCaptor.capture(), any()); - assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(1); - } - - @Test - public void testShowTaskIntent_notEnabled() { - createController(); - mController.setFloatingTasksEnabled(false); - setUpTabletConfig(); - - mController.showTask(mock(Intent.class)); - verify(mWindowManager, never()).addView(any(), any()); - } - - @Test - public void testRemoveTask() { - createController(); - mController.setFloatingTasksEnabled(true); - setUpTabletConfig(); - - mController.showTask(mock(Intent.class)); - verify(mWindowManager).addView(mFloatingTaskLayerCaptor.capture(), any()); - assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(1); - - mController.removeTask(); - verify(mWindowManager).removeView(mFloatingTaskLayerCaptor.capture()); - assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(0); - } -} diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 453a71327b50..8946516af8a2 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -122,6 +122,7 @@ public class SecureSettings { Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW, Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW, Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, + Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, Settings.Secure.ACTIVE_UNLOCK_ON_WAKE, Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT, Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index a39735ffe2c7..cbf79530a6b6 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -177,6 +177,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, NON_NEGATIVE_INTEGER_VALIDATOR); + VALIDATORS.put(Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.SHOW_MEDIA_WHEN_BYPASSING, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.FACE_UNLOCK_APP_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, BOOLEAN_VALIDATOR); diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 2f5b5f456d0b..6d542700fceb 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -125,6 +125,7 @@ android_library { "jsr330", "lottie", "LowLightDreamLib", + "motion_tool_lib", ], manifest: "AndroidManifest.xml", @@ -229,6 +230,7 @@ android_library { "jsr330", "WindowManager-Shell", "LowLightDreamLib", + "motion_tool_lib", ], libs: [ "android.test.runner", diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java index c50340cfd247..e52a57f761c7 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java @@ -82,6 +82,18 @@ public interface FalsingManager { boolean isFalseTap(@Penalty int penalty); /** + * Returns true if the FalsingManager thinks the last gesture was not a valid long tap. + * + * Use this method to validate a long tap for launching an action, like long press on a UMO + * + * The only parameter, penalty, indicates how much this should affect future gesture + * classifications if this long tap looks like a false. + * As long taps are hard to confirm as false or otherwise, + * a low penalty value is encouraged unless context indicates otherwise. + */ + boolean isFalseLongTap(@Penalty int penalty); + + /** * Returns true if the last two gestures do not look like a double tap. * * Only works on data that has already been reported to the FalsingManager. Be sure that diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml index 006b260434bc..9add32c6ee0a 100644 --- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml +++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml @@ -18,6 +18,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/dream_overlay_status_bar" + android:visibility="invisible" android:layout_width="match_parent" android:layout_height="@dimen/dream_overlay_status_bar_height" android:paddingEnd="@dimen/dream_overlay_status_bar_margin" diff --git a/packages/SystemUI/res/values-sw600dp-h900dp/dimens.xml b/packages/SystemUI/res/values-sw600dp-h900dp/dimens.xml new file mode 100644 index 000000000000..aab914f19878 --- /dev/null +++ b/packages/SystemUI/res/values-sw600dp-h900dp/dimens.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<!-- Intended for wide devices that are currently oriented with a lot of available height, + such as tablets. 'hxxxdp' is used instead of 'port' in order to avoid this being applied + to wide devices that are shorter in height, like foldables. --> +<resources> + <!-- Space between status view and notification shelf --> + <dimen name="keyguard_status_view_bottom_margin">35dp</dimen> + <dimen name="keyguard_clock_top_margin">40dp</dimen> +</resources> diff --git a/packages/SystemUI/res/values-sw600dp-port/dimens.xml b/packages/SystemUI/res/values-sw600dp-port/dimens.xml index 347cf2965c77..d9df3373bef1 100644 --- a/packages/SystemUI/res/values-sw600dp-port/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml @@ -17,8 +17,6 @@ <resources> <dimen name="notification_panel_margin_horizontal">48dp</dimen> <dimen name="status_view_margin_horizontal">62dp</dimen> - <dimen name="keyguard_clock_top_margin">40dp</dimen> - <dimen name="keyguard_status_view_bottom_margin">40dp</dimen> <dimen name="bouncer_user_switcher_y_trans">20dp</dimen> <!-- qs_tiles_page_horizontal_margin should be margin / 2, otherwise full space between two diff --git a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml new file mode 100644 index 000000000000..97ead01669a9 --- /dev/null +++ b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<!-- Intended for wide devices that are currently oriented with a lot of available height, + such as tablets. 'hxxxdp' is used instead of 'port' in order to avoid this being applied + to wide devices that are shorter in height, like foldables. --> +<resources> + <!-- Space between status view and notification shelf --> + <dimen name="keyguard_status_view_bottom_margin">70dp</dimen> + <dimen name="keyguard_clock_top_margin">80dp</dimen> +</resources> diff --git a/packages/SystemUI/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/res/values-sw720dp-port/dimens.xml index 3d8da8a6c3e8..17f82b50d7be 100644 --- a/packages/SystemUI/res/values-sw720dp-port/dimens.xml +++ b/packages/SystemUI/res/values-sw720dp-port/dimens.xml @@ -21,8 +21,6 @@ for different hardware and product builds. --> <resources> <dimen name="status_view_margin_horizontal">124dp</dimen> - <dimen name="keyguard_clock_top_margin">80dp</dimen> - <dimen name="keyguard_status_view_bottom_margin">80dp</dimen> <dimen name="bouncer_user_switcher_y_trans">200dp</dimen> <dimen name="large_screen_shade_header_left_padding">24dp</dimen> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 93982cb2c5b9..ce9829b318cd 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -743,6 +743,17 @@ <integer name="complicationRestoreMs">1000</integer> + <!-- Duration in milliseconds of the dream in un-blur animation. --> + <integer name="config_dreamOverlayInBlurDurationMs">249</integer> + <!-- Delay in milliseconds of the dream in un-blur animation. --> + <integer name="config_dreamOverlayInBlurDelayMs">133</integer> + <!-- Duration in milliseconds of the dream in complications fade-in animation. --> + <integer name="config_dreamOverlayInComplicationsDurationMs">282</integer> + <!-- Delay in milliseconds of the dream in top complications fade-in animation. --> + <integer name="config_dreamOverlayInTopComplicationsDelayMs">216</integer> + <!-- Delay in milliseconds of the dream in bottom complications fade-in animation. --> + <integer name="config_dreamOverlayInBottomComplicationsDelayMs">299</integer> + <!-- Icons that don't show in a collapsed non-keyguard statusbar --> <string-array name="config_collapsed_statusbar_icon_blocklist" translatable="false"> <item>@*android:string/status_bar_volume</item> @@ -783,4 +794,8 @@ <item>@color/dream_overlay_aqi_very_unhealthy</item> <item>@color/dream_overlay_aqi_hazardous</item> </integer-array> + + <!-- Whether the device should display hotspot UI. If true, UI will display only when tethering + is available. If false, UI will never show regardless of tethering availability" --> + <bool name="config_show_wifi_tethering">true</bool> </resources> diff --git a/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java b/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java index 8aa3abac831f..a14f97128662 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java +++ b/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java @@ -291,8 +291,10 @@ public class KeyButtonRipple extends Drawable { } private void endAnimations(String reason, boolean cancel) { - Trace.beginSection("KeyButtonRipple.endAnim: reason=" + reason + " cancel=" + cancel); - Trace.endSection(); + if (Trace.isEnabled()) { + Trace.instant(Trace.TRACE_TAG_APP, + "KeyButtonRipple.endAnim: reason=" + reason + " cancel=" + cancel); + } mVisible = false; mTmpArray.addAll(mRunningAnimations); int size = mTmpArray.size(); @@ -502,20 +504,23 @@ public class KeyButtonRipple extends Drawable { @Override public void onAnimationStart(Animator animation) { - Trace.beginSection("KeyButtonRipple.start." + mName); - Trace.endSection(); + if (Trace.isEnabled()) { + Trace.instant(Trace.TRACE_TAG_APP, "KeyButtonRipple.start." + mName); + } } @Override public void onAnimationCancel(Animator animation) { - Trace.beginSection("KeyButtonRipple.cancel." + mName); - Trace.endSection(); + if (Trace.isEnabled()) { + Trace.instant(Trace.TRACE_TAG_APP, "KeyButtonRipple.cancel." + mName); + } } @Override public void onAnimationEnd(Animator animation) { - Trace.beginSection("KeyButtonRipple.end." + mName); - Trace.endSection(); + if (Trace.isEnabled()) { + Trace.instant(Trace.TRACE_TAG_APP, "KeyButtonRipple.end." + mName); + } } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt index 3961438ff591..ca780c8dd3c9 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt @@ -188,13 +188,10 @@ class DefaultClockController( dozeFraction: Float, foldFraction: Float, ) : ClockAnimations { - private var foldState = AnimationState(0f) - private var dozeState = AnimationState(0f) + private val dozeState = AnimationState(dozeFraction) + private val foldState = AnimationState(foldFraction) init { - dozeState = AnimationState(dozeFraction) - foldState = AnimationState(foldFraction) - if (foldState.isActive) { clocks.forEach { it.animateFoldAppear(false) } } else { @@ -235,7 +232,7 @@ class DefaultClockController( private class AnimationState( var fraction: Float, ) { - var isActive: Boolean = fraction < 0.5f + var isActive: Boolean = fraction > 0.5f fun update(newFraction: Float): Pair<Boolean, Boolean> { if (newFraction == fraction) { return Pair(isActive, false) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt index f79ca1039865..2dc7a280e423 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt @@ -12,17 +12,16 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * */ -package com.android.wm.shell.floating; - -import android.content.Intent; +package com.android.systemui.shared.keyguard.shared.model /** - * Interface that is exposed to remote callers to manipulate floating task features. + * Collection of all supported "slots", placements where keyguard quick affordances can appear on + * the lock screen. */ -interface IFloatingTasks { - - void showTask(in Intent intent) = 1; - +object KeyguardQuickAffordanceSlots { + const val SLOT_ID_BOTTOM_START = "bottom_start" + const val SLOT_ID_BOTTOM_END = "bottom_end" } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index bfbe88c475ac..abefeba9c417 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -60,11 +60,6 @@ oneway interface IOverviewProxy { void onSystemUiStateChanged(int stateFlags) = 16; /** - * Sent when the split screen is resized - */ - void onSplitScreenSecondaryBoundsChanged(in Rect bounds, in Rect insets) = 17; - - /** * Sent when suggested rotation button could be shown */ void onRotationProposal(int rotation, boolean isValid) = 18; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index b99b72bb4275..1c532fe7a529 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -24,7 +24,6 @@ import android.os.UserHandle; import android.view.MotionEvent; import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.shared.system.RemoteTransitionCompat; /** * Temporary callbacks into SystemUI. diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java deleted file mode 100644 index 37e706a9a4c9..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.shared.system; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.view.WindowManager.TRANSIT_CLOSE; -import static android.view.WindowManager.TRANSIT_OLD_NONE; -import static android.view.WindowManager.TRANSIT_OPEN; -import static android.view.WindowManager.TRANSIT_TO_BACK; -import static android.view.WindowManager.TRANSIT_TO_FRONT; -import static android.view.WindowManager.TransitionOldType; -import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; - -import android.annotation.SuppressLint; -import android.app.IApplicationThread; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.ArrayMap; -import android.util.Log; -import android.view.IRemoteAnimationFinishedCallback; -import android.view.IRemoteAnimationRunner; -import android.view.RemoteAnimationAdapter; -import android.view.RemoteAnimationTarget; -import android.view.SurfaceControl; -import android.window.IRemoteTransition; -import android.window.IRemoteTransitionFinishedCallback; -import android.window.RemoteTransition; -import android.window.TransitionInfo; - -import com.android.wm.shell.util.CounterRotator; - -/** - * @see RemoteAnimationAdapter - */ -public class RemoteAnimationAdapterCompat { - - private final RemoteAnimationAdapter mWrapped; - private final RemoteTransitionCompat mRemoteTransition; - - public RemoteAnimationAdapterCompat(RemoteAnimationRunnerCompat runner, long duration, - long statusBarTransitionDelay, IApplicationThread appThread) { - mWrapped = new RemoteAnimationAdapter(wrapRemoteAnimationRunner(runner), duration, - statusBarTransitionDelay); - mRemoteTransition = buildRemoteTransition(runner, appThread); - } - - public RemoteAnimationAdapter getWrapped() { - return mWrapped; - } - - /** Helper to just build a remote transition. Use this if the legacy adapter isn't needed. */ - public static RemoteTransitionCompat buildRemoteTransition(RemoteAnimationRunnerCompat runner, - IApplicationThread appThread) { - return new RemoteTransitionCompat( - new RemoteTransition(wrapRemoteTransition(runner), appThread)); - } - - public RemoteTransitionCompat getRemoteTransition() { - return mRemoteTransition; - } - - /** Wraps a RemoteAnimationRunnerCompat in an IRemoteAnimationRunner. */ - public static IRemoteAnimationRunner.Stub wrapRemoteAnimationRunner( - final RemoteAnimationRunnerCompat remoteAnimationAdapter) { - return new IRemoteAnimationRunner.Stub() { - @Override - public void onAnimationStart(@TransitionOldType int transit, - RemoteAnimationTarget[] apps, - RemoteAnimationTarget[] wallpapers, - RemoteAnimationTarget[] nonApps, - final IRemoteAnimationFinishedCallback finishedCallback) { - final Runnable animationFinishedCallback = new Runnable() { - @Override - public void run() { - try { - finishedCallback.onAnimationFinished(); - } catch (RemoteException e) { - Log.e("ActivityOptionsCompat", "Failed to call app controlled animation" - + " finished callback", e); - } - } - }; - remoteAnimationAdapter.onAnimationStart(transit, apps, wallpapers, - nonApps, animationFinishedCallback); - } - - @Override - public void onAnimationCancelled(boolean isKeyguardOccluded) { - remoteAnimationAdapter.onAnimationCancelled(); - } - }; - } - - private static IRemoteTransition.Stub wrapRemoteTransition( - final RemoteAnimationRunnerCompat remoteAnimationAdapter) { - return new IRemoteTransition.Stub() { - final ArrayMap<IBinder, Runnable> mFinishRunnables = new ArrayMap<>(); - - @Override - public void startAnimation(IBinder token, TransitionInfo info, - SurfaceControl.Transaction t, - IRemoteTransitionFinishedCallback finishCallback) { - final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>(); - final RemoteAnimationTarget[] apps = - RemoteAnimationTargetCompat.wrapApps(info, t, leashMap); - final RemoteAnimationTarget[] wallpapers = - RemoteAnimationTargetCompat.wrapNonApps( - info, true /* wallpapers */, t, leashMap); - final RemoteAnimationTarget[] nonApps = - RemoteAnimationTargetCompat.wrapNonApps( - info, false /* wallpapers */, t, leashMap); - - // TODO(b/177438007): Move this set-up logic into launcher's animation impl. - boolean isReturnToHome = false; - TransitionInfo.Change launcherTask = null; - TransitionInfo.Change wallpaper = null; - int launcherLayer = 0; - int rotateDelta = 0; - float displayW = 0; - float displayH = 0; - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - // skip changes that we didn't wrap - if (!leashMap.containsKey(change.getLeash())) continue; - if (change.getTaskInfo() != null - && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) { - isReturnToHome = change.getMode() == TRANSIT_OPEN - || change.getMode() == TRANSIT_TO_FRONT; - launcherTask = change; - launcherLayer = info.getChanges().size() - i; - } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) { - wallpaper = change; - } - if (change.getParent() == null && change.getEndRotation() >= 0 - && change.getEndRotation() != change.getStartRotation()) { - rotateDelta = change.getEndRotation() - change.getStartRotation(); - displayW = change.getEndAbsBounds().width(); - displayH = change.getEndAbsBounds().height(); - } - } - - // Prepare for rotation if there is one - final CounterRotator counterLauncher = new CounterRotator(); - final CounterRotator counterWallpaper = new CounterRotator(); - if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) { - counterLauncher.setup(t, info.getChange(launcherTask.getParent()).getLeash(), - rotateDelta, displayW, displayH); - if (counterLauncher.getSurface() != null) { - t.setLayer(counterLauncher.getSurface(), launcherLayer); - } - } - - if (isReturnToHome) { - if (counterLauncher.getSurface() != null) { - t.setLayer(counterLauncher.getSurface(), info.getChanges().size() * 3); - } - // Need to "boost" the closing things since that's what launcher expects. - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - final SurfaceControl leash = leashMap.get(change.getLeash()); - // skip changes that we didn't wrap - if (leash == null) continue; - final int mode = info.getChanges().get(i).getMode(); - // Only deal with independent layers - if (!TransitionInfo.isIndependent(change, info)) continue; - if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { - t.setLayer(leash, info.getChanges().size() * 3 - i); - counterLauncher.addChild(t, leash); - } - } - // Make wallpaper visible immediately since launcher apparently won't do this. - for (int i = wallpapers.length - 1; i >= 0; --i) { - t.show(wallpapers[i].leash); - t.setAlpha(wallpapers[i].leash, 1.f); - } - } else { - if (launcherTask != null) { - counterLauncher.addChild(t, leashMap.get(launcherTask.getLeash())); - } - if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) { - counterWallpaper.setup(t, info.getChange(wallpaper.getParent()).getLeash(), - rotateDelta, displayW, displayH); - if (counterWallpaper.getSurface() != null) { - t.setLayer(counterWallpaper.getSurface(), -1); - counterWallpaper.addChild(t, leashMap.get(wallpaper.getLeash())); - } - } - } - t.apply(); - - final Runnable animationFinishedCallback = new Runnable() { - @Override - @SuppressLint("NewApi") - public void run() { - final SurfaceControl.Transaction finishTransaction = - new SurfaceControl.Transaction(); - counterLauncher.cleanUp(finishTransaction); - counterWallpaper.cleanUp(finishTransaction); - // Release surface references now. This is apparently to free GPU memory - // while doing quick operations (eg. during CTS). - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - info.getChanges().get(i).getLeash().release(); - } - // Don't release here since launcher might still be using them. Instead - // let launcher release them (eg. via RemoteAnimationTargets) - leashMap.clear(); - try { - finishCallback.onTransitionFinished(null /* wct */, finishTransaction); - } catch (RemoteException e) { - Log.e("ActivityOptionsCompat", "Failed to call app controlled animation" - + " finished callback", e); - } - } - }; - synchronized (mFinishRunnables) { - mFinishRunnables.put(token, animationFinishedCallback); - } - // TODO(bc-unlcok): Pass correct transit type. - remoteAnimationAdapter.onAnimationStart(TRANSIT_OLD_NONE, - apps, wallpapers, nonApps, () -> { - synchronized (mFinishRunnables) { - if (mFinishRunnables.remove(token) == null) return; - } - animationFinishedCallback.run(); - }); - } - - @Override - public void mergeAnimation(IBinder token, TransitionInfo info, - SurfaceControl.Transaction t, IBinder mergeTarget, - IRemoteTransitionFinishedCallback finishCallback) { - // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt - // to legacy cancel. - final Runnable finishRunnable; - synchronized (mFinishRunnables) { - finishRunnable = mFinishRunnables.remove(mergeTarget); - } - if (finishRunnable == null) return; - remoteAnimationAdapter.onAnimationCancelled(); - finishRunnable.run(); - } - }; - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java deleted file mode 100644 index ab55037159ef..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.shared.system; - -import android.view.RemoteAnimationDefinition; - -/** - * @see RemoteAnimationDefinition - */ -public class RemoteAnimationDefinitionCompat { - - private final RemoteAnimationDefinition mWrapped = new RemoteAnimationDefinition(); - - public void addRemoteAnimation(int transition, RemoteAnimationAdapterCompat adapter) { - mWrapped.addRemoteAnimation(transition, adapter.getWrapped()); - } - - public void addRemoteAnimation(int transition, int activityTypeFilter, - RemoteAnimationAdapterCompat adapter) { - mWrapped.addRemoteAnimation(transition, activityTypeFilter, adapter.getWrapped()); - } - - public RemoteAnimationDefinition getWrapped() { - return mWrapped; - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java index 5809c8124946..93c807352521 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java @@ -16,12 +16,197 @@ package com.android.systemui.shared.system; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_OLD_NONE; +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; + +import android.os.IBinder; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Log; +import android.view.IRemoteAnimationFinishedCallback; +import android.view.IRemoteAnimationRunner; import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; import android.view.WindowManager; +import android.view.WindowManager.TransitionOldType; +import android.window.IRemoteTransition; +import android.window.IRemoteTransitionFinishedCallback; +import android.window.TransitionInfo; + +import com.android.wm.shell.util.CounterRotator; + +public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner.Stub { -public interface RemoteAnimationRunnerCompat { - void onAnimationStart(@WindowManager.TransitionOldType int transit, + public abstract void onAnimationStart(@WindowManager.TransitionOldType int transit, RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, Runnable finishedCallback); - void onAnimationCancelled(); + + @Override + public final void onAnimationStart(@TransitionOldType int transit, + RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, + final IRemoteAnimationFinishedCallback finishedCallback) { + + onAnimationStart(transit, apps, wallpapers, + nonApps, () -> { + try { + finishedCallback.onAnimationFinished(); + } catch (RemoteException e) { + Log.e("ActivityOptionsCompat", "Failed to call app controlled animation" + + " finished callback", e); + } + }); + } + + public IRemoteTransition toRemoteTransition() { + return new IRemoteTransition.Stub() { + final ArrayMap<IBinder, Runnable> mFinishRunnables = new ArrayMap<>(); + + @Override + public void startAnimation(IBinder token, TransitionInfo info, + SurfaceControl.Transaction t, + IRemoteTransitionFinishedCallback finishCallback) { + final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>(); + final RemoteAnimationTarget[] apps = + RemoteAnimationTargetCompat.wrapApps(info, t, leashMap); + final RemoteAnimationTarget[] wallpapers = + RemoteAnimationTargetCompat.wrapNonApps( + info, true /* wallpapers */, t, leashMap); + final RemoteAnimationTarget[] nonApps = + RemoteAnimationTargetCompat.wrapNonApps( + info, false /* wallpapers */, t, leashMap); + + // TODO(b/177438007): Move this set-up logic into launcher's animation impl. + boolean isReturnToHome = false; + TransitionInfo.Change launcherTask = null; + TransitionInfo.Change wallpaper = null; + int launcherLayer = 0; + int rotateDelta = 0; + float displayW = 0; + float displayH = 0; + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + // skip changes that we didn't wrap + if (!leashMap.containsKey(change.getLeash())) continue; + if (change.getTaskInfo() != null + && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) { + isReturnToHome = change.getMode() == TRANSIT_OPEN + || change.getMode() == TRANSIT_TO_FRONT; + launcherTask = change; + launcherLayer = info.getChanges().size() - i; + } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) { + wallpaper = change; + } + if (change.getParent() == null && change.getEndRotation() >= 0 + && change.getEndRotation() != change.getStartRotation()) { + rotateDelta = change.getEndRotation() - change.getStartRotation(); + displayW = change.getEndAbsBounds().width(); + displayH = change.getEndAbsBounds().height(); + } + } + + // Prepare for rotation if there is one + final CounterRotator counterLauncher = new CounterRotator(); + final CounterRotator counterWallpaper = new CounterRotator(); + if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) { + counterLauncher.setup(t, info.getChange(launcherTask.getParent()).getLeash(), + rotateDelta, displayW, displayH); + if (counterLauncher.getSurface() != null) { + t.setLayer(counterLauncher.getSurface(), launcherLayer); + } + } + + if (isReturnToHome) { + if (counterLauncher.getSurface() != null) { + t.setLayer(counterLauncher.getSurface(), info.getChanges().size() * 3); + } + // Need to "boost" the closing things since that's what launcher expects. + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + final SurfaceControl leash = leashMap.get(change.getLeash()); + // skip changes that we didn't wrap + if (leash == null) continue; + final int mode = info.getChanges().get(i).getMode(); + // Only deal with independent layers + if (!TransitionInfo.isIndependent(change, info)) continue; + if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { + t.setLayer(leash, info.getChanges().size() * 3 - i); + counterLauncher.addChild(t, leash); + } + } + // Make wallpaper visible immediately since launcher apparently won't do this. + for (int i = wallpapers.length - 1; i >= 0; --i) { + t.show(wallpapers[i].leash); + t.setAlpha(wallpapers[i].leash, 1.f); + } + } else { + if (launcherTask != null) { + counterLauncher.addChild(t, leashMap.get(launcherTask.getLeash())); + } + if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) { + counterWallpaper.setup(t, info.getChange(wallpaper.getParent()).getLeash(), + rotateDelta, displayW, displayH); + if (counterWallpaper.getSurface() != null) { + t.setLayer(counterWallpaper.getSurface(), -1); + counterWallpaper.addChild(t, leashMap.get(wallpaper.getLeash())); + } + } + } + t.apply(); + + final Runnable animationFinishedCallback = () -> { + final SurfaceControl.Transaction finishTransaction = + new SurfaceControl.Transaction(); + counterLauncher.cleanUp(finishTransaction); + counterWallpaper.cleanUp(finishTransaction); + // Release surface references now. This is apparently to free GPU memory + // while doing quick operations (eg. during CTS). + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + info.getChanges().get(i).getLeash().release(); + } + // Don't release here since launcher might still be using them. Instead + // let launcher release them (eg. via RemoteAnimationTargets) + leashMap.clear(); + try { + finishCallback.onTransitionFinished(null /* wct */, finishTransaction); + } catch (RemoteException e) { + Log.e("ActivityOptionsCompat", "Failed to call app controlled animation" + + " finished callback", e); + } + }; + synchronized (mFinishRunnables) { + mFinishRunnables.put(token, animationFinishedCallback); + } + // TODO(bc-unlcok): Pass correct transit type. + onAnimationStart(TRANSIT_OLD_NONE, + apps, wallpapers, nonApps, () -> { + synchronized (mFinishRunnables) { + if (mFinishRunnables.remove(token) == null) return; + } + animationFinishedCallback.run(); + }); + } + + @Override + public void mergeAnimation(IBinder token, TransitionInfo info, + SurfaceControl.Transaction t, IBinder mergeTarget, + IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { + // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt + // to legacy cancel. + final Runnable finishRunnable; + synchronized (mFinishRunnables) { + finishRunnable = mFinishRunnables.remove(mergeTarget); + } + if (finishRunnable == null) return; + onAnimationCancelled(false /* isKeyguardOccluded */); + finishRunnable.run(); + } + }; + } }
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java index d6655a74219c..d4d3d2579b10 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java @@ -18,29 +18,22 @@ package com.android.systemui.shared.system; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.view.RemoteAnimationTarget.MODE_CLOSING; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; -import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; -import static android.window.TransitionFilter.CONTAINER_ORDER_TOP; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.newTarget; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.IApplicationThread; -import android.content.ComponentName; import android.graphics.Rect; import android.os.IBinder; -import android.os.Parcelable; import android.os.RemoteException; import android.util.ArrayMap; import android.util.Log; @@ -53,72 +46,23 @@ import android.window.IRemoteTransitionFinishedCallback; import android.window.PictureInPictureSurfaceTransaction; import android.window.RemoteTransition; import android.window.TaskSnapshot; -import android.window.TransitionFilter; import android.window.TransitionInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.DataClass; import com.android.systemui.shared.recents.model.ThumbnailData; import java.util.ArrayList; -import java.util.concurrent.Executor; /** - * Wrapper to expose RemoteTransition (shell transitions) to Launcher. - * - * @see IRemoteTransition - * @see TransitionFilter + * Helper class to build {@link RemoteTransition} objects */ -@DataClass -public class RemoteTransitionCompat implements Parcelable { +public class RemoteTransitionCompat { private static final String TAG = "RemoteTransitionCompat"; - @NonNull final RemoteTransition mTransition; - @Nullable TransitionFilter mFilter = null; - - RemoteTransitionCompat(RemoteTransition transition) { - mTransition = transition; - } - - public RemoteTransitionCompat(@NonNull RemoteTransitionRunner runner, - @NonNull Executor executor, @Nullable IApplicationThread appThread) { - IRemoteTransition remote = new IRemoteTransition.Stub() { - @Override - public void startAnimation(IBinder transition, TransitionInfo info, - SurfaceControl.Transaction t, - IRemoteTransitionFinishedCallback finishedCallback) { - final Runnable finishAdapter = () -> { - try { - finishedCallback.onTransitionFinished(null /* wct */, null /* sct */); - } catch (RemoteException e) { - Log.e(TAG, "Failed to call transition finished callback", e); - } - }; - executor.execute(() -> runner.startAnimation(transition, info, t, finishAdapter)); - } - - @Override - public void mergeAnimation(IBinder transition, TransitionInfo info, - SurfaceControl.Transaction t, IBinder mergeTarget, - IRemoteTransitionFinishedCallback finishedCallback) { - final Runnable finishAdapter = () -> { - try { - finishedCallback.onTransitionFinished(null /* wct */, null /* sct */); - } catch (RemoteException e) { - Log.e(TAG, "Failed to call transition finished callback", e); - } - }; - executor.execute(() -> runner.mergeAnimation(transition, info, t, mergeTarget, - finishAdapter)); - } - }; - mTransition = new RemoteTransition(remote, appThread); - } - /** Constructor specifically for recents animation */ - public RemoteTransitionCompat(RecentsAnimationListener recents, + public static RemoteTransition newRemoteTransition(RecentsAnimationListener recents, RecentsAnimationControllerCompat controller, IApplicationThread appThread) { IRemoteTransition remote = new IRemoteTransition.Stub() { final RecentsControllerWrap mRecentsSession = new RecentsControllerWrap(); @@ -193,25 +137,7 @@ public class RemoteTransitionCompat implements Parcelable { mRecentsSession.commitTasksAppearedIfNeeded(recents); } }; - mTransition = new RemoteTransition(remote, appThread); - } - - /** Adds a filter check that restricts this remote transition to home open transitions. */ - public void addHomeOpenCheck(ComponentName homeActivity) { - if (mFilter == null) { - mFilter = new TransitionFilter(); - } - // No need to handle the transition that also dismisses keyguard. - mFilter.mNotFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY; - mFilter.mRequirements = - new TransitionFilter.Requirement[]{new TransitionFilter.Requirement(), - new TransitionFilter.Requirement()}; - mFilter.mRequirements[0].mActivityType = ACTIVITY_TYPE_HOME; - mFilter.mRequirements[0].mTopActivity = homeActivity; - mFilter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT}; - mFilter.mRequirements[0].mOrder = CONTAINER_ORDER_TOP; - mFilter.mRequirements[1].mActivityType = ACTIVITY_TYPE_STANDARD; - mFilter.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK}; + return new RemoteTransition(remote, appThread); } /** @@ -505,161 +431,4 @@ public class RemoteTransitionCompat implements Parcelable { @Override public void animateNavigationBarToApp(long duration) { } } - - - - // Code below generated by codegen v1.0.23. - // - // DO NOT MODIFY! - // CHECKSTYLE:OFF Generated code - // - // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java - // - // To exclude the generated code from IntelliJ auto-formatting enable (one-time): - // Settings > Editor > Code Style > Formatter Control - //@formatter:off - - - @DataClass.Generated.Member - /* package-private */ RemoteTransitionCompat( - @NonNull RemoteTransition transition, - @Nullable TransitionFilter filter) { - this.mTransition = transition; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mTransition); - this.mFilter = filter; - - // onConstructed(); // You can define this method to get a callback - } - - @DataClass.Generated.Member - public @NonNull RemoteTransition getTransition() { - return mTransition; - } - - @DataClass.Generated.Member - public @Nullable TransitionFilter getFilter() { - return mFilter; - } - - @Override - @DataClass.Generated.Member - public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { - // You can override field parcelling by defining methods like: - // void parcelFieldName(Parcel dest, int flags) { ... } - - byte flg = 0; - if (mFilter != null) flg |= 0x2; - dest.writeByte(flg); - dest.writeTypedObject(mTransition, flags); - if (mFilter != null) dest.writeTypedObject(mFilter, flags); - } - - @Override - @DataClass.Generated.Member - public int describeContents() { return 0; } - - /** @hide */ - @SuppressWarnings({"unchecked", "RedundantCast"}) - @DataClass.Generated.Member - protected RemoteTransitionCompat(@NonNull android.os.Parcel in) { - // You can override field unparcelling by defining methods like: - // static FieldType unparcelFieldName(Parcel in) { ... } - - byte flg = in.readByte(); - RemoteTransition transition = (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR); - TransitionFilter filter = (flg & 0x2) == 0 ? null : (TransitionFilter) in.readTypedObject(TransitionFilter.CREATOR); - - this.mTransition = transition; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mTransition); - this.mFilter = filter; - - // onConstructed(); // You can define this method to get a callback - } - - @DataClass.Generated.Member - public static final @NonNull Parcelable.Creator<RemoteTransitionCompat> CREATOR - = new Parcelable.Creator<RemoteTransitionCompat>() { - @Override - public RemoteTransitionCompat[] newArray(int size) { - return new RemoteTransitionCompat[size]; - } - - @Override - public RemoteTransitionCompat createFromParcel(@NonNull android.os.Parcel in) { - return new RemoteTransitionCompat(in); - } - }; - - /** - * A builder for {@link RemoteTransitionCompat} - */ - @SuppressWarnings("WeakerAccess") - @DataClass.Generated.Member - public static class Builder { - - private @NonNull RemoteTransition mTransition; - private @Nullable TransitionFilter mFilter; - - private long mBuilderFieldsSet = 0L; - - public Builder( - @NonNull RemoteTransition transition) { - mTransition = transition; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mTransition); - } - - @DataClass.Generated.Member - public @NonNull Builder setTransition(@NonNull RemoteTransition value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x1; - mTransition = value; - return this; - } - - @DataClass.Generated.Member - public @NonNull Builder setFilter(@NonNull TransitionFilter value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x2; - mFilter = value; - return this; - } - - /** Builds the instance. This builder should not be touched after calling this! */ - public @NonNull RemoteTransitionCompat build() { - checkNotUsed(); - mBuilderFieldsSet |= 0x4; // Mark builder used - - if ((mBuilderFieldsSet & 0x2) == 0) { - mFilter = null; - } - RemoteTransitionCompat o = new RemoteTransitionCompat( - mTransition, - mFilter); - return o; - } - - private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x4) != 0) { - throw new IllegalStateException( - "This Builder should not be reused. Use a new Builder instance instead"); - } - } - } - - @DataClass.Generated( - time = 1629321609807L, - codegenVersion = "1.0.23", - sourceFile = "frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java", - inputSignatures = "private static final java.lang.String TAG\nfinal @android.annotation.NonNull android.window.RemoteTransition mTransition\n @android.annotation.Nullable android.window.TransitionFilter mFilter\npublic void addHomeOpenCheck(android.content.ComponentName)\nclass RemoteTransitionCompat extends java.lang.Object implements [android.os.Parcelable]\nprivate com.android.systemui.shared.system.RecentsAnimationControllerCompat mWrapped\nprivate android.window.IRemoteTransitionFinishedCallback mFinishCB\nprivate android.window.WindowContainerToken mPausingTask\nprivate android.window.WindowContainerToken mPipTask\nprivate android.window.TransitionInfo mInfo\nprivate android.view.SurfaceControl mOpeningLeash\nprivate android.util.ArrayMap<android.view.SurfaceControl,android.view.SurfaceControl> mLeashMap\nprivate android.window.PictureInPictureSurfaceTransaction mPipTransaction\nprivate android.os.IBinder mTransition\n void setup(com.android.systemui.shared.system.RecentsAnimationControllerCompat,android.window.TransitionInfo,android.window.IRemoteTransitionFinishedCallback,android.window.WindowContainerToken,android.window.WindowContainerToken,android.util.ArrayMap<android.view.SurfaceControl,android.view.SurfaceControl>,android.os.IBinder)\n @android.annotation.SuppressLint boolean merge(android.window.TransitionInfo,android.view.SurfaceControl.Transaction,com.android.systemui.shared.system.RecentsAnimationListener)\npublic @java.lang.Override com.android.systemui.shared.recents.model.ThumbnailData screenshotTask(int)\npublic @java.lang.Override void setInputConsumerEnabled(boolean)\npublic @java.lang.Override void setAnimationTargetsBehindSystemBars(boolean)\npublic @java.lang.Override void hideCurrentInputMethod()\npublic @java.lang.Override void setFinishTaskTransaction(int,android.window.PictureInPictureSurfaceTransaction,android.view.SurfaceControl)\npublic @java.lang.Override @android.annotation.SuppressLint void finish(boolean,boolean)\npublic @java.lang.Override void setDeferCancelUntilNextTransition(boolean,boolean)\npublic @java.lang.Override void cleanupScreenshot()\npublic @java.lang.Override void setWillFinishToHome(boolean)\npublic @java.lang.Override boolean removeTask(int)\npublic @java.lang.Override void detachNavigationBarFromApp(boolean)\npublic @java.lang.Override void animateNavigationBarToApp(long)\nclass RecentsControllerWrap extends com.android.systemui.shared.system.RecentsAnimationControllerCompat implements []\n@com.android.internal.util.DataClass") - @Deprecated - private void __metadata() {} - - - //@formatter:on - // End of generated code - } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java deleted file mode 100644 index accc456c4209..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shared.system; - -import android.os.IBinder; -import android.view.SurfaceControl; -import android.window.TransitionInfo; - -/** Interface for something that runs a remote transition animation. */ -public interface RemoteTransitionRunner { - /** - * Starts a transition animation. Once complete, the implementation should call - * `finishCallback`. - */ - void startAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, - Runnable finishCallback); - - /** - * Attempts to merge a transition into the currently-running animation. If merge is not - * possible/supported, this should do nothing. Otherwise, the implementation should call - * `finishCallback` immediately to indicate that it merged the transition. - * - * @param transition The transition that wants to be merged into the running animation. - * @param mergeTarget The transition to merge into (that this runner is currently animating). - */ - default void mergeAnimation(IBinder transition, TransitionInfo info, - SurfaceControl.Transaction t, IBinder mergeTarget, Runnable finishCallback) { } -} diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index d48d7ffcd824..c9b8712bdde9 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -228,7 +228,7 @@ open class ClockEventController @Inject constructor( listenForDozing(this) if (featureFlags.isEnabled(DOZING_MIGRATION_1)) { listenForDozeAmountTransition(this) - listenForGoneToAodTransition(this) + listenForAnyStateToAodTransition(this) } else { listenForDozeAmount(this) } @@ -286,10 +286,10 @@ open class ClockEventController @Inject constructor( * dozing. */ @VisibleForTesting - internal fun listenForGoneToAodTransition(scope: CoroutineScope): Job { + internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job { return scope.launch { - keyguardTransitionInteractor.goneToAodTransition.filter { - it.transitionState == TransitionState.STARTED + keyguardTransitionInteractor.anyStateToAodTransition.filter { + it.transitionState == TransitionState.FINISHED }.collect { dozeAmount = 1f clock?.animations?.doze(dozeAmount) diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt index 71470e8870de..a0206f1f1e70 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt @@ -35,6 +35,7 @@ data class KeyguardFingerprintListenModel( val keyguardOccluded: Boolean, val occludingAppRequestingFp: Boolean, val primaryUser: Boolean, + val shouldListenSfpsState: Boolean, val shouldListenForFingerprintAssistant: Boolean, val switchingUser: Boolean, val udfps: Boolean, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index c756a17976bf..2bb3a5f437f5 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -106,8 +106,9 @@ public class KeyguardSecurityContainer extends ConstraintLayout { static final int USER_TYPE_WORK_PROFILE = 2; static final int USER_TYPE_SECONDARY_USER = 3; - @IntDef({MODE_DEFAULT, MODE_ONE_HANDED, MODE_USER_SWITCHER}) + @IntDef({MODE_UNINITIALIZED, MODE_DEFAULT, MODE_ONE_HANDED, MODE_USER_SWITCHER}) public @interface Mode {} + static final int MODE_UNINITIALIZED = -1; static final int MODE_DEFAULT = 0; static final int MODE_ONE_HANDED = 1; static final int MODE_USER_SWITCHER = 2; @@ -154,7 +155,11 @@ public class KeyguardSecurityContainer extends ConstraintLayout { private boolean mDisappearAnimRunning; private SwipeListener mSwipeListener; private ViewMode mViewMode = new DefaultViewMode(); - private @Mode int mCurrentMode = MODE_DEFAULT; + /* + * Using MODE_UNINITIALIZED to mean the view mode is set to DefaultViewMode, but init() has not + * yet been called on it. This will happen when the ViewController is initialized. + */ + private @Mode int mCurrentMode = MODE_UNINITIALIZED; private int mWidth = -1; private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback = @@ -347,6 +352,8 @@ public class KeyguardSecurityContainer extends ConstraintLayout { private String modeToString(@Mode int mode) { switch (mode) { + case MODE_UNINITIALIZED: + return "Uninitialized"; case MODE_DEFAULT: return "Default"; case MODE_ONE_HANDED: diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 79a01b9c9717..fbb114c72add 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -318,6 +318,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard @Override public void onInit() { mSecurityViewFlipperController.init(); + configureMode(); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index bad75e8dd862..558869c46373 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -151,6 +151,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.Assert; +import com.android.systemui.util.settings.SecureSettings; import com.google.android.collect.Lists; @@ -337,17 +338,20 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>> mCallbacks = Lists.newArrayList(); private ContentObserver mDeviceProvisionedObserver; + private ContentObserver mSfpsRequireScreenOnToAuthPrefObserver; private final ContentObserver mTimeFormatChangeObserver; private boolean mSwitchingUser; private boolean mDeviceInteractive; + private boolean mSfpsRequireScreenOnToAuthPrefEnabled; private final SubscriptionManager mSubscriptionManager; private final TelephonyListenerManager mTelephonyListenerManager; private final TrustManager mTrustManager; private final UserManager mUserManager; private final DevicePolicyManager mDevicePolicyManager; private final BroadcastDispatcher mBroadcastDispatcher; + private final SecureSettings mSecureSettings; private final InteractionJankMonitor mInteractionJankMonitor; private final LatencyTracker mLatencyTracker; private final StatusBarStateController mStatusBarStateController; @@ -396,6 +400,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab protected Handler getHandler() { return mHandler; } + private final Handler mHandler; private final IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback = @@ -720,6 +725,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab /** * Request to listen for face authentication when an app is occluding keyguard. + * * @param request if true and mKeyguardOccluded, request face auth listening, else default * to normal behavior. * See {@link KeyguardUpdateMonitor#shouldListenForFace()} @@ -732,6 +738,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab /** * Request to listen for fingerprint when an app is occluding keyguard. + * * @param request if true and mKeyguardOccluded, request fingerprint listening, else default * to normal behavior. * See {@link KeyguardUpdateMonitor#shouldListenForFingerprint(boolean)} @@ -1946,6 +1953,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Context context, @Main Looper mainLooper, BroadcastDispatcher broadcastDispatcher, + SecureSettings secureSettings, DumpManager dumpManager, @Background Executor backgroundExecutor, @Main Executor mainExecutor, @@ -1988,6 +1996,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mStatusBarState = mStatusBarStateController.getState(); mLockPatternUtils = lockPatternUtils; mAuthController = authController; + mSecureSettings = secureSettings; dumpManager.registerDumpable(getClass().getName(), this); mSensorPrivacyManager = sensorPrivacyManager; mActiveUnlockConfig = activeUnlockConfiguration; @@ -2229,9 +2238,35 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Settings.System.TIME_12_24))); } }; + mContext.getContentResolver().registerContentObserver( Settings.System.getUriFor(Settings.System.TIME_12_24), false, mTimeFormatChangeObserver, UserHandle.USER_ALL); + + updateSfpsRequireScreenOnToAuthPref(); + mSfpsRequireScreenOnToAuthPrefObserver = new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + updateSfpsRequireScreenOnToAuthPref(); + } + }; + + mContext.getContentResolver().registerContentObserver( + mSecureSettings.getUriFor( + Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED), + false, + mSfpsRequireScreenOnToAuthPrefObserver, + getCurrentUser()); + } + + protected void updateSfpsRequireScreenOnToAuthPref() { + final int defaultSfpsRequireScreenOnToAuthValue = + mContext.getResources().getBoolean( + com.android.internal.R.bool.config_requireScreenOnToAuthEnabled) ? 1 : 0; + mSfpsRequireScreenOnToAuthPrefEnabled = mSecureSettings.getIntForUser( + Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, + defaultSfpsRequireScreenOnToAuthValue, + getCurrentUser()) != 0; } private void initializeSimState() { @@ -2276,6 +2311,22 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** + * @return true if there's at least one sfps enrollment for the current user. + */ + public boolean isSfpsEnrolled() { + return mAuthController.isSfpsEnrolled(getCurrentUser()); + } + + /** + * @return true if sfps HW is supported on this device. Can return true even if the user has + * not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered. + */ + public boolean isSfpsSupported() { + return mAuthController.getSfpsProps() != null + && !mAuthController.getSfpsProps().isEmpty(); + } + + /** * @return true if there's at least one face enrolled */ public boolean isFaceEnrolled() { @@ -2598,13 +2649,21 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab !(mFingerprintLockedOut && mBouncerIsOrWillBeShowing && mCredentialAttempted); final boolean isEncryptedOrLockdownForUser = isEncryptedOrLockdown(user); + final boolean shouldListenUdfpsState = !isUdfps || (!userCanSkipBouncer - && !isEncryptedOrLockdownForUser - && userDoesNotHaveTrust); + && !isEncryptedOrLockdownForUser + && userDoesNotHaveTrust); + + boolean shouldListenSideFpsState = true; + if (isSfpsSupported() && isSfpsEnrolled()) { + shouldListenSideFpsState = + mSfpsRequireScreenOnToAuthPrefEnabled ? isDeviceInteractive() : true; + } - final boolean shouldListen = shouldListenKeyguardState && shouldListenUserState - && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut(); + boolean shouldListen = shouldListenKeyguardState && shouldListenUserState + && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut() + && shouldListenSideFpsState; maybeLogListenerModelData( new KeyguardFingerprintListenModel( @@ -2626,6 +2685,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mKeyguardOccluded, mOccludingAppRequestingFp, mIsPrimaryUser, + shouldListenSideFpsState, shouldListenForFingerprintAssistant, mSwitchingUser, isUdfps, @@ -3727,6 +3787,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mContext.getContentResolver().unregisterContentObserver(mTimeFormatChangeObserver); } + if (mSfpsRequireScreenOnToAuthPrefObserver != null) { + mContext.getContentResolver().unregisterContentObserver( + mSfpsRequireScreenOnToAuthPrefObserver); + } + try { ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver); } catch (RemoteException e) { @@ -3799,6 +3864,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab pw.println(" mBouncerIsOrWillBeShowing=" + mBouncerIsOrWillBeShowing); pw.println(" mStatusBarState=" + StatusBarState.toString(mStatusBarState)); pw.println(" mUdfpsBouncerShowing=" + mUdfpsBouncerShowing); + } else if (isSfpsSupported()) { + pw.println(" sfpsEnrolled=" + isSfpsEnrolled()); + pw.println(" shouldListenForSfps=" + shouldListenForFingerprint(false)); + pw.println(" mSfpsRequireScreenOnToAuthPrefEnabled=" + + mSfpsRequireScreenOnToAuthPrefEnabled); } } if (mFaceManager != null && mFaceManager.isHardwareDetected()) { diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index b888d54956cf..83747b4f8636 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -278,7 +278,11 @@ public class SystemUIApplication extends Application implements } private static void notifyBootCompleted(CoreStartable coreStartable) { - Trace.beginSection(coreStartable.getClass().getSimpleName() + ".onBootCompleted()"); + if (Trace.isEnabled()) { + Trace.traceBegin( + Trace.TRACE_TAG_APP, + coreStartable.getClass().getSimpleName() + ".onBootCompleted()"); + } coreStartable.onBootCompleted(); Trace.endSection(); } @@ -300,14 +304,18 @@ public class SystemUIApplication extends Application implements private static CoreStartable startAdditionalStartable(String clsName) { CoreStartable startable; if (DEBUG) Log.d(TAG, "loading: " + clsName); + if (Trace.isEnabled()) { + Trace.traceBegin( + Trace.TRACE_TAG_APP, clsName + ".newInstance()"); + } try { - Trace.beginSection(clsName + ".newInstance()"); startable = (CoreStartable) Class.forName(clsName).newInstance(); - Trace.endSection(); } catch (ClassNotFoundException | IllegalAccessException | InstantiationException ex) { throw new RuntimeException(ex); + } finally { + Trace.endSection(); } return startStartable(startable); @@ -315,7 +323,10 @@ public class SystemUIApplication extends Application implements private static CoreStartable startStartable(String clsName, Provider<CoreStartable> provider) { if (DEBUG) Log.d(TAG, "loading: " + clsName); - Trace.beginSection("Provider<" + clsName + ">.get()"); + if (Trace.isEnabled()) { + Trace.traceBegin( + Trace.TRACE_TAG_APP, "Provider<" + clsName + ">.get()"); + } CoreStartable startable = provider.get(); Trace.endSection(); return startStartable(startable); @@ -323,7 +334,10 @@ public class SystemUIApplication extends Application implements private static CoreStartable startStartable(CoreStartable startable) { if (DEBUG) Log.d(TAG, "running: " + startable); - Trace.beginSection(startable.getClass().getSimpleName() + ".start()"); + if (Trace.isEnabled()) { + Trace.traceBegin( + Trace.TRACE_TAG_APP, startable.getClass().getSimpleName() + ".start()"); + } startable.start(); Trace.endSection(); @@ -364,15 +378,22 @@ public class SystemUIApplication extends Application implements public void onConfigurationChanged(Configuration newConfig) { if (mServicesStarted) { ConfigurationController configController = mSysUIComponent.getConfigurationController(); - Trace.beginSection( - configController.getClass().getSimpleName() + ".onConfigurationChanged()"); + if (Trace.isEnabled()) { + Trace.traceBegin( + Trace.TRACE_TAG_APP, + configController.getClass().getSimpleName() + ".onConfigurationChanged()"); + } configController.onConfigurationChanged(newConfig); Trace.endSection(); int len = mServices.length; for (int i = 0; i < len; i++) { if (mServices[i] != null) { - Trace.beginSection( - mServices[i].getClass().getSimpleName() + ".onConfigurationChanged()"); + if (Trace.isEnabled()) { + Trace.traceBegin( + Trace.TRACE_TAG_APP, + mServices[i].getClass().getSimpleName() + + ".onConfigurationChanged()"); + } mServices[i].onConfigurationChanged(newConfig); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java index a21f45f701b3..632fcdc16259 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java @@ -97,7 +97,6 @@ public abstract class SystemUIInitializer { .setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper()) .setRecentTasks(mWMComponent.getRecentTasks()) .setBackAnimation(mWMComponent.getBackAnimation()) - .setFloatingTasks(mWMComponent.getFloatingTasks()) .setDesktopMode(mWMComponent.getDesktopMode()); // Only initialize when not starting from tests since this currently initializes some @@ -118,7 +117,6 @@ public abstract class SystemUIInitializer { .setStartingSurface(Optional.ofNullable(null)) .setRecentTasks(Optional.ofNullable(null)) .setBackAnimation(Optional.ofNullable(null)) - .setFloatingTasks(Optional.ofNullable(null)) .setDesktopMode(Optional.ofNullable(null)); } mSysUIComponent = builder.build(); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 37da2c7d3379..0a2d8ec97ba6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -762,6 +762,12 @@ public class AuthContainerView extends LinearLayout } mContainerState = STATE_ANIMATING_OUT; + // Request hiding soft-keyboard before animating away credential UI, in case IME insets + // animation get delayed by dismissing animation. + if (isAttachedToWindow() && getRootWindowInsets().isVisible(WindowInsets.Type.ime())) { + getWindowInsetsController().hide(WindowInsets.Type.ime()); + } + if (sendReason) { mPendingCallbackReason = reason; } else { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index c015a21c7db4..ff18eeea45a5 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -78,6 +78,7 @@ import com.android.systemui.doze.DozeReceiver; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.Execution; @@ -150,6 +151,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, @Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps; @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser; + @NonNull private final SparseBooleanArray mSfpsEnrolledForUser; @NonNull private final SensorPrivacyManager mSensorPrivacyManager; private final WakefulnessLifecycle mWakefulnessLifecycle; private boolean mAllFingerprintAuthenticatorsRegistered; @@ -159,6 +161,19 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, private final @Background DelayableExecutor mBackgroundExecutor; private final DisplayInfo mCachedDisplayInfo = new DisplayInfo(); + + private final VibratorHelper mVibratorHelper; + + private void vibrateSuccess(int modality) { + mVibratorHelper.vibrateAuthSuccess( + getClass().getSimpleName() + ", modality = " + modality + "BP::success"); + } + + private void vibrateError(int modality) { + mVibratorHelper.vibrateAuthError( + getClass().getSimpleName() + ", modality = " + modality + "BP::error"); + } + @VisibleForTesting final TaskStackListener mTaskStackListener = new TaskStackListener() { @Override @@ -325,6 +340,16 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, } } } + + if (mSidefpsProps == null) { + Log.d(TAG, "handleEnrollmentsChanged, mSidefpsProps is null"); + } else { + for (FingerprintSensorPropertiesInternal prop : mSidefpsProps) { + if (prop.sensorId == sensorId) { + mSfpsEnrolledForUser.put(userId, hasEnrollments); + } + } + } for (Callback cb : mCallbacks) { cb.onEnrollmentsChanged(); } @@ -660,7 +685,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, @NonNull StatusBarStateController statusBarStateController, @NonNull InteractionJankMonitor jankMonitor, @Main Handler handler, - @Background DelayableExecutor bgExecutor) { + @Background DelayableExecutor bgExecutor, + @NonNull VibratorHelper vibrator) { mContext = context; mExecution = execution; mUserManager = userManager; @@ -677,6 +703,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, mWindowManager = windowManager; mInteractionJankMonitor = jankMonitor; mUdfpsEnrolledForUser = new SparseBooleanArray(); + mSfpsEnrolledForUser = new SparseBooleanArray(); + mVibratorHelper = vibrator; mOrientationListener = new BiometricDisplayListener( context, @@ -866,6 +894,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, public void onBiometricAuthenticated(@Modality int modality) { if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: "); + vibrateSuccess(modality); + if (mCurrentDialog != null) { mCurrentDialog.onAuthenticationSucceeded(modality); } else { @@ -889,6 +919,11 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, return mUdfpsProps; } + @Nullable + public List<FingerprintSensorPropertiesInternal> getSfpsProps() { + return mSidefpsProps; + } + private String getErrorString(@Modality int modality, int error, int vendorCode) { switch (modality) { case TYPE_FACE: @@ -913,6 +948,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, Log.d(TAG, String.format("onBiometricError(%d, %d, %d)", modality, error, vendorCode)); } + vibrateError(modality); + final boolean isLockout = (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT) || (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT); @@ -1013,6 +1050,17 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, return mUdfpsEnrolledForUser.get(userId); } + /** + * Whether the passed userId has enrolled SFPS. + */ + public boolean isSfpsEnrolled(int userId) { + if (mSidefpsController == null) { + return false; + } + + return mSfpsEnrolledForUser.get(userId); + } + private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) { mCurrentDialogArgs = args; diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt index 5850c9537ef0..08c7c0fd0b03 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt @@ -127,7 +127,10 @@ open class UserBroadcastDispatcher( action, userId, { - Trace.beginSection("registerReceiver act=$action user=$userId") + if (Trace.isEnabled()) { + Trace.traceBegin( + Trace.TRACE_TAG_APP, "registerReceiver act=$action user=$userId") + } context.registerReceiverAsUser( this, UserHandle.of(userId), @@ -141,7 +144,11 @@ open class UserBroadcastDispatcher( }, { try { - Trace.beginSection("unregisterReceiver act=$action user=$userId") + if (Trace.isEnabled()) { + Trace.traceBegin( + Trace.TRACE_TAG_APP, + "unregisterReceiver act=$action user=$userId") + } context.unregisterReceiver(this) Trace.endSection() logger.logContextReceiverUnregistered(userId, action) diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java index 2245d8462c31..beaccbaf9a70 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java @@ -34,6 +34,8 @@ import com.android.internal.logging.MetricsLogger; import com.android.systemui.classifier.FalsingDataProvider.SessionListener; import com.android.systemui.classifier.HistoryTracker.BeliefListener; import com.android.systemui.dagger.qualifiers.TestHarness; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -65,6 +67,7 @@ public class BrightLineFalsingManager implements FalsingManager { private static final double FALSE_BELIEF_THRESHOLD = 0.9; private final FalsingDataProvider mDataProvider; + private final LongTapClassifier mLongTapClassifier; private final SingleTapClassifier mSingleTapClassifier; private final DoubleTapClassifier mDoubleTapClassifier; private final HistoryTracker mHistoryTracker; @@ -73,6 +76,7 @@ public class BrightLineFalsingManager implements FalsingManager { private final boolean mTestHarness; private final MetricsLogger mMetricsLogger; private int mIsFalseTouchCalls; + private FeatureFlags mFeatureFlags; private static final Queue<String> RECENT_INFO_LOG = new ArrayDeque<>(RECENT_INFO_LOG_SIZE + 1); private static final Queue<DebugSwipeRecord> RECENT_SWIPES = @@ -175,19 +179,23 @@ public class BrightLineFalsingManager implements FalsingManager { public BrightLineFalsingManager(FalsingDataProvider falsingDataProvider, MetricsLogger metricsLogger, @Named(BRIGHT_LINE_GESTURE_CLASSIFERS) Set<FalsingClassifier> classifiers, - SingleTapClassifier singleTapClassifier, DoubleTapClassifier doubleTapClassifier, - HistoryTracker historyTracker, KeyguardStateController keyguardStateController, + SingleTapClassifier singleTapClassifier, LongTapClassifier longTapClassifier, + DoubleTapClassifier doubleTapClassifier, HistoryTracker historyTracker, + KeyguardStateController keyguardStateController, AccessibilityManager accessibilityManager, - @TestHarness boolean testHarness) { + @TestHarness boolean testHarness, + FeatureFlags featureFlags) { mDataProvider = falsingDataProvider; mMetricsLogger = metricsLogger; mClassifiers = classifiers; mSingleTapClassifier = singleTapClassifier; + mLongTapClassifier = longTapClassifier; mDoubleTapClassifier = doubleTapClassifier; mHistoryTracker = historyTracker; mKeyguardStateController = keyguardStateController; mAccessibilityManager = accessibilityManager; mTestHarness = testHarness; + mFeatureFlags = featureFlags; mDataProvider.addSessionListener(mSessionListener); mDataProvider.addGestureCompleteListener(mGestureFinalizedListener); @@ -313,6 +321,58 @@ public class BrightLineFalsingManager implements FalsingManager { } @Override + public boolean isFalseLongTap(@Penalty int penalty) { + if (!mFeatureFlags.isEnabled(Flags.FALSING_FOR_LONG_TAPS)) { + return false; + } + + checkDestroyed(); + + if (skipFalsing(GENERIC)) { + mPriorResults = getPassedResult(1); + logDebug("Skipped falsing"); + return false; + } + + double falsePenalty = 0; + switch(penalty) { + case NO_PENALTY: + falsePenalty = 0; + break; + case LOW_PENALTY: + falsePenalty = 0.1; + break; + case MODERATE_PENALTY: + falsePenalty = 0.3; + break; + case HIGH_PENALTY: + falsePenalty = 0.6; + break; + } + + FalsingClassifier.Result longTapResult = + mLongTapClassifier.isTap(mDataProvider.getRecentMotionEvents().isEmpty() + ? mDataProvider.getPriorMotionEvents() + : mDataProvider.getRecentMotionEvents(), falsePenalty); + mPriorResults = Collections.singleton(longTapResult); + + if (!longTapResult.isFalse()) { + if (mDataProvider.isJustUnlockedWithFace()) { + // Immediately pass if a face is detected. + mPriorResults = getPassedResult(1); + logDebug("False Long Tap: false (face detected)"); + } else { + mPriorResults = getPassedResult(0.1); + logDebug("False Long Tap: false (default)"); + } + return false; + } else { + logDebug("False Long Tap: " + longTapResult.isFalse() + " (simple)"); + return longTapResult.isFalse(); + } + } + + @Override public boolean isFalseDoubleTap() { checkDestroyed(); diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java index 5d04b5f77479..c4723e895ee7 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java @@ -139,6 +139,11 @@ public class FalsingManagerProxy implements FalsingManager, Dumpable { } @Override + public boolean isFalseLongTap(int penalty) { + return mInternalFalsingManager.isFalseLongTap(penalty); + } + + @Override public boolean isFalseDoubleTap() { return mInternalFalsingManager.isFalseDoubleTap(); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java index 7b7f17e1568b..5302af9db836 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java @@ -40,6 +40,7 @@ import dagger.multibindings.ElementsIntoSet; public interface FalsingModule { String BRIGHT_LINE_GESTURE_CLASSIFERS = "bright_line_gesture_classifiers"; String SINGLE_TAP_TOUCH_SLOP = "falsing_single_tap_touch_slop"; + String LONG_TAP_TOUCH_SLOP = "falsing_long_tap_slop"; String DOUBLE_TAP_TOUCH_SLOP = "falsing_double_tap_touch_slop"; String DOUBLE_TAP_TIMEOUT_MS = "falsing_double_tap_timeout_ms"; @@ -81,4 +82,11 @@ public interface FalsingModule { static float providesSingleTapTouchSlop(ViewConfiguration viewConfiguration) { return viewConfiguration.getScaledTouchSlop(); } + + /** */ + @Provides + @Named(LONG_TAP_TOUCH_SLOP) + static float providesLongTapTouchSlop(ViewConfiguration viewConfiguration) { + return viewConfiguration.getScaledTouchSlop() * 1.25f; + } } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/LongTapClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/LongTapClassifier.java new file mode 100644 index 000000000000..1963e69c1547 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/classifier/LongTapClassifier.java @@ -0,0 +1,34 @@ +/* + * 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.classifier; + +import static com.android.systemui.classifier.FalsingModule.LONG_TAP_TOUCH_SLOP; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * Falsing classifier that accepts or rejects a gesture as a long tap. + */ +public class LongTapClassifier extends TapClassifier{ + + @Inject + LongTapClassifier(FalsingDataProvider dataProvider, + @Named(LONG_TAP_TOUCH_SLOP) float touchSlop) { + super(dataProvider, touchSlop); + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java index bd6fbfbd282e..7a7401dfdd22 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java @@ -18,57 +18,17 @@ package com.android.systemui.classifier; import static com.android.systemui.classifier.FalsingModule.SINGLE_TAP_TOUCH_SLOP; -import android.view.MotionEvent; - -import java.util.List; - import javax.inject.Inject; import javax.inject.Named; /** - * Falsing classifier that accepts or rejects a single gesture as a tap. + * Falsing classifier that accepts or rejects a gesture as a single tap. */ -public class SingleTapClassifier extends FalsingClassifier { - private final float mTouchSlop; +public class SingleTapClassifier extends TapClassifier { @Inject SingleTapClassifier(FalsingDataProvider dataProvider, @Named(SINGLE_TAP_TOUCH_SLOP) float touchSlop) { - super(dataProvider); - mTouchSlop = touchSlop; - } - - @Override - Result calculateFalsingResult( - @Classifier.InteractionType int interactionType, - double historyBelief, double historyConfidence) { - return isTap(getRecentMotionEvents(), 0.5); - } - - /** Given a list of {@link android.view.MotionEvent}'s, returns true if the look like a tap. */ - public Result isTap(List<MotionEvent> motionEvents, double falsePenalty) { - if (motionEvents.isEmpty()) { - return falsed(0, "no motion events"); - } - float downX = motionEvents.get(0).getX(); - float downY = motionEvents.get(0).getY(); - - for (MotionEvent event : motionEvents) { - String reason; - if (Math.abs(event.getX() - downX) >= mTouchSlop) { - reason = "dX too big for a tap: " - + Math.abs(event.getX() - downX) - + "vs " - + mTouchSlop; - return falsed(falsePenalty, reason); - } else if (Math.abs(event.getY() - downY) >= mTouchSlop) { - reason = "dY too big for a tap: " - + Math.abs(event.getY() - downY) - + " vs " - + mTouchSlop; - return falsed(falsePenalty, reason); - } - } - return Result.passed(0); + super(dataProvider, touchSlop); } } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TapClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/TapClassifier.java new file mode 100644 index 000000000000..e24cfaa0ff8f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/classifier/TapClassifier.java @@ -0,0 +1,67 @@ +/* + * 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.classifier; + +import android.view.MotionEvent; + +import java.util.List; + +/** + * Falsing classifier that accepts or rejects a gesture as a tap. + */ +public abstract class TapClassifier extends FalsingClassifier{ + private final float mTouchSlop; + + TapClassifier(FalsingDataProvider dataProvider, + float touchSlop) { + super(dataProvider); + mTouchSlop = touchSlop; + } + + @Override + Result calculateFalsingResult( + @Classifier.InteractionType int interactionType, + double historyBelief, double historyConfidence) { + return isTap(getRecentMotionEvents(), 0.5); + } + + /** Given a list of {@link android.view.MotionEvent}'s, returns true if the look like a tap. */ + public Result isTap(List<MotionEvent> motionEvents, double falsePenalty) { + if (motionEvents.isEmpty()) { + return falsed(0, "no motion events"); + } + float downX = motionEvents.get(0).getX(); + float downY = motionEvents.get(0).getY(); + + for (MotionEvent event : motionEvents) { + String reason; + if (Math.abs(event.getX() - downX) >= mTouchSlop) { + reason = "dX too big for a tap: " + + Math.abs(event.getX() - downX) + + "vs " + + mTouchSlop; + return falsed(falsePenalty, reason); + } else if (Math.abs(event.getY() - downY) >= mTouchSlop) { + reason = "dY too big for a tap: " + + Math.abs(event.getY() - downY) + + " vs " + + mTouchSlop; + return falsed(falsePenalty, reason); + } + } + return Result.passed(0); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index d05bd5120872..c260b7df2b45 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -40,7 +40,6 @@ import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; -import com.android.wm.shell.floating.FloatingTasks; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.recents.RecentTasks; @@ -111,9 +110,6 @@ public interface SysUIComponent { Builder setBackAnimation(Optional<BackAnimation> b); @BindsInstance - Builder setFloatingTasks(Optional<FloatingTasks> f); - - @BindsInstance Builder setDesktopMode(Optional<DesktopMode> d); SysUIComponent build(); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 6db562107357..95919c6b2c0d 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -41,12 +41,14 @@ import com.android.systemui.demomode.dagger.DemoModeModule; import com.android.systemui.doze.dagger.DozeComponent; import com.android.systemui.dreams.dagger.DreamModule; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.FlagsModule; import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.data.BouncerViewModule; import com.android.systemui.log.dagger.LogModule; import com.android.systemui.mediaprojection.appselector.MediaProjectionModule; import com.android.systemui.model.SysUiState; +import com.android.systemui.motiontool.MotionToolModule; import com.android.systemui.navigationbar.NavigationBarComponent; import com.android.systemui.notetask.NoteTaskModule; import com.android.systemui.people.PeopleModule; @@ -134,6 +136,7 @@ import dagger.Provides; FooterActionsModule.class, LogModule.class, MediaProjectionModule.class, + MotionToolModule.class, PeopleHubModule.class, PeopleModule.class, PluginModule.class, @@ -240,6 +243,7 @@ public abstract class SystemUIModule { CommonNotifCollection notifCollection, NotifPipeline notifPipeline, SysUiState sysUiState, + FeatureFlags featureFlags, @Main Executor sysuiMainExecutor) { return Optional.ofNullable(BubblesManager.create(context, bubblesOptional, @@ -256,6 +260,7 @@ public abstract class SystemUIModule { notifCollection, notifPipeline, sysUiState, + featureFlags, sysuiMainExecutor)); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index 096f96949382..d756f3a44655 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -32,7 +32,6 @@ import com.android.wm.shell.dagger.WMShellModule; import com.android.wm.shell.dagger.WMSingleton; import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; -import com.android.wm.shell.floating.FloatingTasks; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.recents.RecentTasks; @@ -111,9 +110,6 @@ public interface WMComponent { @WMSingleton Optional<BackAnimation> getBackAnimation(); - @WMSingleton - Optional<FloatingTasks> getFloatingTasks(); - /** * Optional {@link DesktopMode} component for interacting with desktop mode. */ diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java index 60227ee95fc2..937884c79072 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java @@ -171,7 +171,10 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi @Override public void onSensorChanged(SensorEvent event) { - Trace.beginSection("DozeScreenBrightness.onSensorChanged" + event.values[0]); + if (Trace.isEnabled()) { + Trace.traceBegin( + Trace.TRACE_TAG_APP, "DozeScreenBrightness.onSensorChanged" + event.values[0]); + } try { if (mRegistered) { mLastSensorValue = (int) event.values[0]; diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt new file mode 100644 index 000000000000..d8dd6a21d4c1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.AnimatorSet +import android.animation.ValueAnimator +import android.view.View +import androidx.core.animation.doOnEnd +import com.android.systemui.animation.Interpolators +import com.android.systemui.dreams.complication.ComplicationHostViewController +import com.android.systemui.dreams.complication.ComplicationLayoutParams +import com.android.systemui.dreams.dagger.DreamOverlayModule +import com.android.systemui.statusbar.BlurUtils +import java.util.function.Consumer +import javax.inject.Inject +import javax.inject.Named + +/** Controller for dream overlay animations. */ +class DreamOverlayAnimationsController +@Inject +constructor( + private val mBlurUtils: BlurUtils, + private val mComplicationHostViewController: ComplicationHostViewController, + private val mStatusBarViewController: DreamOverlayStatusBarViewController, + private val mOverlayStateController: DreamOverlayStateController, + @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION) + private val mDreamInBlurAnimDuration: Int, + @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DELAY) private val mDreamInBlurAnimDelay: Int, + @Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION) + private val mDreamInComplicationsAnimDuration: Int, + @Named(DreamOverlayModule.DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY) + private val mDreamInTopComplicationsAnimDelay: Int, + @Named(DreamOverlayModule.DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY) + private val mDreamInBottomComplicationsAnimDelay: Int +) { + + var mEntryAnimations: AnimatorSet? = null + + /** Starts the dream content and dream overlay entry animations. */ + fun startEntryAnimations(view: View) { + cancelRunningEntryAnimations() + + mEntryAnimations = AnimatorSet() + mEntryAnimations?.apply { + playTogether( + buildDreamInBlurAnimator(view), + buildDreamInTopComplicationsAnimator(), + buildDreamInBottomComplicationsAnimator() + ) + doOnEnd { mOverlayStateController.setEntryAnimationsFinished(true) } + start() + } + } + + /** Cancels the dream content and dream overlay animations, if they're currently running. */ + fun cancelRunningEntryAnimations() { + if (mEntryAnimations?.isRunning == true) { + mEntryAnimations?.cancel() + } + mEntryAnimations = null + } + + private fun buildDreamInBlurAnimator(view: View): Animator { + return ValueAnimator.ofFloat(1f, 0f).apply { + duration = mDreamInBlurAnimDuration.toLong() + startDelay = mDreamInBlurAnimDelay.toLong() + interpolator = Interpolators.LINEAR + addUpdateListener { animator: ValueAnimator -> + mBlurUtils.applyBlur( + view.viewRootImpl, + mBlurUtils.blurRadiusOfRatio(animator.animatedValue as Float).toInt(), + false /*opaque*/ + ) + } + } + } + + private fun buildDreamInTopComplicationsAnimator(): Animator { + return ValueAnimator.ofFloat(0f, 1f).apply { + duration = mDreamInComplicationsAnimDuration.toLong() + startDelay = mDreamInTopComplicationsAnimDelay.toLong() + interpolator = Interpolators.LINEAR + addUpdateListener { va: ValueAnimator -> + setTopElementsAlpha(va.animatedValue as Float) + } + } + } + + private fun buildDreamInBottomComplicationsAnimator(): Animator { + return ValueAnimator.ofFloat(0f, 1f).apply { + duration = mDreamInComplicationsAnimDuration.toLong() + startDelay = mDreamInBottomComplicationsAnimDelay.toLong() + interpolator = Interpolators.LINEAR + addUpdateListener { va: ValueAnimator -> + setBottomElementsAlpha(va.animatedValue as Float) + } + addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator) { + mComplicationHostViewController + .getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM) + .forEach(Consumer { v: View -> v.visibility = View.VISIBLE }) + } + } + ) + } + } + + /** Sets alpha of top complications and the status bar. */ + private fun setTopElementsAlpha(alpha: Float) { + mComplicationHostViewController + .getViewsAtPosition(ComplicationLayoutParams.POSITION_TOP) + .forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) }) + mStatusBarViewController.setAlpha(alpha) + } + + /** Sets alpha of bottom complications. */ + private fun setBottomElementsAlpha(alpha: Float) { + mComplicationHostViewController + .getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM) + .forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) }) + } + + private fun setAlphaAndEnsureVisible(view: View, alpha: Float) { + if (alpha > 0 && view.visibility != View.VISIBLE) { + view.visibility = View.VISIBLE + } + + view.alpha = alpha + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java index 733a80dd7f69..d0d01846c3d9 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java @@ -54,6 +54,8 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve private final DreamOverlayStatusBarViewController mStatusBarViewController; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private final BlurUtils mBlurUtils; + private final DreamOverlayAnimationsController mDreamOverlayAnimationsController; + private final DreamOverlayStateController mStateController; private final ComplicationHostViewController mComplicationHostViewController; @@ -134,12 +136,16 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve @Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long burnInProtectionUpdateInterval, @Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter, - BouncerCallbackInteractor bouncerCallbackInteractor) { + BouncerCallbackInteractor bouncerCallbackInteractor, + DreamOverlayAnimationsController animationsController, + DreamOverlayStateController stateController) { super(containerView); mDreamOverlayContentView = contentView; mStatusBarViewController = statusBarViewController; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mBlurUtils = blurUtils; + mDreamOverlayAnimationsController = animationsController; + mStateController = stateController; mComplicationHostViewController = complicationHostViewController; mDreamOverlayMaxTranslationY = resources.getDimensionPixelSize( @@ -172,6 +178,11 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve bouncer.addBouncerExpansionCallback(mBouncerExpansionCallback); } mBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback); + + // Start dream entry animations. Skip animations for low light clock. + if (!mStateController.isLowLightActive()) { + mDreamOverlayAnimationsController.startEntryAnimations(mView); + } } @Override @@ -182,6 +193,8 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve bouncer.removeBouncerExpansionCallback(mBouncerExpansionCallback); } mBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback); + + mDreamOverlayAnimationsController.cancelRunningEntryAnimations(); } View getContainerView() { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index d1b73685a3f3..8542412f82f8 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -90,13 +90,15 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ new KeyguardUpdateMonitorCallback() { @Override public void onShadeExpandedChanged(boolean expanded) { - if (mLifecycleRegistry.getCurrentState() != Lifecycle.State.RESUMED - && mLifecycleRegistry.getCurrentState() != Lifecycle.State.STARTED) { - return; - } - - mLifecycleRegistry.setCurrentState( - expanded ? Lifecycle.State.STARTED : Lifecycle.State.RESUMED); + mExecutor.execute(() -> { + if (getCurrentStateLocked() != Lifecycle.State.RESUMED + && getCurrentStateLocked() != Lifecycle.State.STARTED) { + return; + } + + setCurrentStateLocked( + expanded ? Lifecycle.State.STARTED : Lifecycle.State.RESUMED); + }); } }; @@ -146,29 +148,30 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ () -> mExecutor.execute(DreamOverlayService.this::requestExit); mDreamOverlayComponent = dreamOverlayComponentFactory.create(viewModelStore, host); mLifecycleRegistry = mDreamOverlayComponent.getLifecycleRegistry(); - setCurrentState(Lifecycle.State.CREATED); - } - private void setCurrentState(Lifecycle.State state) { - mExecutor.execute(() -> mLifecycleRegistry.setCurrentState(state)); + mExecutor.execute(() -> setCurrentStateLocked(Lifecycle.State.CREATED)); } @Override public void onDestroy() { mKeyguardUpdateMonitor.removeCallback(mKeyguardCallback); - setCurrentState(Lifecycle.State.DESTROYED); - resetCurrentDreamOverlay(); + mExecutor.execute(() -> { + setCurrentStateLocked(Lifecycle.State.DESTROYED); + + resetCurrentDreamOverlayLocked(); + + mDestroyed = true; + }); - mDestroyed = true; super.onDestroy(); } @Override public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) { - setCurrentState(Lifecycle.State.STARTED); - mExecutor.execute(() -> { + setCurrentStateLocked(Lifecycle.State.STARTED); + mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START); if (mDestroyed) { @@ -181,7 +184,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ // Reset the current dream overlay before starting a new one. This can happen // when two dreams overlap (briefly, for a smoother dream transition) and both // dreams are bound to the dream overlay service. - resetCurrentDreamOverlay(); + resetCurrentDreamOverlayLocked(); } mDreamOverlayContainerViewController = @@ -191,7 +194,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ mStateController.setShouldShowComplications(shouldShowComplications()); addOverlayWindowLocked(layoutParams); - setCurrentState(Lifecycle.State.RESUMED); + setCurrentStateLocked(Lifecycle.State.RESUMED); mStateController.setOverlayActive(true); final ComponentName dreamComponent = getDreamComponent(); mStateController.setLowLightActive( @@ -202,6 +205,14 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ }); } + private Lifecycle.State getCurrentStateLocked() { + return mLifecycleRegistry.getCurrentState(); + } + + private void setCurrentStateLocked(Lifecycle.State state) { + mLifecycleRegistry.setCurrentState(state); + } + /** * Inserts {@link Window} to host the dream overlay into the dream's parent window. Must be * called from the main executing thread. The window attributes closely mirror those that are @@ -231,13 +242,13 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ // Make extra sure the container view has been removed from its old parent (otherwise we // risk an IllegalStateException in some cases when setting the container view as the // window's content view and the container view hasn't been properly removed previously). - removeContainerViewFromParent(); + removeContainerViewFromParentLocked(); mWindow.setContentView(mDreamOverlayContainerViewController.getContainerView()); mWindowManager.addView(mWindow.getDecorView(), mWindow.getAttributes()); } - private void removeContainerViewFromParent() { + private void removeContainerViewFromParentLocked() { View containerView = mDreamOverlayContainerViewController.getContainerView(); if (containerView == null) { return; @@ -250,13 +261,14 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ parentView.removeView(containerView); } - private void resetCurrentDreamOverlay() { + private void resetCurrentDreamOverlayLocked() { if (mStarted && mWindow != null) { mWindowManager.removeView(mWindow.getDecorView()); } mStateController.setOverlayActive(false); mStateController.setLowLightActive(false); + mStateController.setEntryAnimationsFinished(false); mDreamOverlayContainerViewController = null; mDreamOverlayTouchMonitor = null; diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java index 72feaca59ace..e80d0beabf2b 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java @@ -51,6 +51,7 @@ public class DreamOverlayStateController implements public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0; public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1; + public static final int STATE_DREAM_ENTRY_ANIMATIONS_FINISHED = 1 << 2; private static final int OP_CLEAR_STATE = 1; private static final int OP_SET_STATE = 2; @@ -202,6 +203,14 @@ public class DreamOverlayStateController implements return containsState(STATE_LOW_LIGHT_ACTIVE); } + /** + * Returns whether the dream content and dream overlay entry animations are finished. + * @return {@code true} if animations are finished, {@code false} otherwise. + */ + public boolean areEntryAnimationsFinished() { + return containsState(STATE_DREAM_ENTRY_ANIMATIONS_FINISHED); + } + private boolean containsState(int state) { return (mState & state) != 0; } @@ -218,7 +227,7 @@ public class DreamOverlayStateController implements } if (existingState != mState) { - notifyCallbacks(callback -> callback.onStateChanged()); + notifyCallbacks(Callback::onStateChanged); } } @@ -239,6 +248,15 @@ public class DreamOverlayStateController implements } /** + * Sets whether dream content and dream overlay entry animations are finished. + * @param finished {@code true} if entry animations are finished, {@code false} otherwise. + */ + public void setEntryAnimationsFinished(boolean finished) { + modifyState(finished ? OP_SET_STATE : OP_CLEAR_STATE, + STATE_DREAM_ENTRY_ANIMATIONS_FINISHED); + } + + /** * Returns the available complication types. */ @Complication.ComplicationType diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java index bb1c4303041a..d17fbe31c8d2 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java @@ -16,10 +16,6 @@ package com.android.systemui.dreams; -import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; -import static android.app.StatusBarManager.WINDOW_STATE_HIDING; -import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; - import android.app.AlarmManager; import android.app.StatusBarManager; import android.content.res.Resources; @@ -83,6 +79,9 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve private boolean mIsAttached; + // Whether dream entry animations are finished. + private boolean mEntryAnimationsFinished = false; + private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder() .clearCapabilities() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build(); @@ -109,7 +108,9 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve new DreamOverlayStateController.Callback() { @Override public void onStateChanged() { - updateLowLightState(); + mEntryAnimationsFinished = + mDreamOverlayStateController.areEntryAnimationsFinished(); + updateVisibility(); } }; @@ -195,7 +196,6 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve mStatusBarItemsProvider.addCallback(mStatusBarItemsProviderCallback); mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback); - updateLowLightState(); mTouchInsetSession.addViewToTracking(mView); } @@ -216,6 +216,26 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve mIsAttached = false; } + /** + * Sets alpha of the dream overlay status bar. + * + * No-op if the dream overlay status bar should not be shown. + */ + protected void setAlpha(float alpha) { + updateVisibility(); + + if (mView.getVisibility() != View.VISIBLE) { + return; + } + + mView.setAlpha(alpha); + } + + private boolean shouldShowStatusBar() { + return !mDreamOverlayStateController.isLowLightActive() + && !mStatusBarWindowStateController.windowIsShowing(); + } + private void updateWifiUnavailableStatusIcon() { final NetworkCapabilities capabilities = mConnectivityManager.getNetworkCapabilities( @@ -235,13 +255,12 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve hasAlarm ? buildAlarmContentDescription(alarm) : null); } - private void updateLowLightState() { - int visibility = View.VISIBLE; - if (mDreamOverlayStateController.isLowLightActive() - || mStatusBarWindowStateController.windowIsShowing()) { - visibility = View.INVISIBLE; + private void updateVisibility() { + if (shouldShowStatusBar()) { + mView.setVisibility(View.VISIBLE); + } else { + mView.setVisibility(View.INVISIBLE); } - mView.setVisibility(visibility); } private String buildAlarmContentDescription(AlarmManager.AlarmClockInfo alarm) { @@ -298,21 +317,11 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve } private void onSystemStatusBarStateChanged(@StatusBarManager.WindowVisibleState int state) { - mMainExecutor.execute(() -> { - if (!mIsAttached || mDreamOverlayStateController.isLowLightActive()) { - return; - } + if (!mIsAttached || !mEntryAnimationsFinished) { + return; + } - switch (state) { - case WINDOW_STATE_SHOWING: - mView.setVisibility(View.INVISIBLE); - break; - case WINDOW_STATE_HIDING: - case WINDOW_STATE_HIDDEN: - mView.setVisibility(View.VISIBLE); - break; - } - }); + mMainExecutor.execute(this::updateVisibility); } private void onStatusBarItemsChanged(List<StatusBarItem> newItems) { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java index fd6cfc0700ad..100ccc35e638 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java @@ -28,6 +28,7 @@ import android.view.View; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.lifecycle.LifecycleOwner; +import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.util.ViewController; import java.util.Collection; @@ -49,20 +50,34 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final ComplicationLayoutEngine mLayoutEngine; + private final DreamOverlayStateController mDreamOverlayStateController; private final LifecycleOwner mLifecycleOwner; private final ComplicationCollectionViewModel mComplicationCollectionViewModel; private final HashMap<ComplicationId, Complication.ViewHolder> mComplications = new HashMap<>(); + // Whether dream entry animations are finished. + private boolean mEntryAnimationsFinished = false; + @Inject protected ComplicationHostViewController( @Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout view, ComplicationLayoutEngine layoutEngine, + DreamOverlayStateController dreamOverlayStateController, LifecycleOwner lifecycleOwner, @Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel) { super(view); mLayoutEngine = layoutEngine; mLifecycleOwner = lifecycleOwner; mComplicationCollectionViewModel = viewModel; + mDreamOverlayStateController = dreamOverlayStateController; + + mDreamOverlayStateController.addCallback(new DreamOverlayStateController.Callback() { + @Override + public void onStateChanged() { + mEntryAnimationsFinished = + mDreamOverlayStateController.areEntryAnimationsFinished(); + } + }); } @Override @@ -123,6 +138,11 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay final ComplicationId id = complication.getId(); final Complication.ViewHolder viewHolder = complication.getComplication() .createView(complication); + // Complications to be added before dream entry animations are finished are set + // to invisible and are animated in. + if (!mEntryAnimationsFinished) { + viewHolder.getView().setVisibility(View.INVISIBLE); + } mComplications.put(id, viewHolder); if (viewHolder.getView().getParent() != null) { Log.e(TAG, "View for complication " diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java index 4fe1622d73a5..cb012fa42e94 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java @@ -47,6 +47,14 @@ public abstract class DreamOverlayModule { public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL = "burn_in_protection_update_interval"; public static final String MILLIS_UNTIL_FULL_JITTER = "millis_until_full_jitter"; + public static final String DREAM_IN_BLUR_ANIMATION_DURATION = "dream_in_blur_anim_duration"; + public static final String DREAM_IN_BLUR_ANIMATION_DELAY = "dream_in_blur_anim_delay"; + public static final String DREAM_IN_COMPLICATIONS_ANIMATION_DURATION = + "dream_in_complications_anim_duration"; + public static final String DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY = + "dream_in_top_complications_anim_delay"; + public static final String DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY = + "dream_in_bottom_complications_anim_delay"; /** */ @Provides @@ -114,6 +122,51 @@ public abstract class DreamOverlayModule { return resources.getInteger(R.integer.config_dreamOverlayMillisUntilFullJitter); } + /** + * Duration in milliseconds of the dream in un-blur animation. + */ + @Provides + @Named(DREAM_IN_BLUR_ANIMATION_DURATION) + static int providesDreamInBlurAnimationDuration(@Main Resources resources) { + return resources.getInteger(R.integer.config_dreamOverlayInBlurDurationMs); + } + + /** + * Delay in milliseconds of the dream in un-blur animation. + */ + @Provides + @Named(DREAM_IN_BLUR_ANIMATION_DELAY) + static int providesDreamInBlurAnimationDelay(@Main Resources resources) { + return resources.getInteger(R.integer.config_dreamOverlayInBlurDelayMs); + } + + /** + * Duration in milliseconds of the dream in complications fade-in animation. + */ + @Provides + @Named(DREAM_IN_COMPLICATIONS_ANIMATION_DURATION) + static int providesDreamInComplicationsAnimationDuration(@Main Resources resources) { + return resources.getInteger(R.integer.config_dreamOverlayInComplicationsDurationMs); + } + + /** + * Delay in milliseconds of the dream in top complications fade-in animation. + */ + @Provides + @Named(DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY) + static int providesDreamInTopComplicationsAnimationDelay(@Main Resources resources) { + return resources.getInteger(R.integer.config_dreamOverlayInTopComplicationsDelayMs); + } + + /** + * Delay in milliseconds of the dream in bottom complications fade-in animation. + */ + @Provides + @Named(DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY) + static int providesDreamInBottomComplicationsAnimationDelay(@Main Resources resources) { + return resources.getInteger(R.integer.config_dreamOverlayInBottomComplicationsDelayMs); + } + @Provides @DreamOverlayComponent.DreamOverlayScope static LifecycleOwner providesLifecycleOwner(Lazy<LifecycleRegistry> lifecycleRegistryLazy) { diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 844a311a988b..42f2dab1e065 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -57,7 +57,7 @@ object Flags { val INSTANT_VOICE_REPLY = UnreleasedFlag(111, teamfood = true) // TODO(b/254512425): Tracking Bug - val NOTIFICATION_MEMORY_MONITOR_ENABLED = UnreleasedFlag(112, teamfood = true) + val NOTIFICATION_MEMORY_MONITOR_ENABLED = ReleasedFlag(112) // TODO(b/254512731): Tracking Bug @JvmField val NOTIFICATION_DISMISSAL_FADE = UnreleasedFlag(113, teamfood = true) @@ -89,7 +89,7 @@ object Flags { * replacement of KeyguardBouncer.java. */ // TODO(b/254512385): Tracking Bug - @JvmField val MODERN_BOUNCER = UnreleasedFlag(208) + @JvmField val MODERN_BOUNCER = ReleasedFlag(208) /** * Whether the user interactor and repository should use `UserSwitcherController`. @@ -125,6 +125,14 @@ object Flags { // TODO(b/255607168): Tracking Bug @JvmField val DOZING_MIGRATION_1 = UnreleasedFlag(213) + /** + * Whether to enable the code powering customizable lock screen quick affordances. + * + * Note that this flag does not enable individual implementations of quick affordances like the + * new camera quick affordance. Look for individual flags for those. + */ + @JvmField val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES = UnreleasedFlag(214, teamfood = false) + // 300 - power menu // TODO(b/254512600): Tracking Bug @JvmField val POWER_MENU_LITE = ReleasedFlag(300) @@ -171,10 +179,18 @@ object Flags { @Deprecated("Replaced by mobile and wifi specific flags.") val NEW_STATUS_BAR_PIPELINE_FRONTEND = UnreleasedFlag(605, teamfood = false) + // TODO(b/256614753): Tracking Bug val NEW_STATUS_BAR_MOBILE_ICONS = UnreleasedFlag(606) + // TODO(b/256614210): Tracking Bug val NEW_STATUS_BAR_WIFI_ICON = UnreleasedFlag(607) + // TODO(b/256614751): Tracking Bug + val NEW_STATUS_BAR_MOBILE_ICONS_BACKEND = UnreleasedFlag(608) + + // TODO(b/256613548): Tracking Bug + val NEW_STATUS_BAR_WIFI_ICON_BACKEND = UnreleasedFlag(609) + // 700 - dialer/calls // TODO(b/254512734): Tracking Bug val ONGOING_CALL_STATUS_BAR_CHIP = ReleasedFlag(700) @@ -270,15 +286,6 @@ object Flags { @JvmField @Keep - val FLOATING_TASKS_ENABLED = SysPropBooleanFlag(1106, "persist.wm.debug.floating_tasks", false) - - @JvmField - @Keep - val SHOW_FLOATING_TASKS_AS_BUBBLES = - SysPropBooleanFlag(1107, "persist.wm.debug.floating_tasks_as_bubbles", false) - - @JvmField - @Keep val ENABLE_FLING_TO_DISMISS_BUBBLE = SysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", true) @@ -292,6 +299,9 @@ object Flags { val ENABLE_PIP_KEEP_CLEAR_ALGORITHM = SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false) + // TODO(b/256873975): Tracking Bug + @JvmField @Keep val WM_BUBBLE_BAR = UnreleasedFlag(1111) + // 1200 - predictive back @JvmField @Keep @@ -327,7 +337,7 @@ object Flags { val CHOOSER_UNBUNDLED = UnreleasedFlag(1500, teamfood = true) // 1700 - clipboard - @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700) + @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700, true) @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = UnreleasedFlag(1701) // 1800 - shade container @@ -336,6 +346,12 @@ object Flags { // 1900 - note task @JvmField val NOTE_TASKS = SysPropBooleanFlag(1900, "persist.sysui.debug.note_tasks") + // 2000 - device controls + @Keep val USE_APP_PANELS = UnreleasedFlag(2000, true) + + // 2100 - Falsing Manager + @JvmField val FALSING_FOR_LONG_TAPS = ReleasedFlag(2100) + // Pay no attention to the reflection behind the curtain. // ========================== Curtain ========================== // | | diff --git a/packages/SystemUI/src/com/android/systemui/flags/OWNERS b/packages/SystemUI/src/com/android/systemui/flags/OWNERS new file mode 100644 index 000000000000..c9d2db1d8acb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/flags/OWNERS @@ -0,0 +1,12 @@ +set noparent + +# Bug component: 1203176 + +mankoff@google.com # send reviews here + +pixel@google.com +juliacr@google.com +cinek@google.com +alexflo@google.com +dsandler@android.com +adamcohen@google.com diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java index 546a4093ec7d..450fa1420408 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java @@ -33,6 +33,8 @@ import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; import android.widget.ImageView; +import android.window.OnBackInvokedCallback; +import android.window.OnBackInvokedDispatcher; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; @@ -61,6 +63,7 @@ public class WorkLockActivity extends Activity { private UserManager mUserManager; private PackageManager mPackageManager; private final BroadcastDispatcher mBroadcastDispatcher; + private final OnBackInvokedCallback mBackCallback = this::onBackInvoked; @Inject public WorkLockActivity(BroadcastDispatcher broadcastDispatcher, UserManager userManager, @@ -95,6 +98,10 @@ public class WorkLockActivity extends Activity { if (badgedIcon != null) { ((ImageView) findViewById(R.id.icon)).setImageDrawable(badgedIcon); } + + getOnBackInvokedDispatcher().registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, + mBackCallback); } @VisibleForTesting @@ -134,11 +141,16 @@ public class WorkLockActivity extends Activity { @Override public void onDestroy() { unregisterBroadcastReceiver(); + getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mBackCallback); super.onDestroy(); } @Override public void onBackPressed() { + onBackInvoked(); + } + + private void onBackInvoked() { // Ignore back presses. } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 56a1f1ae936e..f08463bfd5e9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -42,6 +42,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; +import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule; import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule; import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule; import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule; @@ -71,6 +72,7 @@ import dagger.Provides; KeyguardUserSwitcherComponent.class}, includes = { FalsingModule.class, + KeyguardDataQuickAffordanceModule.class, KeyguardQuickAffordanceModule.class, KeyguardRepositoryModule.class, StartKeyguardTransitionModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt index c600e13cc2dd..d6f521c6dc87 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt @@ -53,6 +53,10 @@ constructor( override val key: String = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS + override val pickerName: String by lazy { context.getString(component.getTileTitleId()) } + + override val pickerIconResourceId: Int by lazy { component.getTileImageId() } + override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> = component.canShowWhileLockedSetting.flatMapLatest { canShowWhileLocked -> if (canShowWhileLocked) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt new file mode 100644 index 000000000000..bea9363efc81 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.data.quickaffordance + +import dagger.Module +import dagger.Provides +import dagger.multibindings.ElementsIntoSet + +@Module +object KeyguardDataQuickAffordanceModule { + @Provides + @ElementsIntoSet + fun quickAffordanceConfigs( + home: HomeControlsKeyguardQuickAffordanceConfig, + quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig, + qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig, + ): Set<KeyguardQuickAffordanceConfig> { + return setOf( + home, + quickAccessWallet, + qrCodeScanner, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt index 0a8090bb11ac..fd40d1dd1a0e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt @@ -29,6 +29,10 @@ interface KeyguardQuickAffordanceConfig { /** Unique identifier for this quick affordance. It must be globally unique. */ val key: String + val pickerName: String + + val pickerIconResourceId: Int + /** * The ever-changing state of the affordance. * diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt new file mode 100644 index 000000000000..9c9354fec695 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.data.quickaffordance + +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** + * Manages and provides access to the current "selections" of keyguard quick affordances, answering + * the question "which affordances should the keyguard show?". + */ +@SysUISingleton +class KeyguardQuickAffordanceSelectionManager @Inject constructor() { + + // TODO(b/254858695): implement a persistence layer (database). + private val _selections = MutableStateFlow<Map<String, List<String>>>(emptyMap()) + + /** IDs of affordances to show, indexed by slot ID, and sorted in descending priority order. */ + val selections: Flow<Map<String, List<String>>> = _selections.asStateFlow() + + /** + * Returns a snapshot of the IDs of affordances to show, indexed by slot ID, and sorted in + * descending priority order. + */ + suspend fun getSelections(): Map<String, List<String>> { + return _selections.value + } + + /** + * Updates the IDs of affordances to show at the slot with the given ID. The order of affordance + * IDs should be descending priority order. + */ + suspend fun setSelections( + slotId: String, + affordanceIds: List<String>, + ) { + // Must make a copy of the map and update it, otherwise, the MutableStateFlow won't emit + // when we set its value to the same instance of the original map, even if we change the + // map by updating the value of one of its keys. + val copy = _selections.value.toMutableMap() + copy[slotId] = affordanceIds + _selections.value = copy + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt index d620b2a1654c..11f72ffa4757 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.data.quickaffordance +import android.content.Context import com.android.systemui.R import com.android.systemui.animation.Expandable import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging @@ -24,6 +25,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.qrcodescanner.controller.QRCodeScannerController import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose @@ -34,11 +36,16 @@ import kotlinx.coroutines.flow.Flow class QrCodeScannerKeyguardQuickAffordanceConfig @Inject constructor( + @Application context: Context, private val controller: QRCodeScannerController, ) : KeyguardQuickAffordanceConfig { override val key: String = BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER + override val pickerName = context.getString(R.string.qr_code_scanner_title) + + override val pickerIconResourceId = R.drawable.ic_qr_code_scanner + override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> = conflatedCallbackFlow { val callback = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt index be57a323b5d1..303e6a1a95cc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.data.quickaffordance +import android.content.Context import android.graphics.drawable.Drawable import android.service.quickaccesswallet.GetWalletCardsError import android.service.quickaccesswallet.GetWalletCardsResponse @@ -29,6 +30,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.plugins.ActivityStarter import com.android.systemui.wallet.controller.QuickAccessWalletController import javax.inject.Inject @@ -40,12 +42,17 @@ import kotlinx.coroutines.flow.Flow class QuickAccessWalletKeyguardQuickAffordanceConfig @Inject constructor( + @Application context: Context, private val walletController: QuickAccessWalletController, private val activityStarter: ActivityStarter, ) : KeyguardQuickAffordanceConfig { override val key: String = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET + override val pickerName = context.getString(R.string.accessibility_wallet_button) + + override val pickerIconResourceId = R.drawable.ic_wallet_lockscreen + override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> = conflatedCallbackFlow { val callback = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt new file mode 100644 index 000000000000..95f614fbf7b1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.data.repository + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager +import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation +import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation +import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +/** Abstracts access to application state related to keyguard quick affordances. */ +@SysUISingleton +class KeyguardQuickAffordanceRepository +@Inject +constructor( + @Application private val scope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, + private val selectionManager: KeyguardQuickAffordanceSelectionManager, + private val configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>, +) { + /** + * List of [KeyguardQuickAffordanceConfig] instances of the affordances at the slot with the + * given ID. The configs are sorted in descending priority order. + */ + val selections: StateFlow<Map<String, List<KeyguardQuickAffordanceConfig>>> = + selectionManager.selections + .map { selectionsBySlotId -> + selectionsBySlotId.mapValues { (_, selections) -> + configs.filter { selections.contains(it.key) } + } + } + .stateIn( + scope = scope, + started = SharingStarted.Eagerly, + initialValue = emptyMap(), + ) + + /** + * Returns a snapshot of the [KeyguardQuickAffordanceConfig] instances of the affordances at the + * slot with the given ID. The configs are sorted in descending priority order. + */ + suspend fun getSelections(slotId: String): List<KeyguardQuickAffordanceConfig> { + val selections = selectionManager.getSelections().getOrDefault(slotId, emptyList()) + return configs.filter { selections.contains(it.key) } + } + + /** + * Returns a snapshot of the IDs of the selected affordances, indexed by slot ID. The configs + * are sorted in descending priority order. + */ + suspend fun getSelections(): Map<String, List<String>> { + return selectionManager.getSelections() + } + + /** + * Updates the IDs of affordances to show at the slot with the given ID. The order of affordance + * IDs should be descending priority order. + */ + fun setSelections( + slotId: String, + affordanceIds: List<String>, + ) { + scope.launch(backgroundDispatcher) { + selectionManager.setSelections( + slotId = slotId, + affordanceIds = affordanceIds, + ) + } + } + + /** + * Returns the list of representation objects for all known affordances, regardless of what is + * selected. This is useful for building experiences like the picker/selector or user settings + * so the user can see everything that can be selected in a menu. + */ + fun getAffordancePickerRepresentations(): List<KeyguardQuickAffordancePickerRepresentation> { + return configs.map { config -> + KeyguardQuickAffordancePickerRepresentation( + id = config.key, + name = config.pickerName, + iconResourceId = config.pickerIconResourceId, + ) + } + } + + /** + * Returns the list of representation objects for all available slots on the keyguard. This is + * useful for building experiences like the picker/selector or user settings so the user can see + * each slot and select which affordance(s) is/are installed in each slot on the keyguard. + */ + fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> { + // TODO(b/256195304): source these from a config XML file. + return listOf( + KeyguardSlotPickerRepresentation( + id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + ), + KeyguardSlotPickerRepresentation( + id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, + ), + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index 6baaf5f10024..c867c6e9229b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -23,9 +23,12 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.doze.DozeHost import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.statusbar.phone.BiometricUnlockController +import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode import com.android.systemui.statusbar.policy.KeyguardStateController import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose @@ -65,6 +68,9 @@ interface KeyguardRepository { */ val isKeyguardShowing: Flow<Boolean> + /** Observable for whether the bouncer is showing. */ + val isBouncerShowing: Flow<Boolean> + /** * Observable for whether we are in doze state. * @@ -95,6 +101,9 @@ interface KeyguardRepository { /** Observable for device wake/sleep state */ val wakefulnessState: Flow<WakefulnessModel> + /** Observable for biometric unlock modes */ + val biometricUnlockState: Flow<BiometricUnlockModel> + /** * Returns `true` if the keyguard is showing; `false` otherwise. * @@ -125,6 +134,7 @@ constructor( private val keyguardStateController: KeyguardStateController, dozeHost: DozeHost, wakefulnessLifecycle: WakefulnessLifecycle, + biometricUnlockController: BiometricUnlockController, ) : KeyguardRepository { private val _animateBottomAreaDozingTransitions = MutableStateFlow(false) override val animateBottomAreaDozingTransitions = @@ -159,6 +169,29 @@ constructor( awaitClose { keyguardStateController.removeCallback(callback) } } + override val isBouncerShowing: Flow<Boolean> = conflatedCallbackFlow { + val callback = + object : KeyguardStateController.Callback { + override fun onBouncerShowingChanged() { + trySendWithFailureLogging( + keyguardStateController.isBouncerShowing, + TAG, + "updated isBouncerShowing" + ) + } + } + + keyguardStateController.addCallback(callback) + // Adding the callback does not send an initial update. + trySendWithFailureLogging( + keyguardStateController.isBouncerShowing, + TAG, + "initial isBouncerShowing" + ) + + awaitClose { keyguardStateController.removeCallback(callback) } + } + override val isDozing: Flow<Boolean> = conflatedCallbackFlow { val callback = @@ -248,6 +281,24 @@ constructor( awaitClose { wakefulnessLifecycle.removeObserver(callback) } } + override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow { + val callback = + object : BiometricUnlockController.BiometricModeListener { + override fun onModeChanged(@WakeAndUnlockMode mode: Int) { + trySendWithFailureLogging(biometricModeIntToObject(mode), TAG, "biometric mode") + } + } + + biometricUnlockController.addBiometricModeListener(callback) + trySendWithFailureLogging( + biometricModeIntToObject(biometricUnlockController.getMode()), + TAG, + "initial biometric mode" + ) + + awaitClose { biometricUnlockController.removeBiometricModeListener(callback) } + } + override fun setAnimateDozingTransitions(animate: Boolean) { _animateBottomAreaDozingTransitions.value = animate } @@ -279,6 +330,20 @@ constructor( } } + private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockModel { + return when (value) { + 0 -> BiometricUnlockModel.NONE + 1 -> BiometricUnlockModel.WAKE_AND_UNLOCK + 2 -> BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING + 3 -> BiometricUnlockModel.SHOW_BOUNCER + 4 -> BiometricUnlockModel.ONLY_WAKE + 5 -> BiometricUnlockModel.UNLOCK_COLLAPSING + 6 -> BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM + 7 -> BiometricUnlockModel.DISMISS_BOUNCER + else -> throw IllegalArgumentException("Invalid BiometricUnlockModel value: $value") + } + } + companion object { private const val TAG = "KeyguardRepositoryImpl" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt new file mode 100644 index 000000000000..7e01db34319f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.animation.ValueAnimator +import com.android.systemui.animation.Interpolators +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch + +@SysUISingleton +class AodToGoneTransitionInteractor +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val keyguardInteractor: KeyguardInteractor, + private val keyguardTransitionRepository: KeyguardTransitionRepository, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, +) : TransitionInteractor("AOD->GONE") { + + private val wakeAndUnlockModes = + setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING) + + override fun start() { + scope.launch { + keyguardInteractor.biometricUnlockState + .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) + .collect { pair -> + val (biometricUnlockState, keyguardState) = pair + if ( + keyguardState == KeyguardState.AOD && + wakeAndUnlockModes.contains(biometricUnlockState) + ) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.AOD, + KeyguardState.GONE, + getAnimator(), + ) + ) + } + } + } + } + + private fun getAnimator(): ValueAnimator { + return ValueAnimator().apply { + setInterpolator(Interpolators.LINEAR) + setDuration(TRANSITION_DURATION_MS) + } + } + + companion object { + private const val TRANSITION_DURATION_MS = 500L + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 03c6a789326e..614ff8d930d8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -19,6 +19,8 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.WakefulnessModel import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -39,10 +41,19 @@ constructor( val dozeAmount: Flow<Float> = repository.dozeAmount /** Whether the system is in doze mode. */ val isDozing: Flow<Boolean> = repository.isDozing - /** Whether the keyguard is showing to not. */ + /** Whether the keyguard is showing or not. */ val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing + /** Whether the bouncer is showing or not. */ + val isBouncerShowing: Flow<Boolean> = repository.isBouncerShowing /** The device wake/sleep state */ val wakefulnessState: Flow<WakefulnessModel> = repository.wakefulnessState + /** Observable for the [StatusBarState] */ + val statusBarState: Flow<StatusBarState> = repository.statusBarState + /** + * Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear, + * side, under display) is used to unlock the device. + */ + val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState fun isKeyguardShowing(): Boolean { return repository.isKeyguardShowing() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index 13d97aaf28da..92caa89bb0e8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -18,19 +18,30 @@ package com.android.systemui.keyguard.domain.interactor import android.content.Intent +import android.util.Log import com.android.internal.widget.LockPatternUtils import com.android.systemui.animation.Expandable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig +import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry +import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation +import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserTracker +import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import com.android.systemui.statusbar.policy.KeyguardStateController +import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart @SysUISingleton @@ -43,7 +54,12 @@ constructor( private val keyguardStateController: KeyguardStateController, private val userTracker: UserTracker, private val activityStarter: ActivityStarter, + private val featureFlags: FeatureFlags, + private val repository: Lazy<KeyguardQuickAffordanceRepository>, ) { + private val isUsingRepository: Boolean + get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES) + /** Returns an observable for the quick affordance at the given position. */ fun quickAffordance( position: KeyguardQuickAffordancePosition @@ -72,7 +88,19 @@ constructor( configKey: String, expandable: Expandable?, ) { - @Suppress("UNCHECKED_CAST") val config = registry.get(configKey) + @Suppress("UNCHECKED_CAST") + val config = + if (isUsingRepository) { + val (slotId, decodedConfigKey) = configKey.decode() + repository.get().selections.value[slotId]?.find { it.key == decodedConfigKey } + } else { + registry.get(configKey) + } + if (config == null) { + Log.e(TAG, "Affordance config with key of \"$configKey\" not found!") + return + } + when (val result = config.onTriggered(expandable)) { is KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity -> launchQuickAffordance( @@ -84,28 +112,138 @@ constructor( } } + /** + * Selects an affordance with the given ID on the slot with the given ID. + * + * @return `true` if the affordance was selected successfully; `false` otherwise. + */ + suspend fun select(slotId: String, affordanceId: String): Boolean { + check(isUsingRepository) + + val slots = repository.get().getSlotPickerRepresentations() + val slot = slots.find { it.id == slotId } ?: return false + val selections = + repository.get().getSelections().getOrDefault(slotId, emptyList()).toMutableList() + val alreadySelected = selections.remove(affordanceId) + if (!alreadySelected) { + while (selections.size > 0 && selections.size >= slot.maxSelectedAffordances) { + selections.removeAt(0) + } + } + + selections.add(affordanceId) + + repository + .get() + .setSelections( + slotId = slotId, + affordanceIds = selections, + ) + + return true + } + + /** + * Unselects one or all affordances from the slot with the given ID. + * + * @param slotId The ID of the slot. + * @param affordanceId The ID of the affordance to remove; if `null`, removes all affordances + * from the slot. + * @return `true` if the affordance was successfully removed; `false` otherwise (for example, if + * the affordance was not on the slot to begin with). + */ + suspend fun unselect(slotId: String, affordanceId: String?): Boolean { + check(isUsingRepository) + + val slots = repository.get().getSlotPickerRepresentations() + if (slots.find { it.id == slotId } == null) { + return false + } + + if (affordanceId.isNullOrEmpty()) { + return if ( + repository.get().getSelections().getOrDefault(slotId, emptyList()).isEmpty() + ) { + false + } else { + repository.get().setSelections(slotId = slotId, affordanceIds = emptyList()) + true + } + } + + val selections = + repository.get().getSelections().getOrDefault(slotId, emptyList()).toMutableList() + return if (selections.remove(affordanceId)) { + repository + .get() + .setSelections( + slotId = slotId, + affordanceIds = selections, + ) + true + } else { + false + } + } + + /** Returns affordance IDs indexed by slot ID, for all known slots. */ + suspend fun getSelections(): Map<String, List<String>> { + check(isUsingRepository) + + val selections = repository.get().getSelections() + return repository.get().getSlotPickerRepresentations().associate { slotRepresentation -> + slotRepresentation.id to (selections[slotRepresentation.id] ?: emptyList()) + } + } + private fun quickAffordanceInternal( position: KeyguardQuickAffordancePosition ): Flow<KeyguardQuickAffordanceModel> { - val configs = registry.getAll(position) + return if (isUsingRepository) { + repository + .get() + .selections + .map { it[position.toSlotId()] ?: emptyList() } + .flatMapLatest { configs -> combinedConfigs(position, configs) } + } else { + combinedConfigs(position, registry.getAll(position)) + } + } + + private fun combinedConfigs( + position: KeyguardQuickAffordancePosition, + configs: List<KeyguardQuickAffordanceConfig>, + ): Flow<KeyguardQuickAffordanceModel> { + if (configs.isEmpty()) { + return flowOf(KeyguardQuickAffordanceModel.Hidden) + } + return combine( configs.map { config -> - // We emit an initial "Hidden" value to make sure that there's always an initial - // value and avoid subtle bugs where the downstream isn't receiving any values - // because one config implementation is not emitting an initial value. For example, - // see b/244296596. + // We emit an initial "Hidden" value to make sure that there's always an + // initial value and avoid subtle bugs where the downstream isn't receiving + // any values because one config implementation is not emitting an initial + // value. For example, see b/244296596. config.lockScreenState.onStart { emit(KeyguardQuickAffordanceConfig.LockScreenState.Hidden) } } ) { states -> val index = - states.indexOfFirst { it is KeyguardQuickAffordanceConfig.LockScreenState.Visible } + states.indexOfFirst { state -> + state is KeyguardQuickAffordanceConfig.LockScreenState.Visible + } if (index != -1) { val visibleState = states[index] as KeyguardQuickAffordanceConfig.LockScreenState.Visible + val configKey = configs[index].key KeyguardQuickAffordanceModel.Visible( - configKey = configs[index].key, + configKey = + if (isUsingRepository) { + configKey.encode(position.toSlotId()) + } else { + configKey + }, icon = visibleState.icon, activationState = visibleState.activationState, ) @@ -145,4 +283,39 @@ constructor( ) } } + + private fun KeyguardQuickAffordancePosition.toSlotId(): String { + return when (this) { + KeyguardQuickAffordancePosition.BOTTOM_START -> + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + KeyguardQuickAffordancePosition.BOTTOM_END -> + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END + } + } + + private fun String.encode(slotId: String): String { + return "$slotId$DELIMITER$this" + } + + private fun String.decode(): Pair<String, String> { + val splitUp = this.split(DELIMITER) + return Pair(splitUp[0], splitUp[1]) + } + + fun getAffordancePickerRepresentations(): List<KeyguardQuickAffordancePickerRepresentation> { + check(isUsingRepository) + + return repository.get().getAffordancePickerRepresentations() + } + + fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> { + check(isUsingRepository) + + return repository.get().getSlotPickerRepresentations() + } + + companion object { + private const val TAG = "KeyguardQuickAffordanceInteractor" + private const val DELIMITER = "::" + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt index 83d94325b9d4..57fb4a114700 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt @@ -41,11 +41,13 @@ constructor( } scope.launch { - interactor.finishedKeyguardState.collect { logger.i("Finished transition to", it) } + interactor.finishedKeyguardTransitionStep.collect { + logger.i("Finished transition", it) + } } scope.launch { - interactor.startedKeyguardState.collect { logger.i("Started transition to", it) } + interactor.startedKeyguardTransitionStep.collect { logger.i("Started transition", it) } } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt index d5ea77b8e729..a7c6d4450336 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt @@ -41,6 +41,7 @@ constructor( is AodLockscreenTransitionInteractor -> Log.d(TAG, "Started $it") is GoneAodTransitionInteractor -> Log.d(TAG, "Started $it") is LockscreenGoneTransitionInteractor -> Log.d(TAG, "Started $it") + is AodToGoneTransitionInteractor -> Log.d(TAG, "Started $it") } it.start() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index dffd097a77c5..749183e241e5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -21,7 +21,6 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.AOD -import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -44,8 +43,9 @@ constructor( /** LOCKSCREEN->AOD transition information. */ val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD) - /** GONE->AOD information. */ - val goneToAodTransition: Flow<TransitionStep> = repository.transition(GONE, AOD) + /** (any)->AOD transition information */ + val anyStateToAodTransition: Flow<TransitionStep> = + repository.transitions.filter { step -> step.to == KeyguardState.AOD } /** * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <-> @@ -57,15 +57,15 @@ constructor( lockscreenToAodTransition, ) + /* The last [TransitionStep] with a [TransitionState] of FINISHED */ + val finishedKeyguardTransitionStep: Flow<TransitionStep> = + repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED } + /* The last completed [KeyguardState] transition */ val finishedKeyguardState: Flow<KeyguardState> = - repository.transitions - .filter { step -> step.transitionState == TransitionState.FINISHED } - .map { step -> step.to } + finishedKeyguardTransitionStep.map { step -> step.to } - /* The last started [KeyguardState] transition */ - val startedKeyguardState: Flow<KeyguardState> = - repository.transitions - .filter { step -> step.transitionState == TransitionState.STARTED } - .map { step -> step.to } + /* The last [TransitionStep] with a [TransitionState] of STARTED */ + val startedKeyguardTransitionStep: Flow<TransitionStep> = + repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt index 761f3fd9d9f9..fd4814d2bc94 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt @@ -16,14 +16,16 @@ package com.android.systemui.keyguard.domain.interactor +import android.animation.ValueAnimator +import com.android.systemui.animation.Interpolators import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.util.kotlin.sample import java.util.UUID @@ -38,7 +40,7 @@ class LockscreenBouncerTransitionInteractor @Inject constructor( @Application private val scope: CoroutineScope, - private val keyguardRepository: KeyguardRepository, + private val keyguardInteractor: KeyguardInteractor, private val shadeRepository: ShadeRepository, private val keyguardTransitionRepository: KeyguardTransitionRepository, private val keyguardTransitionInteractor: KeyguardTransitionInteractor @@ -47,12 +49,47 @@ constructor( private var transitionId: UUID? = null override fun start() { + listenForDraggingUpToBouncer() + listenForBouncerHiding() + } + + private fun listenForBouncerHiding() { + scope.launch { + keyguardInteractor.isBouncerShowing + .sample(keyguardInteractor.wakefulnessState, { a, b -> Pair(a, b) }) + .collect { pair -> + val (isBouncerShowing, wakefulnessState) = pair + if (!isBouncerShowing) { + val to = + if ( + wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP || + wakefulnessState == WakefulnessModel.ASLEEP + ) { + KeyguardState.AOD + } else { + KeyguardState.LOCKSCREEN + } + keyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = name, + from = KeyguardState.BOUNCER, + to = to, + animator = getAnimator(), + ) + ) + } + } + } + } + + /* Starts transitions when manually dragging up the bouncer from the lockscreen. */ + private fun listenForDraggingUpToBouncer() { scope.launch { shadeRepository.shadeModel .sample( combine( keyguardTransitionInteractor.finishedKeyguardState, - keyguardRepository.statusBarState, + keyguardInteractor.statusBarState, ) { keyguardState, statusBarState -> Pair(keyguardState, statusBarState) }, @@ -100,4 +137,15 @@ constructor( } } } + + private fun getAnimator(): ValueAnimator { + return ValueAnimator().apply { + setInterpolator(Interpolators.LINEAR) + setDuration(TRANSITION_DURATION_MS) + } + } + + companion object { + private const val TRANSITION_DURATION_MS = 300L + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt index 728bafae2a4c..37f33afbf53e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt @@ -42,6 +42,8 @@ abstract class StartKeyguardTransitionModule { @Binds @IntoSet abstract fun goneAod(impl: GoneAodTransitionInteractor): TransitionInteractor + @Binds @IntoSet abstract fun aodGone(impl: AodToGoneTransitionInteractor): TransitionInteractor + @Binds @IntoSet abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt new file mode 100644 index 000000000000..db709b476c5d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.systemui.keyguard.shared.model + +/** Model device wakefulness states. */ +enum class BiometricUnlockModel { + /** Mode in which we don't need to wake up the device when we authenticate. */ + NONE, + /** + * Mode in which we wake up the device, and directly dismiss Keyguard. Active when we acquire a + * fingerprint while the screen is off and the device was sleeping. + */ + WAKE_AND_UNLOCK, + /** + * Mode in which we wake the device up, and fade out the Keyguard contents because they were + * already visible while pulsing in doze mode. + */ + WAKE_AND_UNLOCK_PULSING, + /** + * Mode in which we wake up the device, but play the normal dismiss animation. Active when we + * acquire a fingerprint pulsing in doze mode. + */ + SHOW_BOUNCER, + /** + * Mode in which we only wake up the device, and keyguard was not showing when we authenticated. + */ + ONLY_WAKE, + /** + * Mode in which fingerprint unlocks the device or passive auth (ie face auth) unlocks the + * device while being requested when keyguard is occluded or showing. + */ + UNLOCK_COLLAPSING, + /** When bouncer is visible and will be dismissed. */ + DISMISS_BOUNCER, + /** Mode in which fingerprint wakes and unlocks the device from a dream. */ + WAKE_AND_UNLOCK_FROM_DREAM, +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt index 1550ab3bed63..a56bc900f936 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -12,8 +12,19 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * */ -package com.android.systemui.shared.system; +package com.android.systemui.keyguard.shared.model + +import androidx.annotation.DrawableRes -parcelable RemoteTransitionCompat; +/** + * Representation of a quick affordance for use to build "picker", "selector", or "settings" + * experiences. + */ +data class KeyguardQuickAffordancePickerRepresentation( + val id: String, + val name: String, + @DrawableRes val iconResourceId: Int, +) diff --git a/services/tests/servicestests/src/com/android/server/appwidget/DummyAppWidget.java b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSlotPickerRepresentation.kt index fd99b21c9538..86f27567ddf8 100644 --- a/services/tests/servicestests/src/com/android/server/appwidget/DummyAppWidget.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSlotPickerRepresentation.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,21 +11,18 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. + * */ -package com.android.server.appwidget; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; +package com.android.systemui.keyguard.shared.model /** - * Placeholder widget for testing + * Representation of a quick affordance slot (or position) for use to build "picker", "selector", or + * "settings" experiences. */ -public class DummyAppWidget extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - } -} +data class KeyguardSlotPickerRepresentation( + val id: String, + /** The maximum number of selected affordances that can be present on this slot. */ + val maxSelectedAffordances: Int = 1, +) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt index 0ca358210813..732a6f7b887a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt @@ -21,10 +21,11 @@ data class TransitionStep( val to: KeyguardState = KeyguardState.NONE, val value: Float = 0f, // constrained [0.0, 1.0] val transitionState: TransitionState = TransitionState.FINISHED, + val ownerName: String = "", ) { constructor( info: TransitionInfo, value: Float, transitionState: TransitionState, - ) : this(info.from, info.to, value, transitionState) + ) : this(info.from, info.to, value, transitionState, info.ownerName) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt index e38c1baaeae9..baeee9fc4f74 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt @@ -610,7 +610,11 @@ constructor( // are // elements in mediaPlayers. if (MediaPlayerData.players().size != mediaContent.childCount) { - Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync") + throw IllegalStateException( + "Size of players list and number of views in carousel are out of sync. " + + "Players size is ${MediaPlayerData.players().size}. " + + "View count is ${mediaContent.childCount}." + ) } return existingPlayer == null } @@ -667,7 +671,11 @@ constructor( // are // elements in mediaPlayers. if (MediaPlayerData.players().size != mediaContent.childCount) { - Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync") + throw IllegalStateException( + "Size of players list and number of views in carousel are out of sync. " + + "Players size is ${MediaPlayerData.players().size}. " + + "View count is ${mediaContent.childCount}." + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt new file mode 100644 index 000000000000..1324d2c5c27b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.motiontool + +import android.view.WindowManagerGlobal +import com.android.app.motiontool.DdmHandleMotionTool +import com.android.app.motiontool.MotionToolManager +import com.android.app.viewcapture.ViewCapture +import com.android.systemui.CoreStartable +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +interface MotionToolModule { + + companion object { + + @Provides + fun provideDdmHandleMotionTool(motionToolManager: MotionToolManager): DdmHandleMotionTool { + return DdmHandleMotionTool.getInstance(motionToolManager) + } + + @Provides + fun provideMotionToolManager( + viewCapture: ViewCapture, + windowManagerGlobal: WindowManagerGlobal + ): MotionToolManager { + return MotionToolManager.getInstance(viewCapture, windowManagerGlobal) + } + + @Provides + fun provideWindowManagerGlobal(): WindowManagerGlobal = WindowManagerGlobal.getInstance() + + @Provides fun provideViewCapture(): ViewCapture = ViewCapture.getInstance() + } + + @Binds + @IntoMap + @ClassKey(MotionToolStartable::class) + fun bindMotionToolStartable(impl: MotionToolStartable): CoreStartable +} diff --git a/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolStartable.kt b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolStartable.kt new file mode 100644 index 000000000000..fbb9538316f7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolStartable.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.motiontool + +import com.android.app.motiontool.DdmHandleMotionTool +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +@SysUISingleton +class MotionToolStartable +@Inject +internal constructor(private val ddmHandleMotionTool: DdmHandleMotionTool) : CoreStartable { + + override fun start() { + ddmHandleMotionTool.register() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index d247f249e2fd..b964b76795b8 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -22,7 +22,7 @@ import android.os.UserManager import android.view.KeyEvent import com.android.systemui.dagger.SysUISingleton import com.android.systemui.util.kotlin.getOrNull -import com.android.wm.shell.floating.FloatingTasks +import com.android.wm.shell.bubbles.Bubbles import java.util.Optional import javax.inject.Inject @@ -39,7 +39,7 @@ internal class NoteTaskController constructor( private val context: Context, private val intentResolver: NoteTaskIntentResolver, - private val optionalFloatingTasks: Optional<FloatingTasks>, + private val optionalBubbles: Optional<Bubbles>, private val optionalKeyguardManager: Optional<KeyguardManager>, private val optionalUserManager: Optional<UserManager>, @NoteTaskEnabledKey private val isEnabled: Boolean, @@ -54,7 +54,7 @@ constructor( } private fun showNoteTask() { - val floatingTasks = optionalFloatingTasks.getOrNull() ?: return + val bubbles = optionalBubbles.getOrNull() ?: return val keyguardManager = optionalKeyguardManager.getOrNull() ?: return val userManager = optionalUserManager.getOrNull() ?: return val intent = intentResolver.resolveIntent() ?: return @@ -66,7 +66,7 @@ constructor( context.startActivity(intent) } else { // TODO(b/254606432): Should include Intent.EXTRA_FLOATING_WINDOW_MODE parameter. - floatingTasks.showOrSetStashed(intent) + bubbles.showAppBubble(intent) } } } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt index d84717da3a21..0a5b6008981b 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt @@ -17,7 +17,7 @@ package com.android.systemui.notetask import com.android.systemui.statusbar.CommandQueue -import com.android.wm.shell.floating.FloatingTasks +import com.android.wm.shell.bubbles.Bubbles import dagger.Lazy import java.util.Optional import javax.inject.Inject @@ -26,7 +26,7 @@ import javax.inject.Inject internal class NoteTaskInitializer @Inject constructor( - private val optionalFloatingTasks: Optional<FloatingTasks>, + private val optionalBubbles: Optional<Bubbles>, private val lazyNoteTaskController: Lazy<NoteTaskController>, private val commandQueue: CommandQueue, @NoteTaskEnabledKey private val isEnabled: Boolean, @@ -40,7 +40,7 @@ constructor( } fun initialize() { - if (isEnabled && optionalFloatingTasks.isPresent) { + if (isEnabled && optionalBubbles.isPresent) { commandQueue.addCallback(callbacks) } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 46c4f410d078..ba97297421b3 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -108,7 +108,6 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Optional; -import java.util.function.BiConsumer; import java.util.function.Supplier; import javax.inject.Inject; @@ -470,8 +469,6 @@ public class OverviewProxyService extends CurrentUserTracker implements }; private final StatusBarWindowCallback mStatusBarWindowCallback = this::onStatusBarStateChanged; - private final BiConsumer<Rect, Rect> mSplitScreenBoundsChangeListener = - this::notifySplitScreenBoundsChanged; // This is the death handler for the binder from the launcher service private final IBinder.DeathRecipient mOverviewServiceDeathRcpt @@ -839,26 +836,6 @@ public class OverviewProxyService extends CurrentUserTracker implements } } - /** - * Notifies the Launcher of split screen size changes - * - * @param secondaryWindowBounds Bounds of the secondary window including the insets - * @param secondaryWindowInsets stable insets received by the secondary window - */ - public void notifySplitScreenBoundsChanged( - Rect secondaryWindowBounds, Rect secondaryWindowInsets) { - try { - if (mOverviewProxy != null) { - mOverviewProxy.onSplitScreenSecondaryBoundsChanged( - secondaryWindowBounds, secondaryWindowInsets); - } else { - Log.e(TAG_OPS, "Failed to get overview proxy for split screen bounds."); - } - } catch (RemoteException e) { - Log.e(TAG_OPS, "Failed to call onSplitScreenSecondaryBoundsChanged()", e); - } - } - private final ScreenLifecycle.Observer mLifecycleObserver = new ScreenLifecycle.Observer() { /** * Notifies the Launcher that screen turned on and ready to use diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index fac2a97e65a3..350f49bcf37d 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -455,7 +455,6 @@ public final class NotificationPanelViewController { * need to take this into account in our panel height calculation. */ private boolean mQsAnimatorExpand; - private boolean mIsLaunchTransitionFinished; private ValueAnimator mQsSizeChangeAnimator; private boolean mQsScrimEnabled = true; private boolean mQsTouchAboveFalsingThreshold; @@ -1753,7 +1752,6 @@ public final class NotificationPanelViewController { } public void resetViews(boolean animate) { - mIsLaunchTransitionFinished = false; mCentralSurfaces.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */, true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */); if (animate && !isFullyCollapsed()) { @@ -3779,10 +3777,6 @@ public final class NotificationPanelViewController { mQs.closeCustomizer(); } - public boolean isLaunchTransitionFinished() { - return mIsLaunchTransitionFinished; - } - public void setIsLaunchAnimationRunning(boolean running) { boolean wasRunning = mIsLaunchAnimationRunning; mIsLaunchAnimationRunning = running; @@ -5681,6 +5675,7 @@ public final class NotificationPanelViewController { /** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */ public boolean onInterceptTouchEvent(MotionEvent event) { + mShadeLog.logMotionEvent(event, "NPVC onInterceptTouchEvent"); if (SPEW_LOGCAT) { Log.v(TAG, "NPVC onInterceptTouchEvent (" + event.getId() + "): (" + event.getX() @@ -5693,6 +5688,8 @@ public final class NotificationPanelViewController { // Do not let touches go to shade or QS if the bouncer is visible, // but still let user swipe down to expand the panel, dismissing the bouncer. if (mCentralSurfaces.isBouncerShowing()) { + mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: " + + "bouncer is showing"); return true; } if (mCommandQueue.panelsEnabled() @@ -5700,15 +5697,21 @@ public final class NotificationPanelViewController { && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { mMetricsLogger.count(COUNTER_PANEL_OPEN, 1); mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1); + mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: " + + "HeadsUpTouchHelper"); return true; } if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0) && mPulseExpansionHandler.onInterceptTouchEvent(event)) { + mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: " + + "PulseExpansionHandler"); return true; } if (!isFullyCollapsed() && onQsIntercept(event)) { debugLog("onQsIntercept true"); + mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: " + + "QsIntercept"); return true; } if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted @@ -5739,6 +5742,9 @@ public final class NotificationPanelViewController { if (mAnimatingOnDown && mClosing && !mHintAnimationRunning) { cancelHeightAnimator(); mTouchSlopExceeded = true; + mShadeLog.v("NotificationPanelViewController MotionEvent intercepted:" + + " mAnimatingOnDown: true, mClosing: true, mHintAnimationRunning:" + + " false"); return true; } mInitialExpandY = y; @@ -5783,6 +5789,8 @@ public final class NotificationPanelViewController { && hAbs > Math.abs(x - mInitialExpandX)) { cancelHeightAnimator(); startExpandMotion(x, y, true /* startTracking */, mExpandedHeight); + mShadeLog.v("NotificationPanelViewController MotionEvent" + + " intercepted: startExpandMotion"); return true; } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt index 73c6d507f035..85b259e54f37 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt @@ -1,3 +1,19 @@ +/* + * 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.shade import android.view.View diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt index 084b7dc3a646..bf622c941abb 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt @@ -52,6 +52,7 @@ class PulsingGestureListener @Inject constructor( private val centralSurfaces: CentralSurfaces, private val ambientDisplayConfiguration: AmbientDisplayConfiguration, private val statusBarStateController: StatusBarStateController, + private val shadeLogger: ShadeLogger, tunerService: TunerService, dumpManager: DumpManager ) : GestureDetector.SimpleOnGestureListener(), Dumpable { @@ -77,18 +78,23 @@ class PulsingGestureListener @Inject constructor( } override fun onSingleTapUp(e: MotionEvent): Boolean { - if (statusBarStateController.isDozing && - singleTapEnabled && - !dockManager.isDocked && - !falsingManager.isProximityNear && - !falsingManager.isFalseTap(LOW_PENALTY) - ) { - centralSurfaces.wakeUpIfDozing( + val isNotDocked = !dockManager.isDocked + shadeLogger.logSingleTapUp(statusBarStateController.isDozing, singleTapEnabled, isNotDocked) + if (statusBarStateController.isDozing && singleTapEnabled && isNotDocked) { + val proximityIsNotNear = !falsingManager.isProximityNear + val isNotAFalseTap = !falsingManager.isFalseTap(LOW_PENALTY) + shadeLogger.logSingleTapUpFalsingState(proximityIsNotNear, isNotAFalseTap) + if (proximityIsNotNear && isNotAFalseTap) { + shadeLogger.d("Single tap handled, requesting centralSurfaces.wakeUpIfDozing") + centralSurfaces.wakeUpIfDozing( SystemClock.uptimeMillis(), notificationShadeWindowView, - "PULSING_SINGLE_TAP") + "PULSING_SINGLE_TAP" + ) + } return true } + shadeLogger.d("onSingleTapUp event ignored") return false } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt index 7f1bba350af1..40ed40a767e5 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt @@ -1,3 +1,19 @@ +/* + * 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.shade import android.view.MotionEvent @@ -16,6 +32,10 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { buffer.log(TAG, LogLevel.VERBOSE, msg) } + fun d(@CompileTimeConstant msg: String) { + buffer.log(TAG, LogLevel.DEBUG, msg) + } + private inline fun log( logLevel: LogLevel, initializer: LogMessage.() -> Unit, @@ -123,4 +143,25 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { "animatingQs=$long1" }) } + + fun logSingleTapUp(isDozing: Boolean, singleTapEnabled: Boolean, isNotDocked: Boolean) { + log(LogLevel.DEBUG, { + bool1 = isDozing + bool2 = singleTapEnabled + bool3 = isNotDocked + }, { + "PulsingGestureListener#onSingleTapUp all of this must true for single " + + "tap to be detected: isDozing: $bool1, singleTapEnabled: $bool2, isNotDocked: $bool3" + }) + } + + fun logSingleTapUpFalsingState(proximityIsNotNear: Boolean, isNotFalseTap: Boolean) { + log(LogLevel.DEBUG, { + bool1 = proximityIsNotNear + bool2 = isNotFalseTap + }, { + "PulsingGestureListener#onSingleTapUp all of this must true for single " + + "tap to be detected: proximityIsNotNear: $bool1, isNotFalseTap: $bool2" + }) + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/NoOpOverScroller.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/NoOpOverScroller.kt index f4db3ab9289b..8847dbd0b0c0 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/transition/NoOpOverScroller.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/transition/NoOpOverScroller.kt @@ -1,3 +1,19 @@ +/* + * 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.shade.transition import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt index a77c21a8da57..218e897794fc 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt @@ -1,3 +1,19 @@ +/* + * 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.shade.transition import android.content.res.Configuration diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt index 22e847deb7b2..a4642e060a22 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt @@ -1,3 +1,19 @@ +/* + * 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.shade.transition import com.android.systemui.shade.PanelState diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt index 1e8208f52fdc..1054aa59db61 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt @@ -1,3 +1,19 @@ +/* + * 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.shade.transition import android.content.Context diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt index 8c57194c0950..fde08ee859e0 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt @@ -1,3 +1,19 @@ +/* + * 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.shade.transition import android.animation.Animator diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java index 258f9fc5449f..c070fccf9808 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar; import android.annotation.NonNull; import android.annotation.Nullable; import android.media.AudioAttributes; +import android.os.Process; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; @@ -33,6 +34,7 @@ import java.util.concurrent.Executor; import javax.inject.Inject; /** + * */ @SysUISingleton public class VibratorHelper { @@ -40,9 +42,18 @@ public class VibratorHelper { private final Vibrator mVibrator; public static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES = VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH); + + private static final VibrationEffect BIOMETRIC_SUCCESS_VIBRATION_EFFECT = + VibrationEffect.get(VibrationEffect.EFFECT_CLICK); + private static final VibrationEffect BIOMETRIC_ERROR_VIBRATION_EFFECT = + VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK); + private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK); + private final Executor mExecutor; /** + * */ @Inject public VibratorHelper(@Nullable Vibrator vibrator, @Background Executor executor) { @@ -109,4 +120,23 @@ public class VibratorHelper { } mExecutor.execute(mVibrator::cancel); } + + /** + * Perform vibration when biometric authentication success + */ + public void vibrateAuthSuccess(String reason) { + vibrate(Process.myUid(), + "com.android.systemui", + BIOMETRIC_SUCCESS_VIBRATION_EFFECT, reason, + HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES); + } + + /** + * Perform vibration when biometric authentication error + */ + public void vibrateAuthError(String reason) { + vibrate(Process.myUid(), "com.android.systemui", + BIOMETRIC_ERROR_VIBRATION_EFFECT, reason, + HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt index 470cbcb842c4..5dbb4f9d70df 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt @@ -33,6 +33,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback +import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider import com.android.systemui.statusbar.notification.collection.render.NodeController import com.android.systemui.statusbar.notification.dagger.IncomingHeader import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder @@ -68,6 +69,7 @@ class HeadsUpCoordinator @Inject constructor( private val mHeadsUpViewBinder: HeadsUpViewBinder, private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider, private val mRemoteInputManager: NotificationRemoteInputManager, + private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider, @IncomingHeader private val mIncomingHeaderController: NodeController, @Main private val mExecutor: DelayableExecutor, ) : Coordinator { @@ -380,6 +382,12 @@ class HeadsUpCoordinator @Inject constructor( * Notification was just added and if it should heads up, bind the view and then show it. */ override fun onEntryAdded(entry: NotificationEntry) { + // First check whether this notification should launch a full screen intent, and + // launch it if needed. + if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) { + mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry) + } + // shouldHeadsUp includes check for whether this notification should be filtered val shouldHeadsUpEver = mNotificationInterruptStateProvider.shouldHeadsUp(entry) mPostedEntries[entry.key] = PostedEntry( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProvider.kt new file mode 100644 index 000000000000..74ff78e25a2f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProvider.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.provider + +import android.util.Log +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.util.ListenerSet +import javax.inject.Inject + +/** + * A class that enables communication of decisions to launch a notification's full screen intent. + */ +@SysUISingleton +class LaunchFullScreenIntentProvider @Inject constructor() { + companion object { + private const val TAG = "LaunchFullScreenIntentProvider" + } + private val listeners = ListenerSet<Listener>() + + /** + * Registers a listener with this provider. These listeners will be alerted whenever a full + * screen intent should be launched for a notification entry. + */ + fun registerListener(listener: Listener) { + listeners.addIfAbsent(listener) + } + + /** Removes the specified listener. */ + fun removeListener(listener: Listener) { + listeners.remove(listener) + } + + /** + * Sends a request to launch full screen intent for the given notification entry to all + * registered listeners. + */ + fun launchFullScreenIntent(entry: NotificationEntry) { + if (listeners.isEmpty()) { + // This should never happen, but we should definitely know if it does because having + // no listeners would indicate that FSIs are getting entirely dropped on the floor. + Log.wtf(TAG, "no listeners found when launchFullScreenIntent requested") + } + for (listener in listeners) { + listener.onFullScreenIntentRequested(entry) + } + } + + /** Listener interface for passing full screen intent launch decisions. */ + fun interface Listener { + /** + * Invoked whenever a full screen intent launch is requested for the given notification + * entry. + */ + fun onFullScreenIntentRequested(entry: NotificationEntry) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 9e7717caf69c..de158c4d017e 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 @@ -1341,21 +1341,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mOnKeyguard; } - public void removeAllChildren() { - List<ExpandableNotificationRow> notificationChildren = - mChildrenContainer.getAttachedChildren(); - ArrayList<ExpandableNotificationRow> clonedList = new ArrayList<>(notificationChildren); - for (int i = 0; i < clonedList.size(); i++) { - ExpandableNotificationRow row = clonedList.get(i); - if (row.keepInParent()) { - continue; - } - mChildrenContainer.removeNotification(row); - row.setIsChildInGroup(false, null); - } - onAttachedChildrenCountChanged(); - } - @Override public void dismiss(boolean refocusOnDismiss) { super.dismiss(refocusOnDismiss); 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 c4ef28e889ca..2c3330e12229 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 @@ -114,6 +114,8 @@ import com.android.systemui.util.Assert; import com.android.systemui.util.DumpUtilsKt; import com.android.systemui.util.LargeScreenUtils; +import com.google.errorprone.annotations.CompileTimeConstant; + import java.io.PrintWriter; import java.lang.annotation.Retention; import java.util.ArrayList; @@ -3693,6 +3695,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.INPUT) void handleEmptySpaceClick(MotionEvent ev) { + logEmptySpaceClick(ev, isBelowLastNotification(mInitialTouchX, mInitialTouchY), + mStatusBarState, mTouchIsClick); switch (ev.getActionMasked()) { case MotionEvent.ACTION_MOVE: final float touchSlop = getTouchSlop(ev); @@ -3704,10 +3708,32 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable case MotionEvent.ACTION_UP: if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick && isBelowLastNotification(mInitialTouchX, mInitialTouchY)) { + debugLog("handleEmptySpaceClick: touch event propagated further"); mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY); } break; + default: + debugLog("handleEmptySpaceClick: MotionEvent ignored"); + } + } + + private void debugLog(@CompileTimeConstant String s) { + if (mLogger == null) { + return; + } + mLogger.d(s); + } + + private void logEmptySpaceClick(MotionEvent ev, boolean isTouchBelowLastNotification, + int statusBarState, boolean touchIsClick) { + if (mLogger == null) { + return; } + mLogger.logEmptySpaceClick( + isTouchBelowLastNotification, + statusBarState, + touchIsClick, + MotionEvent.actionToString(ev.getActionMasked())); } @ShadeViewRefactor(RefactorComponent.INPUT) 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 4c52db7f8732..64dd6dcd3008 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 @@ -2,6 +2,7 @@ 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.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey @@ -10,6 +11,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER +import com.google.errorprone.annotations.CompileTimeConstant import javax.inject.Inject class NotificationStackScrollLogger @Inject constructor( @@ -56,6 +58,25 @@ class NotificationStackScrollLogger @Inject constructor( "key: $str1 expected: $bool1 actual: $bool2" }) } + + fun d(@CompileTimeConstant msg: String) = buffer.log(TAG, DEBUG, msg) + + fun logEmptySpaceClick( + isBelowLastNotification: Boolean, + statusBarState: Int, + touchIsClick: Boolean, + motionEventDesc: String + ) { + buffer.log(TAG, DEBUG, { + int1 = statusBarState + bool1 = touchIsClick + bool2 = isBelowLastNotification + str1 = motionEventDesc + }, { + "handleEmptySpaceClick: statusBarState: $int1 isTouchAClick: $bool1 " + + "isTouchBelowNotification: $bool2 motionEvent: $str1" + }) + } } private const val TAG = "NotificationStackScroll"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index a2798f47be65..6e68079706f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -27,11 +27,8 @@ import android.hardware.fingerprint.FingerprintManager; import android.metrics.LogMaker; import android.os.Handler; import android.os.PowerManager; -import android.os.Process; import android.os.SystemClock; import android.os.Trace; -import android.os.VibrationAttributes; -import android.os.VibrationEffect; import android.util.Log; import androidx.annotation.Nullable; @@ -70,8 +67,10 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.HashSet; import java.util.Map; import java.util.Optional; +import java.util.Set; import javax.inject.Inject; @@ -87,12 +86,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock"; private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl(); private static final int UDFPS_ATTEMPTS_BEFORE_SHOW_BOUNCER = 3; - private static final VibrationEffect SUCCESS_VIBRATION_EFFECT = - VibrationEffect.get(VibrationEffect.EFFECT_CLICK); - private static final VibrationEffect ERROR_VIBRATION_EFFECT = - VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK); - private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = - VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK); @IntDef(prefix = { "MODE_" }, value = { MODE_NONE, @@ -167,7 +160,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private final NotificationShadeWindowController mNotificationShadeWindowController; private final SessionTracker mSessionTracker; private final int mConsecutiveFpFailureThreshold; - private final boolean mShouldVibrate; private int mMode; private BiometricSourceType mBiometricType; private KeyguardViewController mKeyguardViewController; @@ -177,7 +169,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private PendingAuthenticated mPendingAuthenticated = null; private boolean mHasScreenTurnedOnSinceAuthenticating; private boolean mFadedAwayAfterWakeAndUnlock; - private BiometricModeListener mBiometricModeListener; + private Set<BiometricModeListener> mBiometricModeListeners = new HashSet<>(); private final MetricsLogger mMetricsLogger; private final AuthController mAuthController; @@ -307,8 +299,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mHandler = handler; mConsecutiveFpFailureThreshold = resources.getInteger( R.integer.fp_consecutive_failure_time_ms); - mShouldVibrate = !(resources.getBoolean( - com.android.internal.R.bool.system_server_plays_face_haptics)); mKeyguardBypassController = keyguardBypassController; mKeyguardBypassController.setUnlockController(this); mMetricsLogger = metricsLogger; @@ -326,9 +316,14 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mKeyguardViewController = keyguardViewController; } - /** Sets a {@link BiometricModeListener}. */ - public void setBiometricModeListener(BiometricModeListener biometricModeListener) { - mBiometricModeListener = biometricModeListener; + /** Adds a {@link BiometricModeListener}. */ + public void addBiometricModeListener(BiometricModeListener listener) { + mBiometricModeListeners.add(listener); + } + + /** Removes a {@link BiometricModeListener}. */ + public void removeBiometricModeListener(BiometricModeListener listener) { + mBiometricModeListeners.remove(listener); } private final Runnable mReleaseBiometricWakeLockRunnable = new Runnable() { @@ -423,10 +418,10 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp public void startWakeAndUnlock(BiometricSourceType biometricSourceType, boolean isStrongBiometric) { int mode = calculateMode(biometricSourceType, isStrongBiometric); - if (BiometricSourceType.FACE == biometricSourceType && (mode == MODE_WAKE_AND_UNLOCK + if (mode == MODE_WAKE_AND_UNLOCK || mode == MODE_WAKE_AND_UNLOCK_PULSING || mode == MODE_UNLOCK_COLLAPSING - || mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM || mode == MODE_DISMISS_BOUNCER)) { - vibrateSuccess(); + || mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM || mode == MODE_DISMISS_BOUNCER) { + vibrateSuccess(biometricSourceType); } startWakeAndUnlock(mode); } @@ -511,15 +506,12 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp break; } onModeChanged(mMode); - if (mBiometricModeListener != null) { - mBiometricModeListener.notifyBiometricAuthModeChanged(); - } Trace.endSection(); } private void onModeChanged(@WakeAndUnlockMode int mode) { - if (mBiometricModeListener != null) { - mBiometricModeListener.onModeChanged(mode); + for (BiometricModeListener listener : mBiometricModeListeners) { + listener.onModeChanged(mode); } } @@ -659,10 +651,11 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp } // Suppress all face auth errors if fingerprint can be used to authenticate - if (biometricSourceType == BiometricSourceType.FACE + if ((biometricSourceType == BiometricSourceType.FACE && !mUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - KeyguardUpdateMonitor.getCurrentUser())) { - vibrateError(); + KeyguardUpdateMonitor.getCurrentUser())) + || (biometricSourceType == BiometricSourceType.FINGERPRINT)) { + vibrateError(biometricSourceType); } cleanup(); @@ -688,24 +681,15 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp cleanup(); } - private void vibrateSuccess() { - if (mShouldVibrate) { - mVibratorHelper.vibrate(Process.myUid(), - "com.android.systemui", - SUCCESS_VIBRATION_EFFECT, - getClass().getSimpleName() + "::success", - HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES); - } + //these haptics are for device-entry only + private void vibrateSuccess(BiometricSourceType type) { + mVibratorHelper.vibrateAuthSuccess( + getClass().getSimpleName() + ", type =" + type + "device-entry::success"); } - private void vibrateError() { - if (mShouldVibrate) { - mVibratorHelper.vibrate(Process.myUid(), - "com.android.systemui", - ERROR_VIBRATION_EFFECT, - getClass().getSimpleName() + "::error", - HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES); - } + private void vibrateError(BiometricSourceType type) { + mVibratorHelper.vibrateAuthError( + getClass().getSimpleName() + ", type =" + type + "device-entry::error"); } private void cleanup() { @@ -734,9 +718,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mMode = MODE_NONE; mBiometricType = null; mNotificationShadeWindowController.setForceDozeBrightness(false); - if (mBiometricModeListener != null) { - mBiometricModeListener.onResetMode(); - mBiometricModeListener.notifyBiometricAuthModeChanged(); + for (BiometricModeListener listener : mBiometricModeListeners) { + listener.onResetMode(); } mNumConsecutiveFpFailures = 0; mLastFpFailureUptimeMillis = 0; @@ -845,10 +828,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp /** An interface to interact with the {@link BiometricUnlockController}. */ public interface BiometricModeListener { /** Called when {@code mMode} is reset to {@link #MODE_NONE}. */ - void onResetMode(); + default void onResetMode() {} /** Called when {@code mMode} has changed in {@link #startWakeAndUnlock(int)}. */ - void onModeChanged(@WakeAndUnlockMode int mode); - /** Called after processing {@link #onModeChanged(int)}. */ - void notifyBiometricAuthModeChanged(); + default void onModeChanged(@WakeAndUnlockMode int mode) {} } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index be3052e3b3d1..0ad72ab77bf9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -1527,11 +1527,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { protected void startKeyguard() { Trace.beginSection("CentralSurfaces#startKeyguard"); mBiometricUnlockController = mBiometricUnlockControllerLazy.get(); - mBiometricUnlockController.setBiometricModeListener( + mBiometricUnlockController.addBiometricModeListener( new BiometricUnlockController.BiometricModeListener() { @Override public void onResetMode() { setWakeAndUnlocking(false); + notifyBiometricAuthModeChanged(); } @Override @@ -1542,11 +1543,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { case BiometricUnlockController.MODE_WAKE_AND_UNLOCK: setWakeAndUnlocking(true); } - } - - @Override - public void notifyBiometricAuthModeChanged() { - CentralSurfacesImpl.this.notifyBiometricAuthModeChanged(); + notifyBiometricAuthModeChanged(); } private void setWakeAndUnlocking(boolean wakeAndUnlocking) { @@ -2572,12 +2569,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // ordering. mMainExecutor.execute(mShadeController::runPostCollapseRunnables); } - } else if (mNotificationPanelViewController.isLaunchTransitionFinished()) { - // We are not dismissing the shade, but the launch transition is already - // finished, - // so nobody will call readyForKeyguardDone anymore. Post it such that - // keyguardDonePending gets called first. - mMainExecutor.execute(mStatusBarKeyguardViewManager::readyForKeyguardDone); } return deferred; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java index 86f6ff850409..0a0ded24ef30 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -18,6 +18,7 @@ import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE; import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW; import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI; +import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI_NEW; import android.annotation.Nullable; import android.content.Context; @@ -53,8 +54,9 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter; import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder; import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView; import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel; +import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter; import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView; -import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel; +import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel; import com.android.systemui.util.Assert; import java.util.ArrayList; @@ -84,7 +86,18 @@ public interface StatusBarIconController { /** */ void setIcon(String slot, StatusBarIcon icon); /** */ - void setSignalIcon(String slot, WifiIconState state); + void setWifiIcon(String slot, WifiIconState state); + + /** + * Sets up a wifi icon using the new data pipeline. No effect if the wifi icon has already been + * set up (inflated and added to the view hierarchy). + * + * This method completely replaces {@link #setWifiIcon} with the information from the new wifi + * data pipeline. Icons will automatically keep their state up to date, so we don't have to + * worry about funneling state objects through anymore. + */ + void setNewWifiIcon(); + /** */ void setMobileIcons(String slot, List<MobileIconState> states); @@ -151,14 +164,14 @@ public interface StatusBarIconController { LinearLayout linearLayout, StatusBarLocation location, StatusBarPipelineFlags statusBarPipelineFlags, - WifiViewModel wifiViewModel, + WifiUiAdapter wifiUiAdapter, MobileUiAdapter mobileUiAdapter, MobileContextProvider mobileContextProvider, DarkIconDispatcher darkIconDispatcher) { super(linearLayout, location, statusBarPipelineFlags, - wifiViewModel, + wifiUiAdapter, mobileUiAdapter, mobileContextProvider); mIconHPadding = mContext.getResources().getDimensionPixelSize( @@ -218,7 +231,7 @@ public interface StatusBarIconController { @SysUISingleton public static class Factory { private final StatusBarPipelineFlags mStatusBarPipelineFlags; - private final WifiViewModel mWifiViewModel; + private final WifiUiAdapter mWifiUiAdapter; private final MobileContextProvider mMobileContextProvider; private final MobileUiAdapter mMobileUiAdapter; private final DarkIconDispatcher mDarkIconDispatcher; @@ -226,12 +239,12 @@ public interface StatusBarIconController { @Inject public Factory( StatusBarPipelineFlags statusBarPipelineFlags, - WifiViewModel wifiViewModel, + WifiUiAdapter wifiUiAdapter, MobileContextProvider mobileContextProvider, MobileUiAdapter mobileUiAdapter, DarkIconDispatcher darkIconDispatcher) { mStatusBarPipelineFlags = statusBarPipelineFlags; - mWifiViewModel = wifiViewModel; + mWifiUiAdapter = wifiUiAdapter; mMobileContextProvider = mobileContextProvider; mMobileUiAdapter = mobileUiAdapter; mDarkIconDispatcher = darkIconDispatcher; @@ -242,7 +255,7 @@ public interface StatusBarIconController { group, location, mStatusBarPipelineFlags, - mWifiViewModel, + mWifiUiAdapter, mMobileUiAdapter, mMobileContextProvider, mDarkIconDispatcher); @@ -260,14 +273,14 @@ public interface StatusBarIconController { ViewGroup group, StatusBarLocation location, StatusBarPipelineFlags statusBarPipelineFlags, - WifiViewModel wifiViewModel, + WifiUiAdapter wifiUiAdapter, MobileUiAdapter mobileUiAdapter, MobileContextProvider mobileContextProvider ) { super(group, location, statusBarPipelineFlags, - wifiViewModel, + wifiUiAdapter, mobileUiAdapter, mobileContextProvider); } @@ -302,19 +315,19 @@ public interface StatusBarIconController { @SysUISingleton public static class Factory { private final StatusBarPipelineFlags mStatusBarPipelineFlags; - private final WifiViewModel mWifiViewModel; + private final WifiUiAdapter mWifiUiAdapter; private final MobileContextProvider mMobileContextProvider; private final MobileUiAdapter mMobileUiAdapter; @Inject public Factory( StatusBarPipelineFlags statusBarPipelineFlags, - WifiViewModel wifiViewModel, + WifiUiAdapter wifiUiAdapter, MobileUiAdapter mobileUiAdapter, MobileContextProvider mobileContextProvider ) { mStatusBarPipelineFlags = statusBarPipelineFlags; - mWifiViewModel = wifiViewModel; + mWifiUiAdapter = wifiUiAdapter; mMobileUiAdapter = mobileUiAdapter; mMobileContextProvider = mobileContextProvider; } @@ -324,7 +337,7 @@ public interface StatusBarIconController { group, location, mStatusBarPipelineFlags, - mWifiViewModel, + mWifiUiAdapter, mMobileUiAdapter, mMobileContextProvider); } @@ -336,10 +349,9 @@ public interface StatusBarIconController { */ class IconManager implements DemoModeCommandReceiver { protected final ViewGroup mGroup; - private final StatusBarLocation mLocation; private final StatusBarPipelineFlags mStatusBarPipelineFlags; - private final WifiViewModel mWifiViewModel; private final MobileContextProvider mMobileContextProvider; + private final LocationBasedWifiViewModel mWifiViewModel; private final MobileIconsViewModel mMobileIconsViewModel; protected final Context mContext; @@ -359,26 +371,33 @@ public interface StatusBarIconController { ViewGroup group, StatusBarLocation location, StatusBarPipelineFlags statusBarPipelineFlags, - WifiViewModel wifiViewModel, + WifiUiAdapter wifiUiAdapter, MobileUiAdapter mobileUiAdapter, MobileContextProvider mobileContextProvider ) { mGroup = group; - mLocation = location; mStatusBarPipelineFlags = statusBarPipelineFlags; - mWifiViewModel = wifiViewModel; mMobileContextProvider = mobileContextProvider; mContext = group.getContext(); mIconSize = mContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.status_bar_icon_size); - if (statusBarPipelineFlags.useNewMobileIcons()) { - // This starts the flow for the new pipeline, and will notify us of changes + if (statusBarPipelineFlags.runNewMobileIconsBackend()) { + // This starts the flow for the new pipeline, and will notify us of changes if + // {@link StatusBarPipelineFlags#useNewMobileIcons} is also true. mMobileIconsViewModel = mobileUiAdapter.createMobileIconsViewModel(); MobileIconsBinder.bind(mGroup, mMobileIconsViewModel); } else { mMobileIconsViewModel = null; } + + if (statusBarPipelineFlags.runNewWifiIconBackend()) { + // This starts the flow for the new pipeline, and will notify us of changes if + // {@link StatusBarPipelineFlags#useNewWifiIcon} is also true. + mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, location); + } else { + mWifiViewModel = null; + } } public boolean isDemoable() { @@ -429,6 +448,9 @@ public interface StatusBarIconController { case TYPE_WIFI: return addWifiIcon(index, slot, holder.getWifiState()); + case TYPE_WIFI_NEW: + return addNewWifiIcon(index, slot); + case TYPE_MOBILE: return addMobileIcon(index, slot, holder.getMobileState()); @@ -450,16 +472,13 @@ public interface StatusBarIconController { @VisibleForTesting protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) { - final BaseStatusBarFrameLayout view; if (mStatusBarPipelineFlags.useNewWifiIcon()) { - view = onCreateModernStatusBarWifiView(slot); - // When [ModernStatusBarWifiView] is created, it will automatically apply the - // correct view state so we don't need to call applyWifiState. - } else { - StatusBarWifiView wifiView = onCreateStatusBarWifiView(slot); - wifiView.applyWifiState(state); - view = wifiView; + throw new IllegalStateException("Attempting to add a mobile icon while the new " + + "icons are enabled is not supported"); } + + final StatusBarWifiView view = onCreateStatusBarWifiView(slot); + view.applyWifiState(state); mGroup.addView(view, index, onCreateLayoutParams()); if (mIsInDemoMode) { @@ -468,6 +487,17 @@ public interface StatusBarIconController { return view; } + protected StatusIconDisplayable addNewWifiIcon(int index, String slot) { + if (!mStatusBarPipelineFlags.useNewWifiIcon()) { + throw new IllegalStateException("Attempting to add a wifi icon using the new" + + "pipeline, but the enabled flag is false."); + } + + ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot); + mGroup.addView(view, index, onCreateLayoutParams()); + return view; + } + @VisibleForTesting protected StatusIconDisplayable addMobileIcon( int index, @@ -523,8 +553,7 @@ public interface StatusBarIconController { } private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) { - return ModernStatusBarWifiView.constructAndBind( - mContext, slot, mWifiViewModel, mLocation); + return ModernStatusBarWifiView.constructAndBind(mContext, slot, mWifiViewModel); } private StatusBarMobileView onCreateStatusBarMobileView(int subId, String slot) { @@ -600,7 +629,8 @@ public interface StatusBarIconController { onSetMobileIcon(viewIndex, holder.getMobileState()); return; case TYPE_MOBILE_NEW: - // Nothing, the icon updates itself now + case TYPE_WIFI_NEW: + // Nothing, the new icons update themselves return; default: break; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java index 31e960ad7d69..674e5747e331 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java @@ -195,12 +195,13 @@ public class StatusBarIconControllerImpl implements Tunable, } } - /** - * Signal icons need to be handled differently, because they can be - * composite views - */ @Override - public void setSignalIcon(String slot, WifiIconState state) { + public void setWifiIcon(String slot, WifiIconState state) { + if (mStatusBarPipelineFlags.useNewWifiIcon()) { + Log.d(TAG, "ignoring old pipeline callback because the new wifi icon is enabled"); + return; + } + if (state == null) { removeIcon(slot, 0); return; @@ -216,6 +217,24 @@ public class StatusBarIconControllerImpl implements Tunable, } } + + @Override + public void setNewWifiIcon() { + if (!mStatusBarPipelineFlags.useNewWifiIcon()) { + Log.d(TAG, "ignoring new pipeline callback because the new wifi icon is disabled"); + return; + } + + String slot = mContext.getString(com.android.internal.R.string.status_bar_wifi); + StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, /* tag= */ 0); + if (holder == null) { + holder = StatusBarIconHolder.forNewWifiIcon(); + setIcon(slot, holder); + } else { + // Don't have to do anything in the new world + } + } + /** * Accept a list of MobileIconStates, which all live in the same slot(?!), and then are sorted * by subId. Don't worry this definitely makes sense and works. @@ -225,7 +244,7 @@ public class StatusBarIconControllerImpl implements Tunable, @Override public void setMobileIcons(String slot, List<MobileIconState> iconStates) { if (mStatusBarPipelineFlags.useNewMobileIcons()) { - Log.d(TAG, "ignoring old pipeline callbacks, because the new " + Log.d(TAG, "ignoring old pipeline callbacks, because the new mobile " + "icons are enabled"); return; } @@ -251,10 +270,11 @@ public class StatusBarIconControllerImpl implements Tunable, public void setNewMobileIconSubIds(List<Integer> subIds) { if (!mStatusBarPipelineFlags.useNewMobileIcons()) { Log.d(TAG, "ignoring new pipeline callback, " - + "since the new icons are disabled"); + + "since the new mobile icons are disabled"); return; } - Slot mobileSlot = mStatusBarIconList.getSlot("mobile"); + String slotName = mContext.getString(com.android.internal.R.string.status_bar_mobile); + Slot mobileSlot = mStatusBarIconList.getSlot(slotName); Collections.reverse(subIds); @@ -262,7 +282,7 @@ public class StatusBarIconControllerImpl implements Tunable, StatusBarIconHolder holder = mobileSlot.getHolderForTag(subId); if (holder == null) { holder = StatusBarIconHolder.fromSubIdForModernMobileIcon(subId); - setIcon("mobile", holder); + setIcon(slotName, holder); } else { // Don't have to do anything in the new world } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java index 68a203e30f98..f6c0da8da8c0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java @@ -51,11 +51,24 @@ public class StatusBarIconHolder { @Deprecated public static final int TYPE_MOBILE_NEW = 3; + /** + * TODO (b/238425913): address this once the new pipeline is in place + * This type exists so that the new wifi pipeline can be used to inform the old view system + * about the existence of the wifi icon. The design of the new pipeline should allow for removal + * of this icon holder type, and obsolete the need for this entire class. + * + * @deprecated This field only exists so the new status bar pipeline can interface with the + * view holder system. + */ + @Deprecated + public static final int TYPE_WIFI_NEW = 4; + @IntDef({ TYPE_ICON, TYPE_WIFI, TYPE_MOBILE, - TYPE_MOBILE_NEW + TYPE_MOBILE_NEW, + TYPE_WIFI_NEW }) @Retention(RetentionPolicy.SOURCE) @interface IconType {} @@ -95,6 +108,13 @@ public class StatusBarIconHolder { return holder; } + /** Creates a new holder with for the new wifi icon. */ + public static StatusBarIconHolder forNewWifiIcon() { + StatusBarIconHolder holder = new StatusBarIconHolder(); + holder.mType = TYPE_WIFI_NEW; + return holder; + } + /** */ public static StatusBarIconHolder fromMobileIconState(MobileIconState state) { StatusBarIconHolder holder = new StatusBarIconHolder(); @@ -172,9 +192,10 @@ public class StatusBarIconHolder { case TYPE_MOBILE: return mMobileState.visible; case TYPE_MOBILE_NEW: - //TODO (b/249790733), the new pipeline can control visibility via the ViewModel + case TYPE_WIFI_NEW: + // The new pipeline controls visibilities via the view model and view binder, so + // this is effectively an unused return value. return true; - default: return true; } @@ -199,7 +220,9 @@ public class StatusBarIconHolder { break; case TYPE_MOBILE_NEW: - //TODO (b/249790733), the new pipeline can control visibility via the ViewModel + case TYPE_WIFI_NEW: + // The new pipeline controls visibilities via the view model and view binder, so + // ignore setVisible. break; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index a00e75642f55..9e2821806693 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -476,7 +476,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } else if (mKeyguardStateController.isShowing() && !hideBouncerOverDream) { if (!isWakeAndUnlocking() && !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER) - && !mNotificationPanelViewController.isLaunchTransitionFinished() && !isUnlockCollapsing()) { if (mBouncer != null) { mBouncer.setExpansion(fraction); @@ -845,21 +844,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (isShowing && isOccluding) { SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED, SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__OCCLUDED); - if (mNotificationPanelViewController.isLaunchTransitionFinished()) { - final Runnable endRunnable = new Runnable() { - @Override - public void run() { - mNotificationShadeWindowController.setKeyguardOccluded(isOccluded); - reset(true /* hideBouncerWhenShowing */); - } - }; - mCentralSurfaces.fadeKeyguardAfterLaunchTransition( - null /* beforeFading */, - endRunnable, - endRunnable); - return; - } - if (mCentralSurfaces.isLaunchingActivityOverLockscreen()) { // When isLaunchingActivityOverLockscreen() is true, we know for sure that the post // collapse runnables will be run. @@ -931,8 +915,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb long uptimeMillis = SystemClock.uptimeMillis(); long delay = Math.max(0, startTime + HIDE_TIMING_CORRECTION_MS - uptimeMillis); - if (mNotificationPanelViewController.isLaunchTransitionFinished() - || mKeyguardStateController.isFlingingToDismissKeyguard()) { + if (mKeyguardStateController.isFlingingToDismissKeyguard()) { final boolean wasFlingingToDismissKeyguard = mKeyguardStateController.isFlingingToDismissKeyguard(); mCentralSurfaces.fadeKeyguardAfterLaunchTransition(new Runnable() { @@ -1309,7 +1292,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public boolean shouldDisableWindowAnimationsForUnlock() { - return mNotificationPanelViewController.isLaunchTransitionFinished(); + return false; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index 5cd2ba1b1cf3..b6ae4a088880 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -23,7 +23,6 @@ import static com.android.systemui.statusbar.phone.CentralSurfaces.getActivityOp import android.app.ActivityManager; import android.app.KeyguardManager; import android.app.Notification; -import android.app.NotificationManager; import android.app.PendingIntent; import android.app.TaskStackBuilder; import android.content.Context; @@ -60,9 +59,8 @@ import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider; -import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -126,7 +124,6 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte Context context, Handler mainThreadHandler, Executor uiBgExecutor, - NotifPipeline notifPipeline, NotificationVisibilityProvider visibilityProvider, HeadsUpManagerPhone headsUpManager, ActivityStarter activityStarter, @@ -151,7 +148,8 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte NotificationPresenter presenter, NotificationPanelViewController panel, ActivityLaunchAnimator activityLaunchAnimator, - NotificationLaunchAnimatorControllerProvider notificationAnimationProvider) { + NotificationLaunchAnimatorControllerProvider notificationAnimationProvider, + LaunchFullScreenIntentProvider launchFullScreenIntentProvider) { mContext = context; mMainThreadHandler = mainThreadHandler; mUiBgExecutor = uiBgExecutor; @@ -182,12 +180,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte mActivityLaunchAnimator = activityLaunchAnimator; mNotificationAnimationProvider = notificationAnimationProvider; - notifPipeline.addCollectionListener(new NotifCollectionListener() { - @Override - public void onEntryAdded(NotificationEntry entry) { - handleFullScreenIntent(entry); - } - }); + launchFullScreenIntentProvider.registerListener(entry -> launchFullScreenIntent(entry)); } /** @@ -549,38 +542,36 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte } @VisibleForTesting - void handleFullScreenIntent(NotificationEntry entry) { - if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) { - if (shouldSuppressFullScreenIntent(entry)) { - mLogger.logFullScreenIntentSuppressedByDnD(entry); - } else if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) { - mLogger.logFullScreenIntentNotImportantEnough(entry); - } else { - // Stop screensaver if the notification has a fullscreen intent. - // (like an incoming phone call) - mUiBgExecutor.execute(() -> { - try { - mDreamManager.awaken(); - } catch (RemoteException e) { - e.printStackTrace(); - } - }); + void launchFullScreenIntent(NotificationEntry entry) { + // Skip if device is in VR mode. + if (mPresenter.isDeviceInVrMode()) { + mLogger.logFullScreenIntentSuppressedByVR(entry); + return; + } - // not immersive & a fullscreen alert should be shown - final PendingIntent fullscreenIntent = - entry.getSbn().getNotification().fullScreenIntent; - mLogger.logSendingFullScreenIntent(entry, fullscreenIntent); - try { - EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION, - entry.getKey()); - mCentralSurfaces.wakeUpForFullScreenIntent(); - fullscreenIntent.send(); - entry.notifyFullScreenIntentLaunched(); - mMetricsLogger.count("note_fullscreen", 1); - } catch (PendingIntent.CanceledException e) { - // ignore - } + // Stop screensaver if the notification has a fullscreen intent. + // (like an incoming phone call) + mUiBgExecutor.execute(() -> { + try { + mDreamManager.awaken(); + } catch (RemoteException e) { + e.printStackTrace(); } + }); + + // not immersive & a fullscreen alert should be shown + final PendingIntent fullscreenIntent = + entry.getSbn().getNotification().fullScreenIntent; + mLogger.logSendingFullScreenIntent(entry, fullscreenIntent); + try { + EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION, + entry.getKey()); + mCentralSurfaces.wakeUpForFullScreenIntent(); + fullscreenIntent.send(); + entry.notifyFullScreenIntentLaunched(); + mMetricsLogger.count("note_fullscreen", 1); + } catch (PendingIntent.CanceledException e) { + // ignore } } @@ -607,12 +598,4 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte mMainThreadHandler.post(mShadeController::collapsePanel); } } - - private boolean shouldSuppressFullScreenIntent(NotificationEntry entry) { - if (mPresenter.isDeviceInVrMode()) { - return true; - } - - return entry.shouldSuppressFullScreenIntent(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt index 81edff45c505..1f0b96a58da6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt @@ -96,19 +96,11 @@ class StatusBarNotificationActivityStarterLogger @Inject constructor( }) } - fun logFullScreenIntentSuppressedByDnD(entry: NotificationEntry) { + fun logFullScreenIntentSuppressedByVR(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { str1 = entry.logKey }, { - "No Fullscreen intent: suppressed by DND: $str1" - }) - } - - fun logFullScreenIntentNotImportantEnough(entry: NotificationEntry) { - buffer.log(TAG, DEBUG, { - str1 = entry.logKey - }, { - "No Fullscreen intent: not important enough: $str1" + "No Fullscreen intent: suppressed by VR mode: $str1" }) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java index 492734e93dca..de7bf3c021dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java @@ -212,7 +212,7 @@ public class StatusBarSignalPolicy implements SignalCallback, private void updateWifiIconWithState(WifiIconState state) { if (DEBUG) Log.d(TAG, "WifiIconState: " + state == null ? "" : state.toString()); if (state.visible && state.resId > 0) { - mIconController.setSignalIcon(mSlotWifi, state); + mIconController.setWifiIcon(mSlotWifi, state); mIconController.setIconVisibility(mSlotWifi, true); } else { mIconController.setIconVisibility(mSlotWifi, false); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt index 06cd12dd1a0d..946d7e4a3e75 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt @@ -27,11 +27,26 @@ class StatusBarPipelineFlags @Inject constructor(private val featureFlags: Featu /** True if we should display the mobile icons using the new status bar data pipeline. */ fun useNewMobileIcons(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS) + /** + * True if we should run the new mobile icons backend to get the logging. + * + * Does *not* affect whether we render the mobile icons using the new backend data. See + * [useNewMobileIcons] for that. + */ + fun runNewMobileIconsBackend(): Boolean = + featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS_BACKEND) || useNewMobileIcons() + /** True if we should display the wifi icon using the new status bar data pipeline. */ fun useNewWifiIcon(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON) - // TODO(b/238425913): Add flags to only run the mobile backend or wifi backend so we get the - // logging without getting the UI effects. + /** + * True if we should run the new wifi icon backend to get the logging. + * + * Does *not* affect whether we render the wifi icon using the new backend data. See + * [useNewWifiIcon] for that. + */ + fun runNewWifiIconBackend(): Boolean = + featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON_BACKEND) || useNewWifiIcon() /** * Returns true if we should apply some coloring to the wifi icon that was rendered with the new diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt index 380017cd3418..c7e0ce173ece 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt @@ -20,6 +20,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.statusbar.phone.StatusBarIconController import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager +import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel import javax.inject.Inject @@ -50,6 +51,7 @@ constructor( private val iconController: StatusBarIconController, private val iconsViewModelFactory: MobileIconsViewModel.Factory, @Application scope: CoroutineScope, + private val statusBarPipelineFlags: StatusBarPipelineFlags, ) { private val mobileSubIds: Flow<List<Int>> = interactor.filteredSubscriptions.mapLatest { infos -> @@ -66,8 +68,14 @@ constructor( private val mobileSubIdsState: StateFlow<List<Int>> = mobileSubIds .onEach { - // Notify the icon controller here so that it knows to add icons - iconController.setNewMobileIconSubIds(it) + // Only notify the icon controller if we want to *render* the new icons. + // Note that this flow may still run if + // [statusBarPipelineFlags.runNewMobileIconsBackend] is true because we may want to + // get the logging data without rendering. + if (statusBarPipelineFlags.useNewMobileIcons()) { + // Notify the icon controller here so that it knows to add icons + iconController.setNewMobileIconSubIds(it) + } } .stateIn(scope, SharingStarted.WhileSubscribed(), listOf()) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt new file mode 100644 index 000000000000..b816364ed4cf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.wifi.ui + +import android.view.ViewGroup +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.statusbar.phone.StatusBarIconController +import com.android.systemui.statusbar.phone.StatusBarLocation +import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags +import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel +import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel +import javax.inject.Inject +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch + +/** + * This class serves as a bridge between the old UI classes and the new data pipeline. + * + * Once the new pipeline notifies [wifiViewModel] that the wifi icon should be visible, this class + * notifies [iconController] to inflate the wifi icon (if needed). After that, the [wifiViewModel] + * has sole responsibility for updating the wifi icon drawable, visibility, etc. and the + * [iconController] will not do any updates to the icon. + */ +@SysUISingleton +class WifiUiAdapter +@Inject +constructor( + private val iconController: StatusBarIconController, + private val wifiViewModel: WifiViewModel, + private val statusBarPipelineFlags: StatusBarPipelineFlags, +) { + /** + * Binds the container for all the status bar icons to a view model, so that we inflate the wifi + * view once we receive a valid icon from the data pipeline. + * + * NOTE: This should go away as we better integrate the data pipeline with the UI. + * + * @return the view model used for this particular group in the given [location]. + */ + fun bindGroup( + statusBarIconGroup: ViewGroup, + location: StatusBarLocation, + ): LocationBasedWifiViewModel { + val locationViewModel = + when (location) { + StatusBarLocation.HOME -> wifiViewModel.home + StatusBarLocation.KEYGUARD -> wifiViewModel.keyguard + StatusBarLocation.QS -> wifiViewModel.qs + } + + statusBarIconGroup.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + locationViewModel.wifiIcon.collect { wifiIcon -> + // Only notify the icon controller if we want to *render* the new icon. + // Note that this flow may still run if + // [statusBarPipelineFlags.runNewWifiIconBackend] is true because we may + // want to get the logging data without rendering. + if (wifiIcon != null && statusBarPipelineFlags.useNewWifiIcon()) { + iconController.setNewWifiIcon() + } + } + } + } + } + + return locationViewModel + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt index 25537b948517..345f8cb75660 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt @@ -30,9 +30,7 @@ import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON -import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel -import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collect @@ -62,26 +60,9 @@ object WifiViewBinder { fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) } - /** - * Binds the view to the appropriate view-model based on the given location. The view will - * continue to be updated following updates from the view-model. - */ - @JvmStatic - fun bind( - view: ViewGroup, - wifiViewModel: WifiViewModel, - location: StatusBarLocation, - ): Binding { - return when (location) { - StatusBarLocation.HOME -> bind(view, wifiViewModel.home) - StatusBarLocation.KEYGUARD -> bind(view, wifiViewModel.keyguard) - StatusBarLocation.QS -> bind(view, wifiViewModel.qs) - } - } - /** Binds the view to the view-model, continuing to update the former based on the latter. */ @JvmStatic - private fun bind( + fun bind( view: ViewGroup, viewModel: LocationBasedWifiViewModel, ): Binding { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt index 0cd9bd7d97b0..a45076b53356 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt @@ -26,9 +26,8 @@ import com.android.systemui.statusbar.BaseStatusBarFrameLayout import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN -import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder -import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel +import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel /** * A new and more modern implementation of [com.android.systemui.statusbar.StatusBarWifiView] that @@ -81,12 +80,11 @@ class ModernStatusBarWifiView( private fun initView( slotName: String, - wifiViewModel: WifiViewModel, - location: StatusBarLocation, + wifiViewModel: LocationBasedWifiViewModel, ) { slot = slotName initDotView() - binding = WifiViewBinder.bind(this, wifiViewModel, location) + binding = WifiViewBinder.bind(this, wifiViewModel) } // Mostly duplicated from [com.android.systemui.statusbar.StatusBarWifiView]. @@ -116,14 +114,13 @@ class ModernStatusBarWifiView( fun constructAndBind( context: Context, slot: String, - wifiViewModel: WifiViewModel, - location: StatusBarLocation, + wifiViewModel: LocationBasedWifiViewModel, ): ModernStatusBarWifiView { return ( LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null) as ModernStatusBarWifiView ).also { - it.initView(slot, wifiViewModel, location) + it.initView(slot, wifiViewModel) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt index 89b96b7bc75d..0782bbb774eb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt @@ -145,7 +145,8 @@ constructor( else -> null } } - .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null) + .logOutputChange(logger, "icon") { icon -> icon?.contentDescription.toString() } + .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null) /** The wifi activity status. Null if we shouldn't display the activity status. */ private val activity: Flow<WifiActivityModel?> = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java index 1d414745e6ed..7acdaffb48c4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java @@ -118,7 +118,10 @@ public final class DeviceStateRotationLockSettingController private void updateDeviceState(int state) { Log.v(TAG, "updateDeviceState [state=" + state + "]"); - Trace.beginSection("updateDeviceState [state=" + state + "]"); + if (Trace.isEnabled()) { + Trace.traceBegin( + Trace.TRACE_TAG_APP, "updateDeviceState [state=" + state + "]"); + } try { if (mDeviceState == state) { return; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java index bd2123a251c3..69b55c81f48b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java @@ -33,6 +33,7 @@ import android.util.Log; import androidx.annotation.NonNull; import com.android.internal.util.ConcurrentUtils; +import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -63,6 +64,7 @@ public class HotspotControllerImpl implements HotspotController, WifiManager.Sof private volatile int mNumConnectedDevices; // Assume tethering is available until told otherwise private volatile boolean mIsTetheringSupported = true; + private final boolean mIsTetheringSupportedConfig; private volatile boolean mHasTetherableWifiRegexs = true; private boolean mWaitingForTerminalState; @@ -100,23 +102,29 @@ public class HotspotControllerImpl implements HotspotController, WifiManager.Sof mTetheringManager = context.getSystemService(TetheringManager.class); mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); mMainHandler = mainHandler; - mTetheringManager.registerTetheringEventCallback( - new HandlerExecutor(backgroundHandler), mTetheringCallback); + mIsTetheringSupportedConfig = context.getResources() + .getBoolean(R.bool.config_show_wifi_tethering); + if (mIsTetheringSupportedConfig) { + mTetheringManager.registerTetheringEventCallback( + new HandlerExecutor(backgroundHandler), mTetheringCallback); + } dumpManager.registerDumpable(getClass().getSimpleName(), this); } /** * Whether hotspot is currently supported. * - * This will return {@code true} immediately on creation of the controller, but may be updated - * later. Callbacks from this controllers will notify if the state changes. + * This may return {@code true} immediately on creation of the controller, but may be updated + * later as capabilities are collected from System Server. + * + * Callbacks from this controllers will notify if the state changes. * * @return {@code true} if hotspot is supported (or we haven't been told it's not) * @see #addCallback */ @Override public boolean isHotspotSupported() { - return mIsTetheringSupported && mHasTetherableWifiRegexs + return mIsTetheringSupportedConfig && mIsTetheringSupported && mHasTetherableWifiRegexs && UserManager.get(mContext).isUserAdmin(ActivityManager.getCurrentUser()); } diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt index 07e5cf9d9df2..208061433325 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt @@ -28,6 +28,8 @@ import android.util.Log import android.view.WindowManagerGlobal import android.widget.Toast import com.android.internal.logging.UiEventLogger +import com.android.systemui.GuestResetOrExitSessionReceiver +import com.android.systemui.GuestResumeSessionReceiver import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background @@ -58,6 +60,8 @@ constructor( private val devicePolicyManager: DevicePolicyManager, private val refreshUsersScheduler: RefreshUsersScheduler, private val uiEventLogger: UiEventLogger, + resumeSessionReceiver: GuestResumeSessionReceiver, + resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver, ) { /** Whether the device is configured to always have a guest user available. */ val isGuestUserAutoCreated: Boolean = repository.isGuestUserAutoCreated @@ -65,6 +69,11 @@ constructor( /** Whether the guest user is currently being reset. */ val isGuestUserResetting: Boolean = repository.isGuestUserResetting + init { + resumeSessionReceiver.register() + resetOrExitSessionReceiver.register() + } + /** Notifies that the device has finished booting. */ fun onDeviceBootCompleted() { applicationScope.launch { diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index 4e7751464564..a4384d5810ce 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -25,6 +25,7 @@ import static android.service.notification.NotificationListenerService.REASON_GR import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE; import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL; +import static com.android.systemui.flags.Flags.WM_BUBBLE_BAR; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; @@ -51,6 +52,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.model.SysUiState; import com.android.systemui.shade.ShadeController; import com.android.systemui.shared.system.QuickStepContract; @@ -129,6 +131,7 @@ public class BubblesManager { CommonNotifCollection notifCollection, NotifPipeline notifPipeline, SysUiState sysUiState, + FeatureFlags featureFlags, Executor sysuiMainExecutor) { if (bubblesOptional.isPresent()) { return new BubblesManager(context, @@ -146,6 +149,7 @@ public class BubblesManager { notifCollection, notifPipeline, sysUiState, + featureFlags, sysuiMainExecutor); } else { return null; @@ -168,6 +172,7 @@ public class BubblesManager { CommonNotifCollection notifCollection, NotifPipeline notifPipeline, SysUiState sysUiState, + FeatureFlags featureFlags, Executor sysuiMainExecutor) { mContext = context; mBubbles = bubbles; @@ -352,6 +357,7 @@ public class BubblesManager { }); } }; + mBubbles.setBubbleBarEnabled(featureFlags.isEnabled(WM_BUBBLE_BAR)); mBubbles.setSysuiProxy(mSysuiProxy); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt index aca60c033bac..131cf7d33e3a 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt @@ -72,6 +72,7 @@ private fun fingerprintModel(user: Int) = KeyguardFingerprintListenModel( keyguardOccluded = false, occludingAppRequestingFp = false, primaryUser = false, + shouldListenSfpsState = false, shouldListenForFingerprintAssistant = false, switchingUser = false, udfps = false, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index 52f8ef8b7ebc..0a2b3d8498c4 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -194,6 +194,15 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { } @Test + public void onInitConfiguresViewMode() { + mKeyguardSecurityContainerController.onInit(); + verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager), + eq(mUserSwitcherController), + any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class), + eq(mFalsingA11yDelegate)); + } + + @Test public void showSecurityScreen_canInflateAllModes() { SecurityMode[] modes = SecurityMode.values(); for (SecurityMode mode : modes) { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 5104f84f3bc6..a8284d29197d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -20,6 +20,7 @@ import static android.app.StatusBarManager.SESSION_KEYGUARD; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT; +import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON; import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE; import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID; @@ -38,6 +39,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -53,6 +55,7 @@ import android.app.trust.IStrongAuthTracker; import android.app.trust.TrustManager; import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -60,18 +63,21 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; +import android.database.ContentObserver; import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.biometrics.SensorProperties; import android.hardware.face.FaceManager; import android.hardware.face.FaceSensorProperties; import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.net.Uri; import android.nfc.NfcAdapter; import android.os.Bundle; import android.os.CancellationSignal; @@ -110,6 +116,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.settings.GlobalSettings; +import com.android.systemui.util.settings.SecureSettings; import org.junit.After; import org.junit.Assert; @@ -181,6 +188,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock + private SecureSettings mSecureSettings; + @Mock private TelephonyManager mTelephonyManager; @Mock private SensorPrivacyManager mSensorPrivacyManager; @@ -214,6 +223,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { private GlobalSettings mGlobalSettings; private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig; + private final int mCurrentUserId = 100; private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0); @@ -223,6 +233,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Captor private ArgumentCaptor<FaceManager.AuthenticationCallback> mAuthenticationCallbackCaptor; + @Mock + private Uri mURI; + // Direct executor private final Executor mBackgroundExecutor = Runnable::run; private final Executor mMainExecutor = Runnable::run; @@ -305,6 +318,15 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mTestableLooper = TestableLooper.get(this); allowTestableLooperAsMainThread(); + + when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI); + + final ContentResolver contentResolver = mContext.getContentResolver(); + ExtendedMockito.spyOn(contentResolver); + doNothing().when(contentResolver) + .registerContentObserver(any(Uri.class), anyBoolean(), any(ContentObserver.class), + anyInt()); + mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext); verify(mBiometricManager) @@ -1136,6 +1158,64 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void testStartsListeningForSfps_whenKeyguardIsVisible_ifRequireScreenOnToAuthEnabled() + throws RemoteException { + // SFPS supported and enrolled + final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>(); + props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON)); + when(mAuthController.getSfpsProps()).thenReturn(props); + when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); + + // WHEN require screen on to auth is disabled, and keyguard is not awake + when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(0); + mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref(); + + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_requireScreenOnToAuthEnabled, true); + + // Preconditions for sfps auth to run + keyguardNotGoingAway(); + currentUserIsPrimary(); + currentUserDoesNotHaveTrust(); + biometricsNotDisabledThroughDevicePolicyManager(); + biometricsEnabledForCurrentUser(); + userNotCurrentlySwitching(); + + statusBarShadeIsLocked(); + mTestableLooper.processAllMessages(); + + // THEN we should listen for sfps when screen off, because require screen on is disabled + assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue(); + + // WHEN require screen on to auth is enabled, and keyguard is not awake + when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(1); + mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref(); + + // THEN we shouldn't listen for sfps when screen off, because require screen on is enabled + assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse(); + + // Device now awake & keyguard is now interactive + deviceNotGoingToSleep(); + deviceIsInteractive(); + keyguardIsVisible(); + + // THEN we should listen for sfps when screen on, and require screen on is enabled + assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue(); + } + + + private FingerprintSensorPropertiesInternal newFingerprintSensorPropertiesInternal( + @FingerprintSensorProperties.SensorType int sensorType) { + return new FingerprintSensorPropertiesInternal( + 0 /* sensorId */, + SensorProperties.STRENGTH_STRONG, + 1 /* maxEnrollmentsPerUser */, + new ArrayList<ComponentInfoInternal>(), + sensorType, + true /* resetLockoutRequiresHardwareAuthToken */); + } + + @Test public void testShouldNotListenForUdfps_whenTrustEnabled() { // GIVEN a "we should listen for udfps" state mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD); @@ -1804,7 +1884,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { protected TestableKeyguardUpdateMonitor(Context context) { super(context, TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(), - mBroadcastDispatcher, mDumpManager, + mBroadcastDispatcher, mSecureSettings, mDumpManager, mBackgroundExecutor, mMainExecutor, mStatusBarStateController, mLockPatternUtils, mAuthController, mTelephonyListenerManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index e8c760c3e140..d1107c612977 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -35,6 +35,8 @@ import android.view.WindowInsets import android.view.WindowManager import android.widget.ScrollView import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn +import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn import com.android.internal.jank.InteractionJankMonitor import com.android.internal.widget.LockPatternUtils import com.android.systemui.R @@ -159,6 +161,35 @@ class AuthContainerViewTest : SysuiTestCase() { } @Test + fun testDismissesOnFocusLoss_hidesKeyboardWhenVisible() { + val container = initializeFingerprintContainer( + authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL + ) + waitForIdleSync() + + val requestID = authContainer?.requestId ?: 0L + + // Simulate keyboard was shown on the credential view + val windowInsetsController = container.windowInsetsController + spyOn(windowInsetsController) + spyOn(container.rootWindowInsets) + doReturn(true).`when`(container.rootWindowInsets).isVisible(WindowInsets.Type.ime()) + + container.onWindowFocusChanged(false) + waitForIdleSync() + + // Expect hiding IME request will be invoked when dismissing the view + verify(windowInsetsController)?.hide(WindowInsets.Type.ime()) + + verify(callback).onDismissed( + eq(AuthDialogCallback.DISMISSED_USER_CANCELED), + eq<ByteArray?>(null), /* credentialAttestation */ + eq(requestID) + ) + assertThat(container.parent).isNull() + } + + @Test fun testActionAuthenticated_sendsDismissedAuthenticated() { val container = initializeFingerprintContainer() container.mBiometricCallback.onAction( diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java index a02dfa3935d7..a275c8d33751 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -87,6 +87,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.Execution; import com.android.systemui.util.concurrency.FakeExecution; @@ -173,6 +174,9 @@ public class AuthControllerTest extends SysuiTestCase { private DelayableExecutor mBackgroundExecutor; private TestableAuthController mAuthController; + @Mock + private VibratorHelper mVibratorHelper; + @Before public void setup() throws RemoteException { mContextSpy = spy(mContext); @@ -221,7 +225,8 @@ public class AuthControllerTest extends SysuiTestCase { mAuthController = new TestableAuthController(mContextSpy, mExecution, mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager, - () -> mUdfpsController, () -> mSidefpsController, mStatusBarStateController); + () -> mUdfpsController, () -> mSidefpsController, mStatusBarStateController, + mVibratorHelper); mAuthController.start(); verify(mFingerprintManager).addAuthenticatorsRegisteredCallback( @@ -246,11 +251,13 @@ public class AuthControllerTest extends SysuiTestCase { // This test is sensitive to prior FingerprintManager interactions. reset(mFingerprintManager); + when(mVibratorHelper.hasVibrator()).thenReturn(true); + // This test requires an uninitialized AuthController. AuthController authController = new TestableAuthController(mContextSpy, mExecution, mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager, () -> mUdfpsController, () -> mSidefpsController, - mStatusBarStateController); + mStatusBarStateController, mVibratorHelper); authController.start(); verify(mFingerprintManager).addAuthenticatorsRegisteredCallback( @@ -270,11 +277,13 @@ public class AuthControllerTest extends SysuiTestCase { // This test is sensitive to prior FingerprintManager interactions. reset(mFingerprintManager); + when(mVibratorHelper.hasVibrator()).thenReturn(true); + // This test requires an uninitialized AuthController. AuthController authController = new TestableAuthController(mContextSpy, mExecution, mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager, () -> mUdfpsController, () -> mSidefpsController, - mStatusBarStateController); + mStatusBarStateController, mVibratorHelper); authController.start(); verify(mFingerprintManager).addAuthenticatorsRegisteredCallback( @@ -928,12 +937,13 @@ public class AuthControllerTest extends SysuiTestCase { FaceManager faceManager, Provider<UdfpsController> udfpsControllerFactory, Provider<SidefpsController> sidefpsControllerFactory, - StatusBarStateController statusBarStateController) { + StatusBarStateController statusBarStateController, + VibratorHelper vibratorHelper) { super(context, execution, commandQueue, activityTaskManager, windowManager, fingerprintManager, faceManager, udfpsControllerFactory, sidefpsControllerFactory, mDisplayManager, mWakefulnessLifecycle, mUserManager, mLockPatternUtils, statusBarStateController, - mInteractionJankMonitor, mHandler, mBackgroundExecutor); + mInteractionJankMonitor, mHandler, mBackgroundExecutor, vibratorHelper); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java index 6bc7308a6a40..f8579fff488b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java @@ -39,6 +39,8 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.testing.FakeMetricsLogger; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingDataProvider.GestureFinalizedListener; +import com.android.systemui.flags.FakeFeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -66,6 +68,8 @@ public class BrightLineClassifierTest extends SysuiTestCase { @Mock private SingleTapClassifier mSingleTapClassfier; @Mock + private LongTapClassifier mLongTapClassifier; + @Mock private DoubleTapClassifier mDoubleTapClassifier; @Mock private FalsingClassifier mClassifierA; @@ -80,6 +84,7 @@ public class BrightLineClassifierTest extends SysuiTestCase { private AccessibilityManager mAccessibilityManager; private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); + private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags(); private final FalsingClassifier.Result mFalsedResult = FalsingClassifier.Result.falsed(1, getClass().getSimpleName(), ""); @@ -94,6 +99,7 @@ public class BrightLineClassifierTest extends SysuiTestCase { when(mClassifierB.classifyGesture(anyInt(), anyDouble(), anyDouble())) .thenReturn(mPassedResult); when(mSingleTapClassfier.isTap(any(List.class), anyDouble())).thenReturn(mPassedResult); + when(mLongTapClassifier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult); when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble())) .thenReturn(mPassedResult); mClassifiers.add(mClassifierA); @@ -101,9 +107,9 @@ public class BrightLineClassifierTest extends SysuiTestCase { when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList); when(mKeyguardStateController.isShowing()).thenReturn(true); mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider, - mMetricsLogger, mClassifiers, mSingleTapClassfier, mDoubleTapClassifier, - mHistoryTracker, mKeyguardStateController, mAccessibilityManager, - false); + mMetricsLogger, mClassifiers, mSingleTapClassfier, mLongTapClassifier, + mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController, + mAccessibilityManager, false, mFakeFeatureFlags); ArgumentCaptor<GestureFinalizedListener> gestureCompleteListenerCaptor = @@ -113,6 +119,7 @@ public class BrightLineClassifierTest extends SysuiTestCase { gestureCompleteListenerCaptor.capture()); mGestureFinalizedListener = gestureCompleteListenerCaptor.getValue(); + mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true); } @Test @@ -212,7 +219,7 @@ public class BrightLineClassifierTest extends SysuiTestCase { } @Test - public void testIsFalseTap_EmptyRecentEvents() { + public void testIsFalseSingleTap_EmptyRecentEvents() { // Ensure we look at prior events if recent events has already been emptied. when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(new ArrayList<>()); when(mFalsingDataProvider.getPriorMotionEvents()).thenReturn(mMotionEventList); @@ -223,7 +230,7 @@ public class BrightLineClassifierTest extends SysuiTestCase { @Test - public void testIsFalseTap_RobustCheck_NoFaceAuth() { + public void testIsFalseSingleTap_RobustCheck_NoFaceAuth() { when(mSingleTapClassfier.isTap(mMotionEventList, 0)).thenReturn(mPassedResult); when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble())) .thenReturn(mFalsedResult); @@ -233,13 +240,50 @@ public class BrightLineClassifierTest extends SysuiTestCase { } @Test - public void testIsFalseTap_RobustCheck_FaceAuth() { + public void testIsFalseSingleTap_RobustCheck_FaceAuth() { when(mSingleTapClassfier.isTap(mMotionEventList, 0)).thenReturn(mPassedResult); when(mFalsingDataProvider.isJustUnlockedWithFace()).thenReturn(true); assertThat(mBrightLineFalsingManager.isFalseTap(NO_PENALTY)).isFalse(); } @Test + public void testIsFalseLongTap_EmptyRecentEvents() { + // Ensure we look at prior events if recent events has already been emptied. + when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(new ArrayList<>()); + when(mFalsingDataProvider.getPriorMotionEvents()).thenReturn(mMotionEventList); + + mBrightLineFalsingManager.isFalseLongTap(0); + verify(mLongTapClassifier).isTap(mMotionEventList, 0); + } + + @Test + public void testIsFalseLongTap_FalseLongTap_NotFlagged() { + mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, false); + when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mFalsedResult); + assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isFalse(); + } + + @Test + public void testIsFalseLongTap_FalseLongTap() { + when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mFalsedResult); + assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isTrue(); + } + + @Test + public void testIsFalseLongTap_RobustCheck_NoFaceAuth() { + when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mPassedResult); + when(mFalsingDataProvider.isJustUnlockedWithFace()).thenReturn(false); + assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isFalse(); + } + + @Test + public void testIsFalseLongTap_RobustCheck_FaceAuth() { + when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mPassedResult); + when(mFalsingDataProvider.isJustUnlockedWithFace()).thenReturn(true); + assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isFalse(); + } + + @Test public void testIsFalseDoubleTap() { when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble())) .thenReturn(mPassedResult); diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java index b811aab6d35f..4281ee0f139f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java @@ -32,6 +32,8 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.testing.FakeMetricsLogger; import com.android.systemui.SysuiTestCase; +import com.android.systemui.flags.FakeFeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -57,6 +59,8 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { @Mock private SingleTapClassifier mSingleTapClassifier; @Mock + private LongTapClassifier mLongTapClassifier; + @Mock private DoubleTapClassifier mDoubleTapClassifier; @Mock private FalsingClassifier mClassifierA; @@ -71,6 +75,7 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { private final FalsingClassifier.Result mPassedResult = FalsingClassifier.Result.passed(1); private final FalsingClassifier.Result mFalsedResult = FalsingClassifier.Result.falsed(1, getClass().getSimpleName(), ""); + private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags(); @Before public void setup() { @@ -78,15 +83,17 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { when(mClassifierA.classifyGesture(anyInt(), anyDouble(), anyDouble())) .thenReturn(mFalsedResult); when(mSingleTapClassifier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult); + when(mLongTapClassifier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult); when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble())) .thenReturn(mFalsedResult); mClassifiers.add(mClassifierA); when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList); when(mKeyguardStateController.isShowing()).thenReturn(true); mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider, - mMetricsLogger, mClassifiers, mSingleTapClassifier, mDoubleTapClassifier, - mHistoryTracker, mKeyguardStateController, mAccessibilityManager, - false); + mMetricsLogger, mClassifiers, mSingleTapClassifier, mLongTapClassifier, + mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController, + mAccessibilityManager, false, mFakeFeatureFlags); + mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true); } @Test @@ -105,6 +112,13 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { @Test + public void testA11yDisablesLongTap() { + assertThat(mBrightLineFalsingManager.isFalseLongTap(1)).isTrue(); + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true); + assertThat(mBrightLineFalsingManager.isFalseLongTap(1)).isFalse(); + } + + @Test public void testA11yDisablesDoubleTap() { assertThat(mBrightLineFalsingManager.isFalseDoubleTap()).isTrue(); when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java index c5a7de410eb0..c234178f8af3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java @@ -92,6 +92,12 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { @Mock BouncerCallbackInteractor mBouncerCallbackInteractor; + @Mock + DreamOverlayAnimationsController mAnimationsController; + + @Mock + DreamOverlayStateController mStateController; + DreamOverlayContainerViewController mController; @Before @@ -115,7 +121,9 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { MAX_BURN_IN_OFFSET, BURN_IN_PROTECTION_UPDATE_INTERVAL, MILLIS_UNTIL_FULL_JITTER, - mBouncerCallbackInteractor); + mBouncerCallbackInteractor, + mAnimationsController, + mStateController); } @Test @@ -188,4 +196,31 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { verify(mBlurUtils).blurRadiusOfRatio(1 - scaledFraction); verify(mBlurUtils).applyBlur(mViewRoot, (int) blurRadius, false); } + + @Test + public void testStartDreamEntryAnimationsOnAttachedNonLowLight() { + when(mStateController.isLowLightActive()).thenReturn(false); + + mController.onViewAttached(); + + verify(mAnimationsController).startEntryAnimations(mDreamOverlayContainerView); + verify(mAnimationsController, never()).cancelRunningEntryAnimations(); + } + + @Test + public void testNeverStartDreamEntryAnimationsOnAttachedForLowLight() { + when(mStateController.isLowLightActive()).thenReturn(true); + + mController.onViewAttached(); + + verify(mAnimationsController, never()).startEntryAnimations(mDreamOverlayContainerView); + } + + @Test + public void testCancelDreamEntryAnimationsOnDetached() { + mController.onViewAttached(); + mController.onViewDetached(); + + verify(mAnimationsController).cancelRunningEntryAnimations(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java index f370be190761..f04a37f4c3fa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java @@ -253,6 +253,7 @@ public class DreamOverlayServiceTest extends SysuiTestCase { verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED); verify(mStateController).setOverlayActive(false); verify(mStateController).setLowLightActive(false); + verify(mStateController).setEntryAnimationsFinished(false); } @Test @@ -273,24 +274,28 @@ public class DreamOverlayServiceTest extends SysuiTestCase { @Test public void testDecorViewNotAddedToWindowAfterDestroy() throws Exception { - when(mDreamOverlayContainerView.getParent()) - .thenReturn(mDreamOverlayContainerViewParent) - .thenReturn(null); - final IBinder proxy = mService.onBind(new Intent()); final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); + // Destroy the service. + mService.onDestroy(); + mMainExecutor.runAllReady(); + // Inform the overlay service of dream starting. overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, false /*shouldShowComplication*/); + mMainExecutor.runAllReady(); - // Destroy the service. - mService.onDestroy(); + verify(mWindowManager, never()).addView(any(), any()); + } - // Run executor tasks. + @Test + public void testNeverRemoveDecorViewIfNotAdded() { + // Service destroyed before dream started. + mService.onDestroy(); mMainExecutor.runAllReady(); - verify(mWindowManager, never()).addView(any(), any()); + verify(mWindowManager, never()).removeView(any()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java index d1d32a13589a..c21c7a2aacbe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java @@ -234,4 +234,20 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { verify(mCallback, times(1)).onStateChanged(); assertThat(stateController.isLowLightActive()).isTrue(); } + + @Test + public void testNotifyEntryAnimationsFinishedChanged() { + final DreamOverlayStateController stateController = + new DreamOverlayStateController(mExecutor); + + stateController.addCallback(mCallback); + mExecutor.runAllReady(); + assertThat(stateController.areEntryAnimationsFinished()).isFalse(); + + stateController.setEntryAnimationsFinished(true); + mExecutor.runAllReady(); + + verify(mCallback, times(1)).onStateChanged(); + assertThat(stateController.areEntryAnimationsFinished()).isTrue(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java index aa021781296a..85c28190d77b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java @@ -19,16 +19,20 @@ package com.android.systemui.dreams; import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.AlarmManager; +import android.content.Context; import android.content.res.Resources; import android.hardware.SensorPrivacyManager; import android.net.ConnectivityManager; @@ -55,6 +59,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -69,7 +74,7 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { "{count, plural, =1 {# notification} other {# notifications}}"; @Mock - DreamOverlayStatusBarView mView; + MockDreamOverlayStatusBarView mView; @Mock ConnectivityManager mConnectivityManager; @Mock @@ -105,6 +110,9 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { @Mock DreamOverlayStateController mDreamOverlayStateController; + @Captor + private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor; + private final Executor mMainExecutor = Runnable::run; DreamOverlayStatusBarViewController mController; @@ -115,6 +123,8 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { when(mResources.getString(R.string.dream_overlay_status_bar_notification_indicator)) .thenReturn(NOTIFICATION_INDICATOR_FORMATTER_STRING); + doCallRealMethod().when(mView).setVisibility(anyInt()); + doCallRealMethod().when(mView).getVisibility(); mController = new DreamOverlayStatusBarViewController( mView, @@ -454,12 +464,10 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { public void testStatusBarHiddenWhenSystemStatusBarShown() { mController.onViewAttached(); - final ArgumentCaptor<StatusBarWindowStateListener> - callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class); - verify(mStatusBarWindowStateController).addListener(callbackCapture.capture()); - callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_SHOWING); + updateEntryAnimationsFinished(); + updateStatusBarWindowState(true); - verify(mView).setVisibility(View.INVISIBLE); + assertThat(mView.getVisibility()).isEqualTo(View.INVISIBLE); } @Test @@ -467,29 +475,43 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { mController.onViewAttached(); reset(mView); - final ArgumentCaptor<StatusBarWindowStateListener> - callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class); - verify(mStatusBarWindowStateController).addListener(callbackCapture.capture()); - callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_HIDDEN); + updateEntryAnimationsFinished(); + updateStatusBarWindowState(false); - verify(mView).setVisibility(View.VISIBLE); + assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE); } @Test public void testUnattachedStatusBarVisibilityUnchangedWhenSystemStatusBarHidden() { mController.onViewAttached(); + updateEntryAnimationsFinished(); mController.onViewDetached(); reset(mView); - final ArgumentCaptor<StatusBarWindowStateListener> - callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class); - verify(mStatusBarWindowStateController).addListener(callbackCapture.capture()); - callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_SHOWING); + updateStatusBarWindowState(true); verify(mView, never()).setVisibility(anyInt()); } @Test + public void testNoChangeToVisibilityBeforeDreamStartedWhenStatusBarHidden() { + mController.onViewAttached(); + + // Trigger status bar window state change. + final StatusBarWindowStateListener listener = updateStatusBarWindowState(false); + + // Verify no visibility change because dream not started. + verify(mView, never()).setVisibility(anyInt()); + + // Dream entry animations finished. + updateEntryAnimationsFinished(); + + // Trigger another status bar window state change, and verify visibility change. + listener.onStatusBarWindowStateChanged(WINDOW_STATE_HIDDEN); + assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test public void testExtraStatusBarItemSetWhenItemsChange() { mController.onViewAttached(); when(mStatusBarItem.getView()).thenReturn(mStatusBarItemView); @@ -507,16 +529,75 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { public void testLowLightHidesStatusBar() { when(mDreamOverlayStateController.isLowLightActive()).thenReturn(true); mController.onViewAttached(); + updateEntryAnimationsFinished(); + + final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture = + ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class); + verify(mDreamOverlayStateController).addCallback(callbackCapture.capture()); + callbackCapture.getValue().onStateChanged(); - verify(mView).setVisibility(View.INVISIBLE); + assertThat(mView.getVisibility()).isEqualTo(View.INVISIBLE); reset(mView); when(mDreamOverlayStateController.isLowLightActive()).thenReturn(false); + callbackCapture.getValue().onStateChanged(); + + assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + public void testNoChangeToVisibilityBeforeDreamStartedWhenLowLightStateChange() { + when(mDreamOverlayStateController.isLowLightActive()).thenReturn(false); + mController.onViewAttached(); + + // No change to visibility because dream not fully started. + verify(mView, never()).setVisibility(anyInt()); + + // Dream entry animations finished. + updateEntryAnimationsFinished(); + + // Trigger state change and verify visibility changed. final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture = ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class); verify(mDreamOverlayStateController).addCallback(callbackCapture.capture()); callbackCapture.getValue().onStateChanged(); - verify(mView).setVisibility(View.VISIBLE); + assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE); + } + + private StatusBarWindowStateListener updateStatusBarWindowState(boolean show) { + when(mStatusBarWindowStateController.windowIsShowing()).thenReturn(show); + final ArgumentCaptor<StatusBarWindowStateListener> + callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class); + verify(mStatusBarWindowStateController).addListener(callbackCapture.capture()); + final StatusBarWindowStateListener listener = callbackCapture.getValue(); + listener.onStatusBarWindowStateChanged(show ? WINDOW_STATE_SHOWING : WINDOW_STATE_HIDDEN); + return listener; + } + + private void updateEntryAnimationsFinished() { + when(mDreamOverlayStateController.areEntryAnimationsFinished()).thenReturn(true); + + verify(mDreamOverlayStateController).addCallback(mCallbackCaptor.capture()); + final DreamOverlayStateController.Callback callback = mCallbackCaptor.getValue(); + callback.onStateChanged(); + } + + private static class MockDreamOverlayStatusBarView extends DreamOverlayStatusBarView { + private int mVisibility = View.VISIBLE; + + private MockDreamOverlayStatusBarView(Context context) { + super(context); + } + + @Override + public void setVisibility(int visibility) { + mVisibility = visibility; + } + + @Override + public int getVisibility() { + return mVisibility; + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java index 3b9e398141ee..b477592f8fbc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java @@ -16,6 +16,7 @@ package com.android.systemui.dreams.complication; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -29,16 +30,18 @@ import androidx.lifecycle.Observer; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.dreams.DreamOverlayStateController; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; @SmallTest @@ -77,9 +80,20 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase { @Mock ComplicationLayoutParams mComplicationLayoutParams; + @Mock + DreamOverlayStateController mDreamOverlayStateController; + + @Captor + private ArgumentCaptor<Observer<Collection<ComplicationViewModel>>> mObserverCaptor; + + @Captor + private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor; + @Complication.Category static final int COMPLICATION_CATEGORY = Complication.CATEGORY_SYSTEM; + private ComplicationHostViewController mController; + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -91,32 +105,28 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase { when(mViewHolder.getCategory()).thenReturn(COMPLICATION_CATEGORY); when(mViewHolder.getLayoutParams()).thenReturn(mComplicationLayoutParams); when(mComplicationView.getParent()).thenReturn(mComplicationHostView); - } - /** - * Ensures the lifecycle of complications is properly handled. - */ - @Test - public void testViewModelObservation() { - final ArgumentCaptor<Observer<Collection<ComplicationViewModel>>> observerArgumentCaptor = - ArgumentCaptor.forClass(Observer.class); - final ComplicationHostViewController controller = new ComplicationHostViewController( + mController = new ComplicationHostViewController( mComplicationHostView, mLayoutEngine, + mDreamOverlayStateController, mLifecycleOwner, mViewModel); - controller.init(); - - verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner), - observerArgumentCaptor.capture()); + mController.init(); + } + /** + * Ensures the lifecycle of complications is properly handled. + */ + @Test + public void testViewModelObservation() { final Observer<Collection<ComplicationViewModel>> observer = - observerArgumentCaptor.getValue(); + captureComplicationViewModelsObserver(); - // Add complication and ensure it is added to the view. + // Add a complication and ensure it is added to the view. final HashSet<ComplicationViewModel> complications = new HashSet<>( - Arrays.asList(mComplicationViewModel)); + Collections.singletonList(mComplicationViewModel)); observer.onChanged(complications); verify(mLayoutEngine).addComplication(eq(mComplicationId), eq(mComplicationView), @@ -127,4 +137,48 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase { verify(mLayoutEngine).removeComplication(eq(mComplicationId)); } + + @Test + public void testNewComplicationsBeforeEntryAnimationsFinishSetToInvisible() { + final Observer<Collection<ComplicationViewModel>> observer = + captureComplicationViewModelsObserver(); + + // Add a complication before entry animations are finished. + final HashSet<ComplicationViewModel> complications = new HashSet<>( + Collections.singletonList(mComplicationViewModel)); + observer.onChanged(complications); + + // The complication view should be set to invisible. + verify(mComplicationView).setVisibility(View.INVISIBLE); + } + + @Test + public void testNewComplicationsAfterEntryAnimationsFinishNotSetToInvisible() { + final Observer<Collection<ComplicationViewModel>> observer = + captureComplicationViewModelsObserver(); + + // Dream entry animations finished. + when(mDreamOverlayStateController.areEntryAnimationsFinished()).thenReturn(true); + final DreamOverlayStateController.Callback stateCallback = captureOverlayStateCallback(); + stateCallback.onStateChanged(); + + // Add a complication after entry animations are finished. + final HashSet<ComplicationViewModel> complications = new HashSet<>( + Collections.singletonList(mComplicationViewModel)); + observer.onChanged(complications); + + // The complication view should not be set to invisible. + verify(mComplicationView, never()).setVisibility(View.INVISIBLE); + } + + private Observer<Collection<ComplicationViewModel>> captureComplicationViewModelsObserver() { + verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner), + mObserverCaptor.capture()); + return mObserverCaptor.getValue(); + } + + private DreamOverlayStateController.Callback captureOverlayStateCallback() { + verify(mDreamOverlayStateController).addCallback(mCallbackCaptor.capture()); + return mCallbackCaptor.getValue(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java deleted file mode 100644 index 640e6dc0461d..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.keyguard; - -import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.annotation.UserIdInt; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.graphics.drawable.Drawable; -import android.os.Looper; -import android.os.UserHandle; -import android.os.UserManager; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.broadcast.BroadcastDispatcher; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * runtest systemui -c com.android.systemui.keyguard.WorkLockActivityTest - */ -@SmallTest -@RunWith(AndroidJUnit4.class) -public class WorkLockActivityTest extends SysuiTestCase { - private static final @UserIdInt int USER_ID = 270; - private static final String CALLING_PACKAGE_NAME = "com.android.test"; - - private @Mock UserManager mUserManager; - private @Mock PackageManager mPackageManager; - private @Mock Context mContext; - private @Mock BroadcastDispatcher mBroadcastDispatcher; - private @Mock Drawable mDrawable; - private @Mock Drawable mBadgedDrawable; - - private WorkLockActivity mActivity; - - private static class WorkLockActivityTestable extends WorkLockActivity { - WorkLockActivityTestable(Context baseContext, BroadcastDispatcher broadcastDispatcher, - UserManager userManager, PackageManager packageManager) { - super(broadcastDispatcher, userManager, packageManager); - attachBaseContext(baseContext); - } - } - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - if (Looper.myLooper() == null) { - Looper.prepare(); - } - mActivity = new WorkLockActivityTestable(mContext, mBroadcastDispatcher, mUserManager, - mPackageManager); - } - - @Test - public void testGetBadgedIcon() throws Exception { - ApplicationInfo info = new ApplicationInfo(); - when(mPackageManager.getApplicationInfoAsUser(eq(CALLING_PACKAGE_NAME), any(), - eq(USER_ID))).thenReturn(info); - when(mPackageManager.getApplicationIcon(eq(info))).thenReturn(mDrawable); - when(mUserManager.getBadgedIconForUser(any(), eq(UserHandle.of(USER_ID)))).thenReturn( - mBadgedDrawable); - mActivity.setIntent(new Intent() - .putExtra(Intent.EXTRA_USER_ID, USER_ID) - .putExtra(Intent.EXTRA_PACKAGE_NAME, CALLING_PACKAGE_NAME)); - - assertEquals(mBadgedDrawable, mActivity.getBadgedIcon()); - } - - @Test - public void testUnregisteredFromDispatcher() { - mActivity.unregisterBroadcastReceiver(); - verify(mBroadcastDispatcher).unregisterReceiver(any()); - verify(mContext, never()).unregisterReceiver(any()); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt new file mode 100644 index 000000000000..c7f1decac670 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.systemui.keyguard + +import android.annotation.UserIdInt +import android.content.Context +import android.content.Intent +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.pm.PackageManager.ApplicationInfoFlags +import android.graphics.drawable.Drawable +import android.os.Looper +import android.os.UserHandle +import android.os.UserManager +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.nullable +import com.android.systemui.util.mockito.whenever +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +/** runtest systemui -c com.android.systemui.keyguard.WorkLockActivityTest */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class WorkLockActivityTest : SysuiTestCase() { + private val context: Context = mock() + private val userManager: UserManager = mock() + private val packageManager: PackageManager = mock() + private val broadcastDispatcher: BroadcastDispatcher = mock() + private val drawable: Drawable = mock() + private val badgedDrawable: Drawable = mock() + private lateinit var activity: WorkLockActivity + + private class WorkLockActivityTestable + constructor( + baseContext: Context, + broadcastDispatcher: BroadcastDispatcher, + userManager: UserManager, + packageManager: PackageManager, + ) : WorkLockActivity(broadcastDispatcher, userManager, packageManager) { + init { + attachBaseContext(baseContext) + } + } + + @Before + fun setUp() { + if (Looper.myLooper() == null) { + Looper.prepare() + } + activity = + WorkLockActivityTestable( + baseContext = context, + broadcastDispatcher = broadcastDispatcher, + userManager = userManager, + packageManager = packageManager + ) + } + + @Test + fun testGetBadgedIcon() { + val info = ApplicationInfo() + whenever( + packageManager.getApplicationInfoAsUser( + eq(CALLING_PACKAGE_NAME), + any<ApplicationInfoFlags>(), + eq(USER_ID) + ) + ) + .thenReturn(info) + whenever(packageManager.getApplicationIcon(ArgumentMatchers.eq(info))).thenReturn(drawable) + whenever(userManager.getBadgedIconForUser(any(), eq(UserHandle.of(USER_ID)))) + .thenReturn(badgedDrawable) + activity.intent = + Intent() + .putExtra(Intent.EXTRA_USER_ID, USER_ID) + .putExtra(Intent.EXTRA_PACKAGE_NAME, CALLING_PACKAGE_NAME) + assertEquals(badgedDrawable, activity.badgedIcon) + } + + @Test + fun testUnregisteredFromDispatcher() { + activity.unregisterBroadcastReceiver() + verify(broadcastDispatcher).unregisterReceiver(any()) + verify(context, never()).unregisterReceiver(nullable()) + } + + @Test + fun onBackPressed_finishActivity() { + assertFalse(activity.isFinishing) + + activity.onBackPressed() + + assertFalse(activity.isFinishing) + } + + companion object { + @UserIdInt private val USER_ID = 270 + private const val CALLING_PACKAGE_NAME = "com.android.test" + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt index f18acbad14f2..0fb181d41484 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt @@ -23,14 +23,11 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.yield -/** - * Fake implementation of a quick affordance data source. - * - * This class is abstract to force tests to provide extensions of it as the system that references - * these configs uses each implementation's class type to refer to them. - */ -abstract class FakeKeyguardQuickAffordanceConfig( +/** Fake implementation of a quick affordance data source. */ +class FakeKeyguardQuickAffordanceConfig( override val key: String, + override val pickerName: String = key, + override val pickerIconResourceId: Int = 0, ) : KeyguardQuickAffordanceConfig { var onTriggeredResult: OnTriggeredResult = OnTriggeredResult.Handled diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt new file mode 100644 index 000000000000..d2422ad7b53b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.data.quickaffordance + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() { + + private lateinit var underTest: KeyguardQuickAffordanceSelectionManager + + @Before + fun setUp() { + underTest = KeyguardQuickAffordanceSelectionManager() + } + + @Test + fun setSelections() = + runBlocking(IMMEDIATE) { + var affordanceIdsBySlotId: Map<String, List<String>>? = null + val job = underTest.selections.onEach { affordanceIdsBySlotId = it }.launchIn(this) + val slotId1 = "slot1" + val slotId2 = "slot2" + val affordanceId1 = "affordance1" + val affordanceId2 = "affordance2" + val affordanceId3 = "affordance3" + + underTest.setSelections( + slotId = slotId1, + affordanceIds = listOf(affordanceId1), + ) + assertSelections( + affordanceIdsBySlotId, + mapOf( + slotId1 to listOf(affordanceId1), + ), + ) + + underTest.setSelections( + slotId = slotId2, + affordanceIds = listOf(affordanceId2), + ) + assertSelections( + affordanceIdsBySlotId, + mapOf( + slotId1 to listOf(affordanceId1), + slotId2 to listOf(affordanceId2), + ) + ) + + underTest.setSelections( + slotId = slotId1, + affordanceIds = listOf(affordanceId1, affordanceId3), + ) + assertSelections( + affordanceIdsBySlotId, + mapOf( + slotId1 to listOf(affordanceId1, affordanceId3), + slotId2 to listOf(affordanceId2), + ) + ) + + underTest.setSelections( + slotId = slotId1, + affordanceIds = listOf(affordanceId3), + ) + assertSelections( + affordanceIdsBySlotId, + mapOf( + slotId1 to listOf(affordanceId3), + slotId2 to listOf(affordanceId2), + ) + ) + + underTest.setSelections( + slotId = slotId2, + affordanceIds = listOf(), + ) + assertSelections( + affordanceIdsBySlotId, + mapOf( + slotId1 to listOf(affordanceId3), + slotId2 to listOf(), + ) + ) + + job.cancel() + } + + private suspend fun assertSelections( + observed: Map<String, List<String>>?, + expected: Map<String, List<String>>, + ) { + assertThat(underTest.getSelections()).isEqualTo(expected) + assertThat(observed).isEqualTo(expected) + } + + companion object { + private val IMMEDIATE = Dispatchers.Main.immediate + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt index 61a3f9f07600..2bd8e9aabab3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt @@ -50,7 +50,7 @@ class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) whenever(controller.intent).thenReturn(INTENT_1) - underTest = QrCodeScannerKeyguardQuickAffordanceConfig(controller) + underTest = QrCodeScannerKeyguardQuickAffordanceConfig(mock(), controller) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt index c05beef6d624..5178154bdeee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt @@ -59,6 +59,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { underTest = QuickAccessWalletKeyguardQuickAffordanceConfig( + mock(), walletController, activityStarter, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt new file mode 100644 index 000000000000..5a7f2bb5cb37 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.data.repository + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager +import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation +import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation +import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() { + + private lateinit var underTest: KeyguardQuickAffordanceRepository + + private lateinit var config1: FakeKeyguardQuickAffordanceConfig + private lateinit var config2: FakeKeyguardQuickAffordanceConfig + + @Before + fun setUp() { + config1 = FakeKeyguardQuickAffordanceConfig("built_in:1") + config2 = FakeKeyguardQuickAffordanceConfig("built_in:2") + underTest = + KeyguardQuickAffordanceRepository( + scope = CoroutineScope(IMMEDIATE), + backgroundDispatcher = IMMEDIATE, + selectionManager = KeyguardQuickAffordanceSelectionManager(), + configs = setOf(config1, config2), + ) + } + + @Test + fun setSelections() = + runBlocking(IMMEDIATE) { + var configsBySlotId: Map<String, List<KeyguardQuickAffordanceConfig>>? = null + val job = underTest.selections.onEach { configsBySlotId = it }.launchIn(this) + val slotId1 = "slot1" + val slotId2 = "slot2" + + underTest.setSelections(slotId1, listOf(config1.key)) + assertSelections( + configsBySlotId, + mapOf( + slotId1 to listOf(config1), + ), + ) + + underTest.setSelections(slotId2, listOf(config2.key)) + assertSelections( + configsBySlotId, + mapOf( + slotId1 to listOf(config1), + slotId2 to listOf(config2), + ), + ) + + underTest.setSelections(slotId1, emptyList()) + underTest.setSelections(slotId2, listOf(config1.key)) + assertSelections( + configsBySlotId, + mapOf( + slotId1 to emptyList(), + slotId2 to listOf(config1), + ), + ) + + job.cancel() + } + + @Test + fun getAffordancePickerRepresentations() { + assertThat(underTest.getAffordancePickerRepresentations()) + .isEqualTo( + listOf( + KeyguardQuickAffordancePickerRepresentation( + id = config1.key, + name = config1.pickerName, + iconResourceId = config1.pickerIconResourceId, + ), + KeyguardQuickAffordancePickerRepresentation( + id = config2.key, + name = config2.pickerName, + iconResourceId = config2.pickerIconResourceId, + ), + ) + ) + } + + @Test + fun getSlotPickerRepresentations() { + assertThat(underTest.getSlotPickerRepresentations()) + .isEqualTo( + listOf( + KeyguardSlotPickerRepresentation( + id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + maxSelectedAffordances = 1, + ), + KeyguardSlotPickerRepresentation( + id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, + maxSelectedAffordances = 1, + ), + ) + ) + } + + private suspend fun assertSelections( + observed: Map<String, List<KeyguardQuickAffordanceConfig>>?, + expected: Map<String, List<KeyguardQuickAffordanceConfig>>, + ) { + assertThat(observed).isEqualTo(expected) + assertThat(underTest.getSelections()) + .isEqualTo(expected.mapValues { (_, configs) -> configs.map { it.key } }) + expected.forEach { (slotId, configs) -> + assertThat(underTest.getSelections(slotId)).isEqualTo(configs) + } + } + + companion object { + private val IMMEDIATE = Dispatchers.Main.immediate + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index b9ab9d37a805..53d9b87b2346 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -21,8 +21,10 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Position import com.android.systemui.doze.DozeHost import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.whenever @@ -46,6 +48,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var dozeHost: DozeHost @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle + @Mock private lateinit var biometricUnlockController: BiometricUnlockController private lateinit var underTest: KeyguardRepositoryImpl @@ -59,6 +62,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { keyguardStateController, dozeHost, wakefulnessLifecycle, + biometricUnlockController, ) } @@ -190,7 +194,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test - fun wakefullness() = runBlockingTest { + fun wakefulness() = runBlockingTest { val values = mutableListOf<WakefulnessModel>() val job = underTest.wakefulnessState.onEach(values::add).launchIn(this) @@ -217,4 +221,63 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { job.cancel() verify(wakefulnessLifecycle).removeObserver(captor.value) } + + @Test + fun isBouncerShowing() = runBlockingTest { + whenever(keyguardStateController.isBouncerShowing).thenReturn(false) + var latest: Boolean? = null + val job = underTest.isBouncerShowing.onEach { latest = it }.launchIn(this) + + assertThat(latest).isFalse() + + val captor = argumentCaptor<KeyguardStateController.Callback>() + verify(keyguardStateController).addCallback(captor.capture()) + + whenever(keyguardStateController.isBouncerShowing).thenReturn(true) + captor.value.onBouncerShowingChanged() + assertThat(latest).isTrue() + + whenever(keyguardStateController.isBouncerShowing).thenReturn(false) + captor.value.onBouncerShowingChanged() + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun biometricUnlockState() = runBlockingTest { + val values = mutableListOf<BiometricUnlockModel>() + val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this) + + val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>() + verify(biometricUnlockController).addBiometricModeListener(captor.capture()) + + captor.value.onModeChanged(BiometricUnlockController.MODE_NONE) + captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK) + captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING) + captor.value.onModeChanged(BiometricUnlockController.MODE_SHOW_BOUNCER) + captor.value.onModeChanged(BiometricUnlockController.MODE_ONLY_WAKE) + captor.value.onModeChanged(BiometricUnlockController.MODE_UNLOCK_COLLAPSING) + captor.value.onModeChanged(BiometricUnlockController.MODE_DISMISS_BOUNCER) + captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM) + + assertThat(values) + .isEqualTo( + listOf( + // Initial value will be NONE, followed by onModeChanged() call + BiometricUnlockModel.NONE, + BiometricUnlockModel.NONE, + BiometricUnlockModel.WAKE_AND_UNLOCK, + BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING, + BiometricUnlockModel.SHOW_BOUNCER, + BiometricUnlockModel.ONLY_WAKE, + BiometricUnlockModel.UNLOCK_COLLAPSING, + BiometricUnlockModel.DISMISS_BOUNCER, + BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM, + ) + ) + + job.cancel() + verify(biometricUnlockController).removeBiometricModeListener(captor.value) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt index 64913c7c28c2..27d5d0a98978 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt @@ -128,11 +128,15 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { assertThat(steps.size).isEqualTo(3) assertThat(steps[0]) - .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED)) + .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME)) assertThat(steps[1]) - .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING)) + .isEqualTo( + TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING, OWNER_NAME) + ) assertThat(steps[2]) - .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED)) + .isEqualTo( + TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME) + ) job.cancel() } @@ -174,15 +178,22 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { } private fun assertSteps(steps: List<TransitionStep>, fractions: List<BigDecimal>) { - assertThat(steps[0]).isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED)) + assertThat(steps[0]) + .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME)) fractions.forEachIndexed { index, fraction -> assertThat(steps[index + 1]) .isEqualTo( - TransitionStep(AOD, LOCKSCREEN, fraction.toFloat(), TransitionState.RUNNING) + TransitionStep( + AOD, + LOCKSCREEN, + fraction.toFloat(), + TransitionState.RUNNING, + OWNER_NAME + ) ) } assertThat(steps[steps.size - 1]) - .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED)) + .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME)) assertThat(wtfHandler.failed).isFalse() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index 7116cc101d3f..8b6603d79244 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -25,10 +25,14 @@ import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition import com.android.systemui.plugins.ActivityStarter @@ -37,6 +41,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.runBlockingTest import org.junit.Before import org.junit.Test @@ -189,6 +195,8 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { /* startActivity= */ true, ), ) + + private val IMMEDIATE = Dispatchers.Main.immediate } @Mock private lateinit var lockPatternUtils: LockPatternUtils @@ -213,10 +221,20 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { whenever(expandable.activityLaunchController()).thenReturn(animationController) homeControls = - object : - FakeKeyguardQuickAffordanceConfig( - BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS - ) {} + FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS) + val quickAccessWallet = + FakeKeyguardQuickAffordanceConfig( + BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET + ) + val qrCodeScanner = + FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER) + val quickAffordanceRepository = + KeyguardQuickAffordanceRepository( + scope = CoroutineScope(IMMEDIATE), + backgroundDispatcher = IMMEDIATE, + selectionManager = KeyguardQuickAffordanceSelectionManager(), + configs = setOf(homeControls, quickAccessWallet, qrCodeScanner), + ) underTest = KeyguardQuickAffordanceInteractor( keyguardInteractor = KeyguardInteractor(repository = FakeKeyguardRepository()), @@ -229,14 +247,8 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { ), KeyguardQuickAffordancePosition.BOTTOM_END to listOf( - object : - FakeKeyguardQuickAffordanceConfig( - BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET - ) {}, - object : - FakeKeyguardQuickAffordanceConfig( - BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER - ) {}, + quickAccessWallet, + qrCodeScanner, ), ), ), @@ -244,6 +256,11 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { keyguardStateController = keyguardStateController, userTracker = userTracker, activityStarter = activityStarter, + featureFlags = + FakeFeatureFlags().apply { + set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false) + }, + repository = { quickAffordanceRepository }, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index ae32ba6676be..33645354d9f3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -22,22 +22,31 @@ import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry import com.android.systemui.keyguard.shared.quickaffordance.ActivationState import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserTracker +import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runBlockingTest import kotlinx.coroutines.yield import org.junit.Before @@ -47,6 +56,7 @@ import org.junit.runners.JUnit4 import org.mockito.Mock import org.mockito.MockitoAnnotations +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(JUnit4::class) class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { @@ -62,6 +72,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig private lateinit var qrCodeScanner: FakeKeyguardQuickAffordanceConfig + private lateinit var featureFlags: FakeFeatureFlags @Before fun setUp() { @@ -71,20 +82,25 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { repository.setKeyguardShowing(true) homeControls = - object : - FakeKeyguardQuickAffordanceConfig( - BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS - ) {} + FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS) quickAccessWallet = - object : - FakeKeyguardQuickAffordanceConfig( - BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET - ) {} + FakeKeyguardQuickAffordanceConfig( + BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET + ) qrCodeScanner = - object : - FakeKeyguardQuickAffordanceConfig( - BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER - ) {} + FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER) + + val quickAffordanceRepository = + KeyguardQuickAffordanceRepository( + scope = CoroutineScope(IMMEDIATE), + backgroundDispatcher = IMMEDIATE, + selectionManager = KeyguardQuickAffordanceSelectionManager(), + configs = setOf(homeControls, quickAccessWallet, qrCodeScanner), + ) + featureFlags = + FakeFeatureFlags().apply { + set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false) + } underTest = KeyguardQuickAffordanceInteractor( @@ -107,6 +123,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { keyguardStateController = keyguardStateController, userTracker = userTracker, activityStarter = activityStarter, + featureFlags = featureFlags, + repository = { quickAffordanceRepository }, ) } @@ -210,6 +228,270 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { job.cancel() } + @Test + fun select() = + runBlocking(IMMEDIATE) { + featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true) + homeControls.setState( + KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON) + ) + quickAccessWallet.setState( + KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON) + ) + qrCodeScanner.setState( + KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON) + ) + + assertThat(underTest.getSelections()) + .isEqualTo( + mapOf<String, List<String>>( + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(), + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(), + ) + ) + + var startConfig: KeyguardQuickAffordanceModel? = null + val job1 = + underTest + .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START) + .onEach { startConfig = it } + .launchIn(this) + var endConfig: KeyguardQuickAffordanceModel? = null + val job2 = + underTest + .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END) + .onEach { endConfig = it } + .launchIn(this) + + underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key) + yield() + yield() + assertThat(startConfig) + .isEqualTo( + KeyguardQuickAffordanceModel.Visible( + configKey = + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + + "::${homeControls.key}", + icon = ICON, + activationState = ActivationState.NotSupported, + ) + ) + assertThat(endConfig) + .isEqualTo( + KeyguardQuickAffordanceModel.Hidden, + ) + assertThat(underTest.getSelections()) + .isEqualTo( + mapOf( + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to + listOf(homeControls.key), + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(), + ) + ) + + underTest.select( + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + quickAccessWallet.key + ) + yield() + yield() + assertThat(startConfig) + .isEqualTo( + KeyguardQuickAffordanceModel.Visible( + configKey = + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + + "::${quickAccessWallet.key}", + icon = ICON, + activationState = ActivationState.NotSupported, + ) + ) + assertThat(endConfig) + .isEqualTo( + KeyguardQuickAffordanceModel.Hidden, + ) + assertThat(underTest.getSelections()) + .isEqualTo( + mapOf( + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to + listOf(quickAccessWallet.key), + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(), + ) + ) + + underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, qrCodeScanner.key) + yield() + yield() + assertThat(startConfig) + .isEqualTo( + KeyguardQuickAffordanceModel.Visible( + configKey = + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + + "::${quickAccessWallet.key}", + icon = ICON, + activationState = ActivationState.NotSupported, + ) + ) + assertThat(endConfig) + .isEqualTo( + KeyguardQuickAffordanceModel.Visible( + configKey = + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END + + "::${qrCodeScanner.key}", + icon = ICON, + activationState = ActivationState.NotSupported, + ) + ) + assertThat(underTest.getSelections()) + .isEqualTo( + mapOf( + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to + listOf(quickAccessWallet.key), + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to + listOf(qrCodeScanner.key), + ) + ) + + job1.cancel() + job2.cancel() + } + + @Test + fun `unselect - one`() = + runBlocking(IMMEDIATE) { + featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true) + homeControls.setState( + KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON) + ) + quickAccessWallet.setState( + KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON) + ) + qrCodeScanner.setState( + KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON) + ) + + var startConfig: KeyguardQuickAffordanceModel? = null + val job1 = + underTest + .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START) + .onEach { startConfig = it } + .launchIn(this) + var endConfig: KeyguardQuickAffordanceModel? = null + val job2 = + underTest + .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END) + .onEach { endConfig = it } + .launchIn(this) + underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key) + yield() + yield() + underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, quickAccessWallet.key) + yield() + yield() + + underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key) + yield() + yield() + + assertThat(startConfig) + .isEqualTo( + KeyguardQuickAffordanceModel.Hidden, + ) + assertThat(endConfig) + .isEqualTo( + KeyguardQuickAffordanceModel.Visible( + configKey = + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END + + "::${quickAccessWallet.key}", + icon = ICON, + activationState = ActivationState.NotSupported, + ) + ) + assertThat(underTest.getSelections()) + .isEqualTo( + mapOf( + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(), + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to + listOf(quickAccessWallet.key), + ) + ) + + underTest.unselect( + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, + quickAccessWallet.key + ) + yield() + yield() + + assertThat(startConfig) + .isEqualTo( + KeyguardQuickAffordanceModel.Hidden, + ) + assertThat(endConfig) + .isEqualTo( + KeyguardQuickAffordanceModel.Hidden, + ) + assertThat(underTest.getSelections()) + .isEqualTo( + mapOf<String, List<String>>( + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(), + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(), + ) + ) + + job1.cancel() + job2.cancel() + } + + @Test + fun `unselect - all`() = + runBlocking(IMMEDIATE) { + featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true) + homeControls.setState( + KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON) + ) + quickAccessWallet.setState( + KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON) + ) + qrCodeScanner.setState( + KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON) + ) + + underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key) + yield() + yield() + underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, quickAccessWallet.key) + yield() + yield() + + underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, null) + yield() + yield() + + assertThat(underTest.getSelections()) + .isEqualTo( + mapOf( + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(), + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to + listOf(quickAccessWallet.key), + ) + ) + + underTest.unselect( + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, + null, + ) + yield() + yield() + + assertThat(underTest.getSelections()) + .isEqualTo( + mapOf<String, List<String>>( + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(), + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(), + ) + ) + } + companion object { private val ICON: Icon = mock { whenever(this.contentDescription) @@ -220,5 +502,6 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { ) } private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337 + private val IMMEDIATE = Dispatchers.Main.immediate } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt index 0424c28a1c24..6333b244fd38 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt @@ -54,9 +54,11 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { @Test fun `transition collectors receives only appropriate events`() = runBlocking(IMMEDIATE) { - var goneToAodSteps = mutableListOf<TransitionStep>() + var lockscreenToAodSteps = mutableListOf<TransitionStep>() val job1 = - underTest.goneToAodTransition.onEach { goneToAodSteps.add(it) }.launchIn(this) + underTest.lockscreenToAodTransition + .onEach { lockscreenToAodSteps.add(it) } + .launchIn(this) var aodToLockscreenSteps = mutableListOf<TransitionStep>() val job2 = @@ -70,14 +72,14 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)) steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING)) steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED)) - steps.add(TransitionStep(GONE, AOD, 0f, STARTED)) - steps.add(TransitionStep(GONE, AOD, 0.1f, RUNNING)) - steps.add(TransitionStep(GONE, AOD, 0.2f, RUNNING)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 0.1f, RUNNING)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 0.2f, RUNNING)) steps.forEach { repository.sendTransitionStep(it) } assertThat(aodToLockscreenSteps).isEqualTo(steps.subList(2, 5)) - assertThat(goneToAodSteps).isEqualTo(steps.subList(5, 8)) + assertThat(lockscreenToAodSteps).isEqualTo(steps.subList(5, 8)) job1.cancel() job2.cancel() @@ -119,10 +121,8 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { fun keyguardStateTests() = runBlocking(IMMEDIATE) { var finishedSteps = mutableListOf<KeyguardState>() - val job1 = + val job = underTest.finishedKeyguardState.onEach { finishedSteps.add(it) }.launchIn(this) - var startedSteps = mutableListOf<KeyguardState>() - val job2 = underTest.startedKeyguardState.onEach { startedSteps.add(it) }.launchIn(this) val steps = mutableListOf<TransitionStep>() @@ -137,10 +137,60 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { steps.forEach { repository.sendTransitionStep(it) } assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, AOD)) - assertThat(startedSteps).isEqualTo(listOf(LOCKSCREEN, AOD, GONE)) - job1.cancel() - job2.cancel() + job.cancel() + } + + @Test + fun finishedKeyguardTransitionStepTests() = + runBlocking(IMMEDIATE) { + var finishedSteps = mutableListOf<TransitionStep>() + val job = + underTest.finishedKeyguardTransitionStep + .onEach { finishedSteps.add(it) } + .launchIn(this) + + val steps = mutableListOf<TransitionStep>() + + steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED)) + steps.add(TransitionStep(AOD, GONE, 1f, STARTED)) + + steps.forEach { repository.sendTransitionStep(it) } + + assertThat(finishedSteps).isEqualTo(listOf(steps[2], steps[5])) + + job.cancel() + } + + @Test + fun startedKeyguardTransitionStepTests() = + runBlocking(IMMEDIATE) { + var startedSteps = mutableListOf<TransitionStep>() + val job = + underTest.startedKeyguardTransitionStep + .onEach { startedSteps.add(it) } + .launchIn(this) + + val steps = mutableListOf<TransitionStep>() + + steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED)) + steps.add(TransitionStep(AOD, GONE, 1f, STARTED)) + + steps.forEach { repository.sendTransitionStep(it) } + + assertThat(startedSteps).isEqualTo(listOf(steps[0], steps[3], steps[6])) + + job.cancel() } companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index f73d1ecf9373..78148c4d3d1b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -23,10 +23,14 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.doze.util.BurnInHelperWrapper +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor @@ -41,6 +45,8 @@ import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlin.math.max import kotlin.math.min +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.runBlockingTest @@ -82,20 +88,13 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { .thenReturn(RETURNED_BURN_IN_OFFSET) homeControlsQuickAffordanceConfig = - object : - FakeKeyguardQuickAffordanceConfig( - BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS - ) {} + FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS) quickAccessWalletAffordanceConfig = - object : - FakeKeyguardQuickAffordanceConfig( - BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET - ) {} + FakeKeyguardQuickAffordanceConfig( + BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET + ) qrCodeScannerAffordanceConfig = - object : - FakeKeyguardQuickAffordanceConfig( - BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER - ) {} + FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER) registry = FakeKeyguardQuickAffordanceRegistry( mapOf( @@ -116,6 +115,18 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { whenever(userTracker.userHandle).thenReturn(mock()) whenever(lockPatternUtils.getStrongAuthForUser(anyInt())) .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED) + val quickAffordanceRepository = + KeyguardQuickAffordanceRepository( + scope = CoroutineScope(IMMEDIATE), + backgroundDispatcher = IMMEDIATE, + selectionManager = KeyguardQuickAffordanceSelectionManager(), + configs = + setOf( + homeControlsQuickAffordanceConfig, + quickAccessWalletAffordanceConfig, + qrCodeScannerAffordanceConfig, + ), + ) underTest = KeyguardBottomAreaViewModel( keyguardInteractor = keyguardInteractor, @@ -127,6 +138,11 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { keyguardStateController = keyguardStateController, userTracker = userTracker, activityStarter = activityStarter, + featureFlags = + FakeFeatureFlags().apply { + set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false) + }, + repository = { quickAffordanceRepository }, ), bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository), burnInHelperWrapper = burnInHelperWrapper, @@ -576,5 +592,6 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { companion object { private const val DEFAULT_BURN_IN_OFFSET = 5 private const val RETURNED_BURN_IN_OFFSET = 3 + private val IMMEDIATE = Dispatchers.Main.immediate } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt index c8e8943689c9..6ca34e0bb7ce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt @@ -49,6 +49,7 @@ import javax.inject.Provider import junit.framework.Assert.assertEquals import junit.framework.Assert.assertTrue import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor @@ -119,6 +120,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { MediaPlayerData.clear() } + @Ignore("b/253229241") @Test fun testPlayerOrdering() { // Test values: key, data, last active time @@ -295,6 +297,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { } } + @Ignore("b/253229241") @Test fun testOrderWithSmartspace_prioritized() { testPlayerOrdering() @@ -312,6 +315,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec) } + @Ignore("b/253229241") @Test fun testOrderWithSmartspace_prioritized_updatingVisibleMediaPlayers() { testPlayerOrdering() @@ -328,6 +332,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(2).isSsMediaRec) } + @Ignore("b/253229241") @Test fun testOrderWithSmartspace_notPrioritized() { testPlayerOrdering() @@ -346,6 +351,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { assertTrue(MediaPlayerData.playerKeys().elementAt(idx).isSsMediaRec) } + @Ignore("b/253229241") @Test fun testPlayingExistingMediaPlayerFromCarousel_visibleMediaPlayersNotUpdated() { testPlayerOrdering() @@ -382,6 +388,8 @@ class MediaCarouselControllerTest : SysuiTestCase() { MediaPlayerData.playerKeys().elementAt(0) ) } + + @Ignore("b/253229241") @Test fun testSwipeDismiss_logged() { mediaCarouselController.mediaCarouselScrollHandler.dismissCallback.invoke() @@ -389,6 +397,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(logger).logSwipeDismiss() } + @Ignore("b/253229241") @Test fun testSettingsButton_logged() { mediaCarouselController.settingsButton.callOnClick() @@ -396,6 +405,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(logger).logCarouselSettings() } + @Ignore("b/253229241") @Test fun testLocationChangeQs_logged() { mediaCarouselController.onDesiredLocationChanged( @@ -406,6 +416,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QS) } + @Ignore("b/253229241") @Test fun testLocationChangeQqs_logged() { mediaCarouselController.onDesiredLocationChanged( @@ -416,6 +427,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QQS) } + @Ignore("b/253229241") @Test fun testLocationChangeLockscreen_logged() { mediaCarouselController.onDesiredLocationChanged( @@ -426,6 +438,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_LOCKSCREEN) } + @Ignore("b/253229241") @Test fun testLocationChangeDream_logged() { mediaCarouselController.onDesiredLocationChanged( @@ -436,6 +449,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_DREAM_OVERLAY) } + @Ignore("b/253229241") @Test fun testRecommendationRemoved_logged() { val packageName = "smartspace package" @@ -449,6 +463,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!)) } + @Ignore("b/253229241") @Test fun testMediaLoaded_ScrollToActivePlayer() { listener.value.onMediaDataLoaded( @@ -506,6 +521,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { ) } + @Ignore("b/253229241") @Test fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() { listener.value.onSmartspaceMediaDataLoaded( @@ -549,6 +565,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { assertEquals(playerIndex, 0) } + @Ignore("b/253229241") @Test fun testRecommendationRemovedWhileNotVisible_updateHostVisibility() { var result = false @@ -560,6 +577,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { assertEquals(true, result) } + @Ignore("b/253229241") @Test fun testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibility() { var result = false @@ -573,6 +591,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { assertEquals(true, result) } + @Ignore("b/253229241") @Test fun testGetCurrentVisibleMediaContentIntent() { val clickIntent1 = mock(PendingIntent::class.java) @@ -619,6 +638,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2) } + @Ignore("b/253229241") @Test fun testSetCurrentState_UpdatePageIndicatorAlphaWhenSquish() { val delta = 0.0001F 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 f20c6a29b840..9758842d1e35 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt @@ -25,8 +25,8 @@ import androidx.test.runner.AndroidJUnit4 import com.android.systemui.SysuiTestCase import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.NOTES_ACTION import com.android.systemui.util.mockito.whenever -import com.android.wm.shell.floating.FloatingTasks -import java.util.* +import com.android.wm.shell.bubbles.Bubbles +import java.util.Optional import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -49,8 +49,8 @@ internal class NoteTaskControllerTest : SysuiTestCase() { @Mock lateinit var context: Context @Mock lateinit var noteTaskIntentResolver: NoteTaskIntentResolver - @Mock lateinit var floatingTasks: FloatingTasks - @Mock lateinit var optionalFloatingTasks: Optional<FloatingTasks> + @Mock lateinit var bubbles: Bubbles + @Mock lateinit var optionalBubbles: Optional<Bubbles> @Mock lateinit var keyguardManager: KeyguardManager @Mock lateinit var optionalKeyguardManager: Optional<KeyguardManager> @Mock lateinit var optionalUserManager: Optional<UserManager> @@ -61,7 +61,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(notesIntent) - whenever(optionalFloatingTasks.orElse(null)).thenReturn(floatingTasks) + whenever(optionalBubbles.orElse(null)).thenReturn(bubbles) whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager) whenever(optionalUserManager.orElse(null)).thenReturn(userManager) whenever(userManager.isUserUnlocked).thenReturn(true) @@ -71,7 +71,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { return NoteTaskController( context = context, intentResolver = noteTaskIntentResolver, - optionalFloatingTasks = optionalFloatingTasks, + optionalBubbles = optionalBubbles, optionalKeyguardManager = optionalKeyguardManager, optionalUserManager = optionalUserManager, isEnabled = isEnabled, @@ -85,16 +85,16 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1) verify(context).startActivity(notesIntent) - verify(floatingTasks, never()).showOrSetStashed(notesIntent) + verify(bubbles, never()).showAppBubble(notesIntent) } @Test - fun handleSystemKey_keyguardIsUnlocked_shouldStartFloatingTask() { + fun handleSystemKey_keyguardIsUnlocked_shouldStartBubbles() { whenever(keyguardManager.isKeyguardLocked).thenReturn(false) createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1) - verify(floatingTasks).showOrSetStashed(notesIntent) + verify(bubbles).showAppBubble(notesIntent) verify(context, never()).startActivity(notesIntent) } @@ -103,17 +103,17 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_UNKNOWN) verify(context, never()).startActivity(notesIntent) - verify(floatingTasks, never()).showOrSetStashed(notesIntent) + verify(bubbles, never()).showAppBubble(notesIntent) } @Test - fun handleSystemKey_floatingTasksIsNull_shouldDoNothing() { - whenever(optionalFloatingTasks.orElse(null)).thenReturn(null) + fun handleSystemKey_bubblesIsNull_shouldDoNothing() { + whenever(optionalBubbles.orElse(null)).thenReturn(null) createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1) verify(context, never()).startActivity(notesIntent) - verify(floatingTasks, never()).showOrSetStashed(notesIntent) + verify(bubbles, never()).showAppBubble(notesIntent) } @Test @@ -123,7 +123,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1) verify(context, never()).startActivity(notesIntent) - verify(floatingTasks, never()).showOrSetStashed(notesIntent) + verify(bubbles, never()).showAppBubble(notesIntent) } @Test @@ -133,7 +133,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1) verify(context, never()).startActivity(notesIntent) - verify(floatingTasks, never()).showOrSetStashed(notesIntent) + verify(bubbles, never()).showAppBubble(notesIntent) } @Test @@ -143,7 +143,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1) verify(context, never()).startActivity(notesIntent) - verify(floatingTasks, never()).showOrSetStashed(notesIntent) + verify(bubbles, never()).showAppBubble(notesIntent) } @Test @@ -151,7 +151,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController(isEnabled = false).handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1) verify(context, never()).startActivity(notesIntent) - verify(floatingTasks, never()).showOrSetStashed(notesIntent) + verify(bubbles, never()).showAppBubble(notesIntent) } @Test @@ -161,6 +161,6 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1) verify(context, never()).startActivity(notesIntent) - verify(floatingTasks, never()).showOrSetStashed(notesIntent) + verify(bubbles, never()).showAppBubble(notesIntent) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt index f344c8d9eec4..334089c43e27 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt @@ -21,8 +21,8 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.CommandQueue import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever -import com.android.wm.shell.floating.FloatingTasks -import java.util.* +import com.android.wm.shell.bubbles.Bubbles +import java.util.Optional import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -43,20 +43,20 @@ import org.mockito.MockitoAnnotations internal class NoteTaskInitializerTest : SysuiTestCase() { @Mock lateinit var commandQueue: CommandQueue - @Mock lateinit var floatingTasks: FloatingTasks - @Mock lateinit var optionalFloatingTasks: Optional<FloatingTasks> + @Mock lateinit var bubbles: Bubbles + @Mock lateinit var optionalBubbles: Optional<Bubbles> @Before fun setUp() { MockitoAnnotations.initMocks(this) - whenever(optionalFloatingTasks.isPresent).thenReturn(true) - whenever(optionalFloatingTasks.orElse(null)).thenReturn(floatingTasks) + whenever(optionalBubbles.isPresent).thenReturn(true) + whenever(optionalBubbles.orElse(null)).thenReturn(bubbles) } private fun createNoteTaskInitializer(isEnabled: Boolean = true): NoteTaskInitializer { return NoteTaskInitializer( - optionalFloatingTasks = optionalFloatingTasks, + optionalBubbles = optionalBubbles, lazyNoteTaskController = mock(), commandQueue = commandQueue, isEnabled = isEnabled, @@ -78,8 +78,8 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { } @Test - fun initialize_floatingTasksNotPresent_shouldDoNothing() { - whenever(optionalFloatingTasks.isPresent).thenReturn(false) + fun initialize_bubblesNotPresent_shouldDoNothing() { + whenever(optionalBubbles.isPresent).thenReturn(false) createNoteTaskInitializer().initialize() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt index 09add652483e..43c694245eba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt @@ -66,6 +66,8 @@ class PulsingGestureListenerTest : SysuiTestCase() { private lateinit var dumpManager: DumpManager @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock + private lateinit var shadeLogger: ShadeLogger private lateinit var tunableCaptor: ArgumentCaptor<Tunable> private lateinit var underTest: PulsingGestureListener @@ -81,6 +83,7 @@ class PulsingGestureListenerTest : SysuiTestCase() { centralSurfaces, ambientDisplayConfiguration, statusBarStateController, + shadeLogger, tunerService, dumpManager ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt index f96c39f007dd..aa1114b8736e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt @@ -34,6 +34,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback +import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider import com.android.systemui.statusbar.notification.collection.render.NodeController import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider @@ -86,6 +87,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { private val mRemoteInputManager: NotificationRemoteInputManager = mock() private val mEndLifetimeExtension: OnEndLifetimeExtensionCallback = mock() private val mHeaderController: NodeController = mock() + private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider = mock() private lateinit var mEntry: NotificationEntry private lateinit var mGroupSummary: NotificationEntry @@ -110,6 +112,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { mHeadsUpViewBinder, mNotificationInterruptStateProvider, mRemoteInputManager, + mLaunchFullScreenIntentProvider, mHeaderController, mExecutor) mCoordinator.attach(mNotifPipeline) @@ -242,6 +245,20 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { } @Test + fun testOnEntryAdded_shouldFullScreen() { + setShouldFullScreen(mEntry) + mCollectionListener.onEntryAdded(mEntry) + verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry) + } + + @Test + fun testOnEntryAdded_shouldNotFullScreen() { + setShouldFullScreen(mEntry, should = false) + mCollectionListener.onEntryAdded(mEntry) + verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) + } + + @Test fun testPromotesAddedHUN() { // GIVEN the current entry should heads up whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true) @@ -794,6 +811,11 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { .thenReturn(should) } + private fun setShouldFullScreen(entry: NotificationEntry, should: Boolean = true) { + whenever(mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .thenReturn(should) + } + private fun finishBind(entry: NotificationEntry) { verify(mHeadsUpManager, never()).showNotification(entry) withArgCaptor<BindCallback> { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index 137842ef314f..12cc11450ede 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -232,7 +232,6 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test public void testUserLockedResetEvenWhenNoChildren() { mGroupRow.setUserLocked(true); - mGroupRow.removeAllChildren(); mGroupRow.setUserLocked(false); assertFalse("The childrencontainer should not be userlocked but is, the state " + "seems out of sync.", mGroupRow.getChildrenContainer().isUserLocked()); @@ -240,12 +239,11 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test public void testReinflatedOnDensityChange() { - mGroupRow.setUserLocked(true); - mGroupRow.removeAllChildren(); - mGroupRow.setUserLocked(false); NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class); - mGroupRow.setChildrenContainer(mockContainer); - mGroupRow.onDensityOrFontScaleChanged(); + mNotifRow.setChildrenContainer(mockContainer); + + mNotifRow.onDensityOrFontScaleChanged(); + verify(mockContainer).reInflateViews(any(), any()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index 6fa217415044..b17747ad2d92 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -143,7 +143,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController, mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper); mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); - mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener); + mBiometricUnlockController.addBiometricModeListener(mBiometricModeListener); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java index 9c56c2670c63..6fb68938b00d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java @@ -45,7 +45,7 @@ import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconStat import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags; import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter; -import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel; +import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter; import com.android.systemui.utils.leaks.LeakCheckedTest; import org.junit.Before; @@ -80,7 +80,7 @@ public class StatusBarIconControllerTest extends LeakCheckedTest { layout, StatusBarLocation.HOME, mock(StatusBarPipelineFlags.class), - mock(WifiViewModel.class), + mock(WifiUiAdapter.class), mock(MobileUiAdapter.class), mMobileContextProvider, mock(DarkIconDispatcher.class)); @@ -124,14 +124,14 @@ public class StatusBarIconControllerTest extends LeakCheckedTest { LinearLayout group, StatusBarLocation location, StatusBarPipelineFlags statusBarPipelineFlags, - WifiViewModel wifiViewModel, + WifiUiAdapter wifiUiAdapter, MobileUiAdapter mobileUiAdapter, MobileContextProvider contextProvider, DarkIconDispatcher darkIconDispatcher) { super(group, location, statusBarPipelineFlags, - wifiViewModel, + wifiUiAdapter, mobileUiAdapter, contextProvider, darkIconDispatcher); @@ -172,7 +172,7 @@ public class StatusBarIconControllerTest extends LeakCheckedTest { super(group, StatusBarLocation.HOME, mock(StatusBarPipelineFlags.class), - mock(WifiViewModel.class), + mock(WifiUiAdapter.class), mock(MobileUiAdapter.class), contextProvider); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 716666657871..ec8d71136452 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -306,17 +306,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test - public void onPanelExpansionChanged_neverTranslatesBouncerWhenLaunchingApp() { - when(mNotificationPanelView.isLaunchTransitionFinished()).thenReturn(true); - mStatusBarKeyguardViewManager.onPanelExpansionChanged( - expansionEvent( - /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE, - /* expanded= */ true, - /* tracking= */ false)); - verify(mBouncer, never()).setExpansion(anyFloat()); - } - - @Test public void onPanelExpansionChanged_neverTranslatesBouncerWhenShadeLocked() { when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED); mStatusBarKeyguardViewManager.onPanelExpansionChanged( @@ -361,7 +350,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Test public void setOccluded_isInLaunchTransition_onKeyguardOccludedChangedCalled() { - when(mNotificationPanelView.isLaunchTransitionFinished()).thenReturn(true); mStatusBarKeyguardViewManager.show(null); mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index c4098578197b..ce54d784520c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -67,8 +67,8 @@ import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider; -import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -123,8 +123,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { @Mock private ShadeControllerImpl mShadeController; @Mock - private NotifPipeline mNotifPipeline; - @Mock private NotificationVisibilityProvider mVisibilityProvider; @Mock private ActivityIntentHelper mActivityIntentHelper; @@ -197,7 +195,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { getContext(), mHandler, mUiBgExecutor, - mNotifPipeline, mVisibilityProvider, headsUpManager, mActivityStarter, @@ -222,7 +219,8 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { mock(NotificationPresenter.class), mock(NotificationPanelViewController.class), mActivityLaunchAnimator, - notificationAnimationProvider + notificationAnimationProvider, + mock(LaunchFullScreenIntentProvider.class) ); // set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg @@ -384,11 +382,9 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { NotificationEntry entry = mock(NotificationEntry.class); when(entry.getImportance()).thenReturn(NotificationManager.IMPORTANCE_HIGH); when(entry.getSbn()).thenReturn(sbn); - when(mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(eq(entry))) - .thenReturn(true); // WHEN - mNotificationActivityStarter.handleFullScreenIntent(entry); + mNotificationActivityStarter.launchFullScreenIntent(entry); // THEN display should try wake up for the full screen intent verify(mCentralSurfaces).wakeUpForFullScreenIntent(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt index c5841098010a..37457b308597 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt @@ -28,7 +28,6 @@ import com.android.systemui.lifecycle.InstantTaskExecutorRule import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON -import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor @@ -40,6 +39,7 @@ import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants +import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope @@ -70,7 +70,7 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { private lateinit var connectivityRepository: FakeConnectivityRepository private lateinit var wifiRepository: FakeWifiRepository private lateinit var interactor: WifiInteractor - private lateinit var viewModel: WifiViewModel + private lateinit var viewModel: LocationBasedWifiViewModel private lateinit var scope: CoroutineScope private lateinit var airplaneModeViewModel: AirplaneModeViewModel @@ -105,23 +105,19 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { scope, statusBarPipelineFlags, wifiConstants, - ) + ).home } @Test fun constructAndBind_hasCorrectSlot() { - val view = ModernStatusBarWifiView.constructAndBind( - context, "slotName", viewModel, StatusBarLocation.HOME - ) + val view = ModernStatusBarWifiView.constructAndBind(context, "slotName", viewModel) assertThat(view.slot).isEqualTo("slotName") } @Test fun getVisibleState_icon_returnsIcon() { - val view = ModernStatusBarWifiView.constructAndBind( - context, SLOT_NAME, viewModel, StatusBarLocation.HOME - ) + val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) view.setVisibleState(STATE_ICON, /* animate= */ false) @@ -130,9 +126,7 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { @Test fun getVisibleState_dot_returnsDot() { - val view = ModernStatusBarWifiView.constructAndBind( - context, SLOT_NAME, viewModel, StatusBarLocation.HOME - ) + val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) view.setVisibleState(STATE_DOT, /* animate= */ false) @@ -141,9 +135,7 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { @Test fun getVisibleState_hidden_returnsHidden() { - val view = ModernStatusBarWifiView.constructAndBind( - context, SLOT_NAME, viewModel, StatusBarLocation.HOME - ) + val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) view.setVisibleState(STATE_HIDDEN, /* animate= */ false) @@ -155,9 +147,7 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { @Test fun setVisibleState_icon_iconShownDotHidden() { - val view = ModernStatusBarWifiView.constructAndBind( - context, SLOT_NAME, viewModel, StatusBarLocation.HOME - ) + val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) view.setVisibleState(STATE_ICON, /* animate= */ false) @@ -172,9 +162,7 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { @Test fun setVisibleState_dot_iconHiddenDotShown() { - val view = ModernStatusBarWifiView.constructAndBind( - context, SLOT_NAME, viewModel, StatusBarLocation.HOME - ) + val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) view.setVisibleState(STATE_DOT, /* animate= */ false) @@ -189,9 +177,7 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { @Test fun setVisibleState_hidden_iconAndDotHidden() { - val view = ModernStatusBarWifiView.constructAndBind( - context, SLOT_NAME, viewModel, StatusBarLocation.HOME - ) + val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) view.setVisibleState(STATE_HIDDEN, /* animate= */ false) @@ -211,9 +197,7 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2) ) - val view = ModernStatusBarWifiView.constructAndBind( - context, SLOT_NAME, viewModel, StatusBarLocation.HOME - ) + val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) ViewUtils.attachView(view) testableLooper.processAllMessages() @@ -230,9 +214,7 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2) ) - val view = ModernStatusBarWifiView.constructAndBind( - context, SLOT_NAME, viewModel, StatusBarLocation.HOME - ) + val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) ViewUtils.attachView(view) testableLooper.processAllMessages() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java index 4f1fb02ecdcd..26df03f31b9a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.policy; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -25,6 +26,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.net.TetheringManager; @@ -36,6 +38,7 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; @@ -96,6 +99,9 @@ public class HotspotControllerImplTest extends SysuiTestCase { }).when(mWifiManager).registerSoftApCallback(any(Executor.class), any(WifiManager.SoftApCallback.class)); + mContext.getOrCreateTestableResources() + .addOverride(R.bool.config_show_wifi_tethering, true); + Handler handler = new Handler(mLooper.getLooper()); mController = new HotspotControllerImpl(mContext, handler, handler, mDumpManager); @@ -176,4 +182,18 @@ public class HotspotControllerImplTest extends SysuiTestCase { verify(mCallback1).onHotspotAvailabilityChanged(false); } + + @Test + public void testHotspotSupported_resource_false() { + mContext.getOrCreateTestableResources() + .addOverride(R.bool.config_show_wifi_tethering, false); + + Handler handler = new Handler(mLooper.getLooper()); + + HotspotController controller = + new HotspotControllerImpl(mContext, handler, handler, mDumpManager); + + verifyNoMoreInteractions(mTetheringManager); + assertFalse(controller.isHotspotSupported()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt index 120bf791c462..b485693af50f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt @@ -23,6 +23,8 @@ import android.os.UserHandle import android.os.UserManager import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger +import com.android.systemui.GuestResetOrExitSessionReceiver +import com.android.systemui.GuestResumeSessionReceiver import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.user.data.repository.FakeUserRepository @@ -55,6 +57,8 @@ class GuestUserInteractorTest : SysuiTestCase() { @Mock private lateinit var dismissDialog: () -> Unit @Mock private lateinit var selectUser: (Int) -> Unit @Mock private lateinit var switchUser: (Int) -> Unit + @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver + @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver private lateinit var underTest: GuestUserInteractor @@ -87,10 +91,18 @@ class GuestUserInteractorTest : SysuiTestCase() { repository = repository, ), uiEventLogger = uiEventLogger, + resumeSessionReceiver = resumeSessionReceiver, + resetOrExitSessionReceiver = resetOrExitSessionReceiver, ) } @Test + fun `registers broadcast receivers`() { + verify(resumeSessionReceiver).register() + verify(resetOrExitSessionReceiver).register() + } + + @Test fun `onDeviceBootCompleted - allowed to add - create guest`() = runBlocking(IMMEDIATE) { setAllowedToAdd() diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt index 1680c36cef87..58f55314c1b6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt @@ -21,6 +21,8 @@ import android.app.ActivityManager import android.app.admin.DevicePolicyManager import android.os.UserManager import com.android.internal.logging.UiEventLogger +import com.android.systemui.GuestResetOrExitSessionReceiver +import com.android.systemui.GuestResumeSessionReceiver import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags @@ -48,6 +50,8 @@ abstract class UserInteractorTest : SysuiTestCase() { @Mock protected lateinit var devicePolicyManager: DevicePolicyManager @Mock protected lateinit var uiEventLogger: UiEventLogger @Mock protected lateinit var dialogShower: UserSwitchDialogController.DialogShower + @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver + @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver protected lateinit var underTest: UserInteractor @@ -107,6 +111,8 @@ abstract class UserInteractorTest : SysuiTestCase() { devicePolicyManager = devicePolicyManager, refreshUsersScheduler = refreshUsersScheduler, uiEventLogger = uiEventLogger, + resumeSessionReceiver = resumeSessionReceiver, + resetOrExitSessionReceiver = resetOrExitSessionReceiver, ) ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt index c12a868dbaed..116023aca655 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt @@ -23,6 +23,8 @@ import android.graphics.drawable.Drawable import android.os.UserManager import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger +import com.android.systemui.GuestResetOrExitSessionReceiver +import com.android.systemui.GuestResumeSessionReceiver import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Text import com.android.systemui.flags.FakeFeatureFlags @@ -69,6 +71,8 @@ class UserSwitcherViewModelTest : SysuiTestCase() { @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController @Mock private lateinit var devicePolicyManager: DevicePolicyManager @Mock private lateinit var uiEventLogger: UiEventLogger + @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver + @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver private lateinit var underTest: UserSwitcherViewModel @@ -104,6 +108,8 @@ class UserSwitcherViewModelTest : SysuiTestCase() { devicePolicyManager = devicePolicyManager, refreshUsersScheduler = refreshUsersScheduler, uiEventLogger = uiEventLogger, + resumeSessionReceiver = resumeSessionReceiver, + resetOrExitSessionReceiver = resetOrExitSessionReceiver, ) underTest = diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index fa7ebf6a2449..bee882d43849 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -86,6 +86,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -394,6 +395,7 @@ public class BubblesTest extends SysuiTestCase { mCommonNotifCollection, mNotifPipeline, mSysUiState, + mock(FeatureFlags.class), syncExecutor); mBubblesManager.addNotifCallback(mNotifCallback); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java index 34c83bd41a02..d47e88fc9385 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java @@ -39,6 +39,7 @@ public class FalsingManagerFake implements FalsingManager { private boolean mShouldEnforceBouncer; private boolean mIsReportingEnabled; private boolean mIsFalseRobustTap; + private boolean mIsFalseLongTap; private boolean mDestroyed; private boolean mIsProximityNear; @@ -87,6 +88,10 @@ public class FalsingManagerFake implements FalsingManager { mIsProximityNear = proxNear; } + public void setFalseLongTap(boolean falseLongTap) { + mIsFalseLongTap = falseLongTap; + } + @Override public boolean isSimpleTap() { checkDestroyed(); @@ -100,6 +105,12 @@ public class FalsingManagerFake implements FalsingManager { } @Override + public boolean isFalseLongTap(int penalty) { + checkDestroyed(); + return mIsFalseLongTap; + } + + @Override public boolean isFalseDoubleTap() { checkDestroyed(); return mIsFalseDoubleTap; diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 11178db8afd2..627bd096143e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.data.repository import com.android.systemui.common.shared.model.Position +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.WakefulnessModel import kotlinx.coroutines.flow.Flow @@ -52,6 +53,12 @@ class FakeKeyguardRepository : KeyguardRepository { private val _wakefulnessState = MutableStateFlow(WakefulnessModel.ASLEEP) override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState + private val _isBouncerShowing = MutableStateFlow(false) + override val isBouncerShowing: Flow<Boolean> = _isBouncerShowing + + private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE) + override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState + override fun isKeyguardShowing(): Boolean { return _isKeyguardShowing.value } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java index 23c7a6139de8..2d6d29a50a74 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java @@ -62,7 +62,11 @@ public class FakeStatusBarIconController extends BaseLeakChecker<IconManager> } @Override - public void setSignalIcon(String slot, WifiIconState state) { + public void setWifiIcon(String slot, WifiIconState state) { + } + + @Override + public void setNewWifiIcon() { } @Override diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 252dcfc5eb9e..4430bb4b3292 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -26,6 +26,7 @@ import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -254,11 +255,13 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private boolean mSafeMode; private int mMaxWidgetBitmapMemory; private boolean mIsProviderInfoPersisted; + private boolean mIsCombinedBroadcastEnabled; AppWidgetServiceImpl(Context context) { mContext = context; } + @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public void onStart() { mPackageManager = AppGlobals.getPackageManager(); mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); @@ -277,6 +280,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku mIsProviderInfoPersisted = !ActivityManager.isLowRamDeviceStatic() && DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.PERSISTS_WIDGET_PROVIDER_INFO, true); + mIsCombinedBroadcastEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.COMBINED_BROADCAST_ENABLED, true); if (DEBUG_PROVIDER_INFO_CACHE && !mIsProviderInfoPersisted) { Slog.d(TAG, "App widget provider info will not be persisted on this device"); } @@ -1123,16 +1128,16 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku final int widgetCount = provider.widgets.size(); if (widgetCount == 1) { - // Tell the provider that it's ready. - sendEnableIntentLocked(provider); + // If we are binding the very first widget from a provider, we will send + // a combined broadcast or 2 separate broadcasts to tell the provider that + // it's ready, and we need them to provide the update now. + sendEnableAndUpdateIntentLocked(provider, new int[]{appWidgetId}); + } else { + // For any widget other then the first one, we just send update intent + // as we normally would. + sendUpdateIntentLocked(provider, new int[]{appWidgetId}); } - // Send an update now -- We need this update now, and just for this appWidgetId. - // It's less critical when the next one happens, so when we schedule the next one, - // we add updatePeriodMillis to its start time. That time will have some slop, - // but that's okay. - sendUpdateIntentLocked(provider, new int[] {appWidgetId}); - // Schedule the future updates. registerForBroadcastsLocked(provider, getWidgetIds(provider.widgets)); @@ -2361,6 +2366,22 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku cancelBroadcastsLocked(provider); } + private void sendEnableAndUpdateIntentLocked(@NonNull Provider p, int[] appWidgetIds) { + final boolean canSendCombinedBroadcast = mIsCombinedBroadcastEnabled && p.info != null + && p.info.isExtendedFromAppWidgetProvider; + if (!canSendCombinedBroadcast) { + // If this function is called by mistake, send two separate broadcasts instead + sendEnableIntentLocked(p); + sendUpdateIntentLocked(p, appWidgetIds); + return; + } + + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); + intent.setComponent(p.id.componentName); + sendBroadcastAsUser(intent, p.id.getProfile()); + } + private void sendEnableIntentLocked(Provider p) { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED); intent.setComponent(p.id.componentName); @@ -2852,7 +2873,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku if (provider.widgets.size() > 0) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "appwidget init " + provider.id.componentName.getPackageName()); - sendEnableIntentLocked(provider); provider.widgets.forEach(widget -> { widget.trackingUpdate = true; Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, @@ -2861,7 +2881,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku Log.i(TAG, "Widget update scheduled on unlock " + widget.toString()); }); int[] appWidgetIds = getWidgetIds(provider.widgets); - sendUpdateIntentLocked(provider, appWidgetIds); + sendEnableAndUpdateIntentLocked(provider, appWidgetIds); registerForBroadcastsLocked(provider, appWidgetIds); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index df5113b16f49..85d6f293852c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2886,10 +2886,13 @@ public class ActivityManagerService extends IActivityManager.Stub || event == Event.ACTIVITY_DESTROYED)) { contentCaptureService.notifyActivityEvent(userId, activity, event); } - // TODO(b/201234353): Move the logic to client side. - if (mVoiceInteractionManagerProvider != null && (event == Event.ACTIVITY_PAUSED - || event == Event.ACTIVITY_RESUMED || event == Event.ACTIVITY_STOPPED)) { - mVoiceInteractionManagerProvider.notifyActivityEventChanged(); + // Currently we have move most of logic to the client side. When the activity lifecycle + // event changed, the client side will notify the VoiceInteractionManagerService. But + // when the application process died, the VoiceInteractionManagerService will miss the + // activity lifecycle event changed, so we still need ACTIVITY_DESTROYED event here to + // know if the activity has been destroyed. + if (mVoiceInteractionManagerProvider != null && event == Event.ACTIVITY_DESTROYED) { + mVoiceInteractionManagerProvider.notifyActivityDestroyed(appToken); } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index d2ba9c6dbfe4..53fcf3293525 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -41,6 +41,7 @@ import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.ActivityThread; import android.app.AlarmManager; import android.app.AppGlobals; import android.app.AppOpsManager; @@ -152,6 +153,7 @@ import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.os.VibratorManager; +import android.provider.DeviceConfig; import android.provider.Settings; import android.provider.Settings.System; import android.service.notification.ZenModeConfig; @@ -173,6 +175,7 @@ import android.widget.Toast; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; import com.android.server.EventLogTags; @@ -231,6 +234,7 @@ public class AudioService extends IAudioService.Stub AudioSystemAdapter.OnVolRangeInitRequestListener { private static final String TAG = "AS.AudioService"; + private static final boolean CONFIG_DEFAULT_VAL = false; private final AudioSystemAdapter mAudioSystem; private final SystemServerAdapter mSystemServer; @@ -981,6 +985,7 @@ public class AudioService extends IAudioService.Stub * @param looper Looper to use for the service's message handler. If this is null, an * {@link AudioSystemThread} is created as the messaging thread instead. */ + @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) public AudioService(Context context, AudioSystemAdapter audioSystem, SystemServerAdapter systemServer, SettingsAdapter settings, @Nullable Looper looper, AppOpsManager appOps) { @@ -1020,8 +1025,12 @@ public class AudioService extends IAudioService.Stub mUseVolumeGroupAliases = mContext.getResources().getBoolean( com.android.internal.R.bool.config_handleVolumeAliasesUsingVolumeGroups); - mNotifAliasRing = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_alias_ring_notif_stream_types); + mNotifAliasRing = !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false); + + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, + ActivityThread.currentApplication().getMainExecutor(), + this::onDeviceConfigChange); // Initialize volume // Priority 1 - Android Property @@ -1240,6 +1249,22 @@ public class AudioService extends IAudioService.Stub } /** + * Separating notification volume from ring is NOT of aliasing the corresponding streams + * @param properties + */ + private void onDeviceConfigChange(DeviceConfig.Properties properties) { + Set<String> changeSet = properties.getKeyset(); + if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) { + boolean newNotifAliasRing = !properties.getBoolean( + SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL); + if (mNotifAliasRing != newNotifAliasRing) { + mNotifAliasRing = newNotifAliasRing; + updateStreamVolumeAlias(true, TAG); + } + } + } + + /** * Called by handling of MSG_INIT_STREAMS_VOLUMES */ private void onInitStreamsAndVolumes() { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index ca4b7475f7dc..dbe971f32dc1 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -100,9 +100,7 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession> owner, cookie, requireConfirmation, sensorId, logger, biometricContext, isStrongBiometric, null /* taskStackListener */, lockoutCache, allowBackgroundAuthentication, - context.getResources().getBoolean( - com.android.internal.R.bool.system_server_plays_face_haptics) - /* shouldVibrate */, + false /* shouldVibrate */, isKeyguardBypassEnabled); setRequestId(requestId); mUsageStats = usageStats; diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java index 9baca98e4d2b..91eec7d19b3a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java @@ -74,7 +74,7 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, owner, cookie, requireConfirmation, sensorId, logger, biometricContext, isStrongBiometric, null /* taskStackListener */, - lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */, + lockoutTracker, allowBackgroundAuthentication, false /* shouldVibrate */, isKeyguardBypassEnabled); setRequestId(requestId); mUsageStats = usageStats; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index a778b573f225..9aecf783a881 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -69,7 +69,6 @@ import java.util.function.Supplier; class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> implements Udfps, LockoutConsumer, PowerPressHandler { private static final String TAG = "FingerprintAuthenticationClient"; - private static final int MESSAGE_IGNORE_AUTH = 1; private static final int MESSAGE_AUTH_SUCCESS = 2; private static final int MESSAGE_FINGER_UP = 3; @NonNull @@ -136,7 +135,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> taskStackListener, lockoutCache, allowBackgroundAuthentication, - true /* shouldVibrate */, + false /* shouldVibrate */, false /* isKeyguardBypassEnabled */); setRequestId(requestId); mLockoutCache = lockoutCache; @@ -235,12 +234,6 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> () -> { long delay = 0; if (authenticated && mSensorProps.isAnySidefpsType()) { - if (mHandler.hasMessages(MESSAGE_IGNORE_AUTH)) { - Slog.i(TAG, "(sideFPS) Ignoring auth due to recent power press"); - onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, - true); - return; - } delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp; if (mSideFpsLastAcquireStartTime != -1) { @@ -497,16 +490,12 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> if (mSensorProps.isAnySidefpsType()) { Slog.i(TAG, "(sideFPS): onPowerPressed"); mHandler.post(() -> { - if (mHandler.hasMessages(MESSAGE_AUTH_SUCCESS)) { - Slog.i(TAG, "(sideFPS): Ignoring auth in queue"); - mHandler.removeMessages(MESSAGE_AUTH_SUCCESS); - // Do not call onError() as that will send an additional callback to coex. - onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true); - } - mHandler.removeMessages(MESSAGE_IGNORE_AUTH); - mHandler.postDelayed(() -> { - }, MESSAGE_IGNORE_AUTH, mIgnoreAuthFor); - + Slog.i(TAG, "(sideFPS): finishing auth"); + // Ignore auths after a power has been detected + mHandler.removeMessages(MESSAGE_AUTH_SUCCESS); + // Do not call onError() as that will send an additional callback to coex. + onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true); + mSensorOverlays.hide(getSensorId()); }); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java index 7ed1a514f9bc..0d620fd3a9e4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java @@ -81,7 +81,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, owner, cookie, requireConfirmation, sensorId, logger, biometricContext, isStrongBiometric, taskStackListener, lockoutTracker, allowBackgroundAuthentication, - true /* shouldVibrate */, false /* isKeyguardBypassEnabled */); + false /* shouldVibrate */, false /* isKeyguardBypassEnabled */); setRequestId(requestId); mLockoutFrameworkImpl = lockoutTracker; mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 5589673973c3..e4aa5e574955 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -157,7 +157,7 @@ public final class DreamManagerService extends SystemService { mDozeConfig = new AmbientDisplayConfiguration(mContext); mUiEventLogger = new UiEventLoggerImpl(); mDreamUiEventLogger = new DreamUiEventLoggerImpl( - mContext.getResources().getString(R.string.config_loggable_dream_prefix)); + mContext.getResources().getStringArray(R.array.config_loggable_dream_prefixes)); AmbientDisplayConfiguration adc = new AmbientDisplayConfiguration(mContext); mAmbientDisplayComponent = ComponentName.unflattenFromString(adc.ambientDisplayComponent()); mDreamsOnlyEnabledForSystemUser = diff --git a/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java b/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java index 26ca74a8d808..96ebcbb6a771 100644 --- a/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java +++ b/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java @@ -26,10 +26,10 @@ import com.android.internal.util.FrameworkStatsLog; * @hide */ public class DreamUiEventLoggerImpl implements DreamUiEventLogger { - final String mLoggableDreamPrefix; + private final String[] mLoggableDreamPrefixes; - DreamUiEventLoggerImpl(String loggableDreamPrefix) { - mLoggableDreamPrefix = loggableDreamPrefix; + DreamUiEventLoggerImpl(String[] loggableDreamPrefixes) { + mLoggableDreamPrefixes = loggableDreamPrefixes; } @Override @@ -38,13 +38,20 @@ public class DreamUiEventLoggerImpl implements DreamUiEventLogger { if (eventID <= 0) { return; } - final boolean isFirstPartyDream = - mLoggableDreamPrefix.isEmpty() ? false : dreamComponentName.startsWith( - mLoggableDreamPrefix); FrameworkStatsLog.write(FrameworkStatsLog.DREAM_UI_EVENT_REPORTED, /* uid = 1 */ 0, /* event_id = 2 */ eventID, /* instance_id = 3 */ 0, - /* dream_component_name = 4 */ isFirstPartyDream ? dreamComponentName : "other"); + /* dream_component_name = 4 */ + isFirstPartyDream(dreamComponentName) ? dreamComponentName : "other"); + } + + private boolean isFirstPartyDream(String dreamComponentName) { + for (int i = 0; i < mLoggableDreamPrefixes.length; ++i) { + if (dreamComponentName.startsWith(mLoggableDreamPrefixes[i])) { + return true; + } + } + return false; } } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 7437b145189f..cfd029346340 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -456,19 +456,24 @@ public final class Settings implements Watchable, Snappable { // The user's preferred activities associated with particular intent // filters. @Watched - private final WatchedSparseArray<PreferredIntentResolver> - mPreferredActivities = new WatchedSparseArray<>(); + private final WatchedSparseArray<PreferredIntentResolver> mPreferredActivities; + private final SnapshotCache<WatchedSparseArray<PreferredIntentResolver>> + mPreferredActivitiesSnapshot; // The persistent preferred activities of the user's profile/device owner // associated with particular intent filters. @Watched private final WatchedSparseArray<PersistentPreferredIntentResolver> - mPersistentPreferredActivities = new WatchedSparseArray<>(); + mPersistentPreferredActivities; + private final SnapshotCache<WatchedSparseArray<PersistentPreferredIntentResolver>> + mPersistentPreferredActivitiesSnapshot; + // For every user, it is used to find to which other users the intent can be forwarded. @Watched - private final WatchedSparseArray<CrossProfileIntentResolver> - mCrossProfileIntentResolvers = new WatchedSparseArray<>(); + private final WatchedSparseArray<CrossProfileIntentResolver> mCrossProfileIntentResolvers; + private final SnapshotCache<WatchedSparseArray<CrossProfileIntentResolver>> + mCrossProfileIntentResolversSnapshot; @Watched final WatchedArrayMap<String, SharedUserSetting> mSharedUsers = new WatchedArrayMap<>(); @@ -477,11 +482,12 @@ public final class Settings implements Watchable, Snappable { // For reading/writing settings file. @Watched - private final WatchedArrayList<Signature> mPastSignatures = - new WatchedArrayList<Signature>(); + private final WatchedArrayList<Signature> mPastSignatures; + private final SnapshotCache<WatchedArrayList<Signature>> mPastSignaturesSnapshot; + @Watched - private final WatchedArrayMap<Long, Integer> mKeySetRefs = - new WatchedArrayMap<Long, Integer>(); + private final WatchedArrayMap<Long, Integer> mKeySetRefs; + private final SnapshotCache<WatchedArrayMap<Long, Integer>> mKeySetRefsSnapshot; // Packages that have been renamed since they were first installed. // Keys are the new names of the packages, values are the original @@ -512,7 +518,8 @@ public final class Settings implements Watchable, Snappable { * scanning to make it less confusing. */ @Watched - private final WatchedArrayList<PackageSetting> mPendingPackages = new WatchedArrayList<>(); + private final WatchedArrayList<PackageSetting> mPendingPackages; + private final SnapshotCache<WatchedArrayList<PackageSetting>> mPendingPackagesSnapshot; private final File mSystemDir; @@ -584,6 +591,26 @@ public final class Settings implements Watchable, Snappable { mInstallerPackagesSnapshot = new SnapshotCache.Auto<>(mInstallerPackages, mInstallerPackages, "Settings.mInstallerPackages"); + mPreferredActivities = new WatchedSparseArray<>(); + mPreferredActivitiesSnapshot = new SnapshotCache.Auto<>(mPreferredActivities, + mPreferredActivities, "Settings.mPreferredActivities"); + mPersistentPreferredActivities = new WatchedSparseArray<>(); + mPersistentPreferredActivitiesSnapshot = new SnapshotCache.Auto<>( + mPersistentPreferredActivities, mPersistentPreferredActivities, + "Settings.mPersistentPreferredActivities"); + mCrossProfileIntentResolvers = new WatchedSparseArray<>(); + mCrossProfileIntentResolversSnapshot = new SnapshotCache.Auto<>( + mCrossProfileIntentResolvers, mCrossProfileIntentResolvers, + "Settings.mCrossProfileIntentResolvers"); + mPastSignatures = new WatchedArrayList<>(); + mPastSignaturesSnapshot = new SnapshotCache.Auto<>(mPastSignatures, mPastSignatures, + "Settings.mPastSignatures"); + mKeySetRefs = new WatchedArrayMap<>(); + mKeySetRefsSnapshot = new SnapshotCache.Auto<>(mKeySetRefs, mKeySetRefs, + "Settings.mKeySetRefs"); + mPendingPackages = new WatchedArrayList<>(); + mPendingPackagesSnapshot = new SnapshotCache.Auto<>(mPendingPackages, mPendingPackages, + "Settings.mPendingPackages"); mKeySetManagerService = new KeySetManagerService(mPackages); // Test-only handler working on background thread. @@ -624,6 +651,26 @@ public final class Settings implements Watchable, Snappable { mInstallerPackagesSnapshot = new SnapshotCache.Auto<>(mInstallerPackages, mInstallerPackages, "Settings.mInstallerPackages"); + mPreferredActivities = new WatchedSparseArray<>(); + mPreferredActivitiesSnapshot = new SnapshotCache.Auto<>(mPreferredActivities, + mPreferredActivities, "Settings.mPreferredActivities"); + mPersistentPreferredActivities = new WatchedSparseArray<>(); + mPersistentPreferredActivitiesSnapshot = new SnapshotCache.Auto<>( + mPersistentPreferredActivities, mPersistentPreferredActivities, + "Settings.mPersistentPreferredActivities"); + mCrossProfileIntentResolvers = new WatchedSparseArray<>(); + mCrossProfileIntentResolversSnapshot = new SnapshotCache.Auto<>( + mCrossProfileIntentResolvers, mCrossProfileIntentResolvers, + "Settings.mCrossProfileIntentResolvers"); + mPastSignatures = new WatchedArrayList<>(); + mPastSignaturesSnapshot = new SnapshotCache.Auto<>(mPastSignatures, mPastSignatures, + "Settings.mPastSignatures"); + mKeySetRefs = new WatchedArrayMap<>(); + mKeySetRefsSnapshot = new SnapshotCache.Auto<>(mKeySetRefs, mKeySetRefs, + "Settings.mKeySetRefs"); + mPendingPackages = new WatchedArrayList<>(); + mPendingPackagesSnapshot = new SnapshotCache.Auto<>(mPendingPackages, mPendingPackages, + "Settings.mPendingPackages"); mKeySetManagerService = new KeySetManagerService(mPackages); mHandler = handler; @@ -700,24 +747,27 @@ public final class Settings implements Watchable, Snappable { mBlockUninstallPackages.snapshot(r.mBlockUninstallPackages); mVersion.putAll(r.mVersion); mVerifierDeviceIdentity = r.mVerifierDeviceIdentity; - WatchedSparseArray.snapshot( - mPreferredActivities, r.mPreferredActivities); - WatchedSparseArray.snapshot( - mPersistentPreferredActivities, r.mPersistentPreferredActivities); - WatchedSparseArray.snapshot( - mCrossProfileIntentResolvers, r.mCrossProfileIntentResolvers); + mPreferredActivities = r.mPreferredActivitiesSnapshot.snapshot(); + mPreferredActivitiesSnapshot = new SnapshotCache.Sealed<>(); + mPersistentPreferredActivities = r.mPersistentPreferredActivitiesSnapshot.snapshot(); + mPersistentPreferredActivitiesSnapshot = new SnapshotCache.Sealed<>(); + mCrossProfileIntentResolvers = r.mCrossProfileIntentResolversSnapshot.snapshot(); + mCrossProfileIntentResolversSnapshot = new SnapshotCache.Sealed<>(); + mSharedUsers.snapshot(r.mSharedUsers); mAppIds = r.mAppIds.snapshot(); - WatchedArrayList.snapshot( - mPastSignatures, r.mPastSignatures); - WatchedArrayMap.snapshot( - mKeySetRefs, r.mKeySetRefs); + + mPastSignatures = r.mPastSignaturesSnapshot.snapshot(); + mPastSignaturesSnapshot = new SnapshotCache.Sealed<>(); + mKeySetRefs = r.mKeySetRefsSnapshot.snapshot(); + mKeySetRefsSnapshot = new SnapshotCache.Sealed<>(); + mRenamedPackages.snapshot(r.mRenamedPackages); mNextAppLinkGeneration.snapshot(r.mNextAppLinkGeneration); mDefaultBrowserApp.snapshot(r.mDefaultBrowserApp); // mReadMessages - WatchedArrayList.snapshot( - mPendingPackages, r.mPendingPackages); + mPendingPackages = r.mPendingPackagesSnapshot.snapshot(); + mPendingPackagesSnapshot = new SnapshotCache.Sealed<>(); mSystemDir = null; // mKeySetManagerService; mPermissions = r.mPermissions; diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 2eb2cf643c42..ccab96888e8f 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -496,7 +496,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** The most recently given options. */ private ActivityOptions mPendingOptions; /** Non-null if {@link #mPendingOptions} specifies the remote animation. */ - private RemoteAnimationAdapter mPendingRemoteAnimation; + RemoteAnimationAdapter mPendingRemoteAnimation; private RemoteTransition mPendingRemoteTransition; ActivityOptions returningOptions; // options that are coming back via convertToTranslucent AppTimeTracker appTimeTracker; // set if we are tracking the time in this app/task/activity @@ -812,7 +812,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A StartingData mStartingData; WindowState mStartingWindow; StartingSurfaceController.StartingSurface mStartingSurface; - boolean startingDisplayed; boolean startingMoved; /** The last set {@link DropInputMode} for this activity surface. */ @@ -821,13 +820,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** Whether the input to this activity will be dropped during the current playing animation. */ private boolean mIsInputDroppedForAnimation; - /** - * If it is non-null, it requires all activities who have the same starting data to be drawn - * to remove the starting window. - * TODO(b/189385912): Remove starting window related fields after migrating them to task. - */ - private StartingData mSharedStartingData; - boolean mHandleExitSplashScreen; @TransferSplashScreenState int mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE; @@ -1200,14 +1192,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.print(" firstWindowDrawn="); pw.print(firstWindowDrawn); pw.print(" mIsExiting="); pw.println(mIsExiting); } - if (mSharedStartingData != null) { - pw.println(prefix + "mSharedStartingData=" + mSharedStartingData); - } - if (mStartingWindow != null || mStartingSurface != null - || startingDisplayed || startingMoved || mVisibleSetFromTransferredStartingWindow) { + if (mStartingWindow != null || mStartingData != null || mStartingSurface != null + || startingMoved || mVisibleSetFromTransferredStartingWindow) { pw.print(prefix); pw.print("startingWindow="); pw.print(mStartingWindow); pw.print(" startingSurface="); pw.print(mStartingSurface); - pw.print(" startingDisplayed="); pw.print(startingDisplayed); + pw.print(" startingDisplayed="); pw.print(isStartingWindowDisplayed()); pw.print(" startingMoved="); pw.print(startingMoved); pw.println(" mVisibleSetFromTransferredStartingWindow=" + mVisibleSetFromTransferredStartingWindow); @@ -2690,13 +2679,23 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + boolean isStartingWindowDisplayed() { + final StartingData data = mStartingData != null ? mStartingData : task != null + ? task.mSharedStartingData : null; + return data != null && data.mIsDisplayed; + } + /** Called when the starting window is added to this activity. */ void attachStartingWindow(@NonNull WindowState startingWindow) { startingWindow.mStartingData = mStartingData; mStartingWindow = startingWindow; - // The snapshot type may have called associateStartingDataWithTask(). - if (mStartingData != null && mStartingData.mAssociatedTask != null) { - attachStartingSurfaceToAssociatedTask(); + if (mStartingData != null) { + if (mStartingData.mAssociatedTask != null) { + // The snapshot type may have called associateStartingDataWithTask(). + attachStartingSurfaceToAssociatedTask(); + } else if (isEmbedded()) { + associateStartingWindowWithTaskIfNeeded(); + } } } @@ -2711,11 +2710,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** Called when the starting window is not added yet but its data is known to fill the task. */ private void associateStartingDataWithTask() { mStartingData.mAssociatedTask = task; - task.forAllActivities(r -> { - if (r.mVisibleRequested && !r.firstWindowDrawn) { - r.mSharedStartingData = mStartingData; - } - }); + task.mSharedStartingData = mStartingData; } /** Associates and attaches an added starting window to the current task. */ @@ -2746,10 +2741,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A void removeStartingWindowAnimation(boolean prepareAnimation) { mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE; - if (mSharedStartingData != null) { - mSharedStartingData.mAssociatedTask.forAllActivities(r -> { - r.mSharedStartingData = null; - }); + if (task != null) { + task.mSharedStartingData = null; } if (mStartingWindow == null) { if (mStartingData != null) { @@ -2772,7 +2765,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mStartingData = null; mStartingSurface = null; mStartingWindow = null; - startingDisplayed = false; if (surface == null) { ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "startingWindow was set but " + "startingSurface==null, couldn't remove"); @@ -4257,7 +4249,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * @return {@code true} if starting window is in app's hierarchy. */ boolean hasStartingWindow() { - if (startingDisplayed || mStartingData != null) { + if (mStartingData != null) { return true; } for (int i = mChildren.size() - 1; i >= 0; i--) { @@ -4355,10 +4347,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Transfer the starting window over to the new token. mStartingData = fromActivity.mStartingData; - mSharedStartingData = fromActivity.mSharedStartingData; mStartingSurface = fromActivity.mStartingSurface; - startingDisplayed = fromActivity.startingDisplayed; - fromActivity.startingDisplayed = false; mStartingWindow = tStartingWindow; reportedVisible = fromActivity.reportedVisible; fromActivity.mStartingData = null; @@ -4424,7 +4413,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Moving pending starting from %s to %s", fromActivity, this); mStartingData = fromActivity.mStartingData; - mSharedStartingData = fromActivity.mSharedStartingData; fromActivity.mStartingData = null; fromActivity.startingMoved = true; scheduleAddStartingWindow(); @@ -6534,14 +6522,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Remove starting window directly if is in a pure task. Otherwise if it is associated with // a task (e.g. nested task fragment), then remove only if all visible windows in the task // are drawn. - final Task associatedTask = - mSharedStartingData != null ? mSharedStartingData.mAssociatedTask : null; + final Task associatedTask = task.mSharedStartingData != null ? task : null; if (associatedTask == null) { removeStartingWindow(); - } else if (associatedTask.getActivity(r -> r.mVisibleRequested && !r.firstWindowDrawn - // Don't block starting window removal if an Activity can't be a starting window - // target. - && r.mSharedStartingData != null) == null) { + } else if (associatedTask.getActivity( + r -> r.mVisibleRequested && !r.firstWindowDrawn) == null) { // The last drawn activity may not be the one that owns the starting window. final ActivityRecord r = associatedTask.topActivityContainsStartingWindow(); if (r != null) { @@ -6756,7 +6741,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mLastTransactionSequence != mWmService.mTransactionSequence) { mLastTransactionSequence = mWmService.mTransactionSequence; mNumDrawnWindows = 0; - startingDisplayed = false; // There is the main base application window, even if it is exiting, wait for it mNumInterestingWindows = findMainWindow(false /* includeStartingApp */) != null ? 1 : 0; @@ -6800,9 +6784,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A isInterestingAndDrawn = true; } } - } else if (w.isDrawn()) { + } else if (mStartingData != null && w.isDrawn()) { // The starting window for this container is drawn. - startingDisplayed = true; + mStartingData.mIsDisplayed = true; } } @@ -7550,7 +7534,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ProtoLog.v(WM_DEBUG_ANIM, "Animation done in %s" + ": reportedVisible=%b okToDisplay=%b okToAnimate=%b startingDisplayed=%b", - this, reportedVisible, okToDisplay(), okToAnimate(), startingDisplayed); + this, reportedVisible, okToDisplay(), okToAnimate(), + isStartingWindowDisplayed()); // clean up thumbnail window if (mThumbnail != null) { @@ -9649,7 +9634,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mStartingWindow != null) { mStartingWindow.writeIdentifierToProto(proto, STARTING_WINDOW); } - proto.write(STARTING_DISPLAYED, startingDisplayed); + proto.write(STARTING_DISPLAYED, isStartingWindowDisplayed()); proto.write(STARTING_MOVED, startingMoved); proto.write(VISIBLE_SET_FROM_TRANSFERRED_STARTING_WINDOW, mVisibleSetFromTransferredStartingWindow); diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 027d485d15a0..ed50c1e979ad 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -2947,10 +2947,14 @@ class ActivityStarter { } } - // Update the target's launch cookie to those specified in the options if set + // Update the target's launch cookie and pending remote animation to those specified in the + // options if set. if (mStartActivity.mLaunchCookie != null) { intentActivity.mLaunchCookie = mStartActivity.mLaunchCookie; } + if (mStartActivity.mPendingRemoteAnimation != null) { + intentActivity.mPendingRemoteAnimation = mStartActivity.mPendingRemoteAnimation; + } // Need to update mTargetRootTask because if task was moved out of it, the original root // task may be destroyed. diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 9c95e31cc5f5..12133bc06df8 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -80,7 +80,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.IntDef; -import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Rect; import android.os.Trace; @@ -172,16 +171,6 @@ public class AppTransitionController { ? null : wallpaperTarget; } - @NonNull - private static ArraySet<ActivityRecord> getAppsForAnimation( - @NonNull ArraySet<ActivityRecord> apps, boolean excludeLauncherFromAnimation) { - final ArraySet<ActivityRecord> appsForAnimation = new ArraySet<>(apps); - if (excludeLauncherFromAnimation) { - appsForAnimation.removeIf(ConfigurationContainer::isActivityTypeHome); - } - return appsForAnimation; - } - /** * Handle application transition for given display. */ @@ -231,45 +220,32 @@ public class AppTransitionController { mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded( mDisplayContent.mOpeningApps); - // Remove launcher from app transition animation while recents is running. Recents animation - // is managed outside of app transition framework, so we just need to commit visibility. - final boolean excludeLauncherFromAnimation = - mDisplayContent.mOpeningApps.stream().anyMatch( - (app) -> app.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) - || mDisplayContent.mClosingApps.stream().anyMatch( - (app) -> app.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)); - final ArraySet<ActivityRecord> openingAppsForAnimation = getAppsForAnimation( - mDisplayContent.mOpeningApps, excludeLauncherFromAnimation); - final ArraySet<ActivityRecord> closingAppsForAnimation = getAppsForAnimation( - mDisplayContent.mClosingApps, excludeLauncherFromAnimation); - @TransitionOldType final int transit = getTransitCompatType( - mDisplayContent.mAppTransition, openingAppsForAnimation, closingAppsForAnimation, - mDisplayContent.mChangingContainers, + mDisplayContent.mAppTransition, mDisplayContent.mOpeningApps, + mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers, mWallpaperControllerLocked.getWallpaperTarget(), getOldWallpaper(), mDisplayContent.mSkipAppTransitionAnimation); mDisplayContent.mSkipAppTransitionAnimation = false; ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "handleAppTransitionReady: displayId=%d appTransition={%s}" - + " excludeLauncherFromAnimation=%b openingApps=[%s] closingApps=[%s] transit=%s", - mDisplayContent.mDisplayId, appTransition.toString(), excludeLauncherFromAnimation, - mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, - AppTransition.appTransitionOldToString(transit)); + + " openingApps=[%s] closingApps=[%s] transit=%s", + mDisplayContent.mDisplayId, appTransition.toString(), mDisplayContent.mOpeningApps, + mDisplayContent.mClosingApps, AppTransition.appTransitionOldToString(transit)); // Find the layout params of the top-most application window in the tokens, which is // what will control the animation theme. If all closing windows are obscured, then there is // no need to do an animation. This is the case, for example, when this transition is being // done behind a dream window. - final ArraySet<Integer> activityTypes = collectActivityTypes(openingAppsForAnimation, - closingAppsForAnimation, mDisplayContent.mChangingContainers); + final ArraySet<Integer> activityTypes = collectActivityTypes(mDisplayContent.mOpeningApps, + mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers); final ActivityRecord animLpActivity = findAnimLayoutParamsToken(transit, activityTypes, - openingAppsForAnimation, closingAppsForAnimation, + mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers); final ActivityRecord topOpeningApp = - getTopApp(openingAppsForAnimation, false /* ignoreHidden */); + getTopApp(mDisplayContent.mOpeningApps, false /* ignoreHidden */); final ActivityRecord topClosingApp = - getTopApp(closingAppsForAnimation, false /* ignoreHidden */); + getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */); final ActivityRecord topChangingApp = getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */); final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity); @@ -281,14 +257,14 @@ public class AppTransitionController { overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes); } - final boolean voiceInteraction = containsVoiceInteraction(closingAppsForAnimation) - || containsVoiceInteraction(openingAppsForAnimation); + final boolean voiceInteraction = containsVoiceInteraction(mDisplayContent.mClosingApps) + || containsVoiceInteraction(mDisplayContent.mOpeningApps); final int layoutRedo; mService.mSurfaceAnimationRunner.deferStartingAnimations(); try { - applyAnimations(openingAppsForAnimation, closingAppsForAnimation, transit, animLp, - voiceInteraction); + applyAnimations(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, transit, + animLp, voiceInteraction); handleClosingApps(); handleOpeningApps(); handleChangingApps(transit); @@ -300,8 +276,8 @@ public class AppTransitionController { layoutRedo = appTransition.goodToGo(transit, topOpeningApp); handleNonAppWindowsInTransition(transit, flags); appTransition.postAnimationCallback(); - appTransition.clear(); } finally { + appTransition.clear(); mService.mSurfaceAnimationRunner.continueStartingAnimations(); } @@ -1226,14 +1202,19 @@ public class AppTransitionController { if (activity == null) { continue; } + if (activity.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) { + ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, + "Delaying app transition for recents animation to finish"); + return false; + } ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Check opening app=%s: allDrawn=%b startingDisplayed=%b " + "startingMoved=%b isRelaunching()=%b startingWindow=%s", - activity, activity.allDrawn, activity.startingDisplayed, + activity, activity.allDrawn, activity.isStartingWindowDisplayed(), activity.startingMoved, activity.isRelaunching(), activity.mStartingWindow); final boolean allDrawn = activity.allDrawn && !activity.isRelaunching(); - if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) { + if (!allDrawn && !activity.isStartingWindowDisplayed() && !activity.startingMoved) { return false; } if (allDrawn) { diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java index fbee343f09b5..300a894d15c9 100644 --- a/services/core/java/com/android/server/wm/StartingData.java +++ b/services/core/java/com/android/server/wm/StartingData.java @@ -38,6 +38,9 @@ public abstract class StartingData { */ Task mAssociatedTask; + /** Whether the starting window is drawn. */ + boolean mIsDisplayed; + protected StartingData(WindowManagerService service, int typeParams) { mService = service; mTypeParams = typeParams; diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index eba49bbc7301..66d7af9b4f6c 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -358,6 +358,13 @@ class Task extends TaskFragment { int mLockTaskUid = -1; // The uid of the application that called startLockTask(). + /** + * If non-null, the starting window should cover the associated task. It is assigned when the + * parent activity of starting window is put in a partial area of the task. This field will be + * cleared when all visible activities in this task are drawn. + */ + StartingData mSharedStartingData; + /** The process that had previously hosted the root activity of this task. * Used to know that we should try harder to keep this process around, in case the * user wants to return to it. */ @@ -3688,6 +3695,9 @@ class Task extends TaskFragment { if (mRootProcess != null) { pw.print(prefix); pw.print("mRootProcess="); pw.println(mRootProcess); } + if (mSharedStartingData != null) { + pw.println(prefix + "mSharedStartingData=" + mSharedStartingData); + } pw.print(prefix); pw.print("taskId=" + mTaskId); pw.println(" rootTaskId=" + getRootTaskId()); pw.print(prefix); pw.println("hasChildPipActivity=" + (mChildPipActivity != null)); diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index d17867673474..879c7e2d75dc 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -1890,10 +1890,10 @@ class TaskFragment extends WindowContainer<WindowContainer> { RemoteAnimationTarget createRemoteAnimationTarget( RemoteAnimationController.RemoteAnimationRecord record) { final ActivityRecord activity = record.getMode() == RemoteAnimationTarget.MODE_OPENING - // There may be a trampoline activity without window on top of the existing task - // which is moving to front. Exclude the finishing activity so the window of next - // activity can be chosen to create the animation target. - ? getTopNonFinishingActivity() + // There may be a launching (e.g. trampoline or embedded) activity without a window + // on top of the existing task which is moving to front. Exclude finishing activity + // so the window of next activity can be chosen to create the animation target. + ? getActivity(r -> !r.finishing && r.hasChild()) : getTopMostActivity(); return activity != null ? activity.createRemoteAnimationTarget(record) : null; } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 744bf0aa42d9..d4c1abfa8d24 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1842,8 +1842,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * @return {@code true} if one or more windows have been displayed, else false. */ boolean hasAppShownWindows() { - return mActivityRecord != null - && (mActivityRecord.firstWindowDrawn || mActivityRecord.startingDisplayed); + return mActivityRecord != null && (mActivityRecord.firstWindowDrawn + || mActivityRecord.isStartingWindowDisplayed()); } @Override diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 6e16b5de1d85..a0ba8fda906f 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -374,13 +374,6 @@ class WindowStateAnimator { } void destroySurfaceLocked(SurfaceControl.Transaction t) { - final ActivityRecord activity = mWin.mActivityRecord; - if (activity != null) { - if (mWin == activity.mStartingWindow) { - activity.startingDisplayed = false; - } - } - if (mSurfaceController == null) { return; } diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java index 7610b7ca5ec3..b33e22fe6d1e 100644 --- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java @@ -164,7 +164,7 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase { } public void testRequestPinAppWidget() { - ComponentName provider = new ComponentName(mTestContext, DummyAppWidget.class); + ComponentName provider = new ComponentName(mTestContext, TestAppWidgetProvider.class); // Set up users. when(mMockShortcutService.requestPinAppWidget(anyString(), any(AppWidgetProviderInfo.class), eq(null), eq(null), anyInt())) @@ -289,6 +289,16 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase { assertEquals(4, updates.size()); } + public void testReceiveBroadcastBehavior_enableAndUpdate() { + TestAppWidgetProvider testAppWidgetProvider = new TestAppWidgetProvider(); + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE); + + testAppWidgetProvider.onReceive(mTestContext, intent); + + assertTrue(testAppWidgetProvider.isBehaviorSuccess()); + } + + public void testUpdatesReceived_queueNotEmpty_multipleWidgetIdProvided() { int widgetId = setupHostAndWidget(); int widgetId2 = bindNewWidget(); @@ -385,7 +395,7 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase { } private int bindNewWidget() { - ComponentName provider = new ComponentName(mTestContext, DummyAppWidget.class); + ComponentName provider = new ComponentName(mTestContext, TestAppWidgetProvider.class); int widgetId = mService.allocateAppWidgetId(mPkgName, HOST_ID); assertTrue(mManager.bindAppWidgetIdIfAllowed(widgetId, provider)); assertEquals(provider, mManager.getAppWidgetInfo(widgetId).provider); diff --git a/services/tests/servicestests/src/com/android/server/appwidget/TestAppWidgetProvider.java b/services/tests/servicestests/src/com/android/server/appwidget/TestAppWidgetProvider.java new file mode 100644 index 000000000000..6c11a6829c12 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/appwidget/TestAppWidgetProvider.java @@ -0,0 +1,50 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appwidget; + +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.content.Context; + +/** + * Placeholder widget for testing + */ +public class TestAppWidgetProvider extends AppWidgetProvider { + private boolean mEnabled; + private boolean mUpdated; + + TestAppWidgetProvider() { + super(); + mEnabled = false; + mUpdated = false; + } + + public boolean isBehaviorSuccess() { + return mEnabled && mUpdated; + } + + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, + int[] appWidgetids) { + mUpdated = true; + } + + @Override + public void onEnabled(Context context) { + mEnabled = true; + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java index 606f4864643c..cd4af0a581f8 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -616,6 +616,20 @@ public class FingerprintAuthenticationClientTest { verify(mCallback).onClientFinished(any(), eq(true)); } + @Test + public void sideFpsPowerPressCancelsIsntantly() throws Exception { + when(mSensorProps.isAnySidefpsType()).thenReturn(true); + + final FingerprintAuthenticationClient client = createClient(1); + client.start(mCallback); + + client.onPowerPressed(); + mLooper.dispatchAll(); + + verify(mCallback, never()).onClientFinished(any(), eq(true)); + verify(mCallback).onClientFinished(any(), eq(false)); + } + private FingerprintAuthenticationClient createClient() throws RemoteException { return createClient(100 /* version */, true /* allowBackgroundAuthentication */); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 462957a88a6c..8a0a4f7c82b7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -2886,6 +2886,7 @@ public class ActivityRecordTests extends WindowTestsBase { fragmentSetup.accept(taskFragment1, new Rect(0, 0, width / 2, height)); task.addChild(taskFragment1, POSITION_TOP); assertEquals(task, activity1.mStartingData.mAssociatedTask); + assertEquals(activity1.mStartingData, task.mSharedStartingData); final TaskFragment taskFragment2 = new TaskFragment( mAtm, null /* fragmentToken */, false /* createdByOrganizer */); @@ -2905,7 +2906,6 @@ public class ActivityRecordTests extends WindowTestsBase { verify(activity1.getSyncTransaction()).reparent(eq(startingWindow.mSurfaceControl), eq(task.mSurfaceControl)); - assertEquals(activity1.mStartingData, startingWindow.mStartingData); assertEquals(task.mSurfaceControl, startingWindow.getAnimationLeashParent()); assertEquals(taskFragment1.getBounds(), activity1.getBounds()); // The activity was resized by task fragment, but starting window must still cover the task. @@ -2916,6 +2916,7 @@ public class ActivityRecordTests extends WindowTestsBase { activity1.onFirstWindowDrawn(activityWindow); activity2.onFirstWindowDrawn(activityWindow); assertNull(activity1.mStartingWindow); + assertNull(task.mSharedStartingData); } @Test @@ -2991,10 +2992,10 @@ public class ActivityRecordTests extends WindowTestsBase { final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING); final TestWindowState startingWindow = createWindowState(attrs, activity); - activity.startingDisplayed = true; + activity.mStartingData = mock(StartingData.class); activity.addWindow(startingWindow); assertTrue("Starting window should be present", activity.hasStartingWindow()); - activity.startingDisplayed = false; + activity.mStartingData = null; assertTrue("Starting window should be present", activity.hasStartingWindow()); activity.removeChild(startingWindow); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 00be7ed5bc6c..496f6817bb08 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -98,6 +98,7 @@ import android.service.voice.IVoiceInteractionSession; import android.util.Pair; import android.util.Size; import android.view.Gravity; +import android.view.RemoteAnimationAdapter; import android.window.TaskFragmentOrganizerToken; import androidx.test.filters.SmallTest; @@ -1315,6 +1316,32 @@ public class ActivityStarterTests extends WindowTestsBase { } @Test + public void testRemoteAnimation_appliesToExistingTask() { + final ActivityStarter starter = prepareStarter(0, false); + + // Put an activity on default display as the top focused activity. + ActivityRecord r = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final Intent intent = new Intent(); + intent.setComponent(ActivityBuilder.getDefaultComponent()); + starter.setReason("testRemoteAnimation_newTask") + .setIntent(intent) + .execute(); + + assertNull(mRootWindowContainer.topRunningActivity().mPendingRemoteAnimation); + + // Relaunch the activity with remote animation indicated in options. + final RemoteAnimationAdapter adaptor = mock(RemoteAnimationAdapter.class); + final ActivityOptions options = ActivityOptions.makeRemoteAnimation(adaptor); + starter.setReason("testRemoteAnimation_existingTask") + .setIntent(intent) + .setActivityOptions(options.toBundle()) + .execute(); + + // Verify the remote animation is updated. + assertEquals(adaptor, mRootWindowContainer.topRunningActivity().mPendingRemoteAnimation); + } + + @Test public void testStartLaunchIntoPipActivity() { final ActivityStarter starter = prepareStarter(0, false); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java index 513791d2b8a5..0332c4b4597b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -1300,6 +1300,8 @@ public class AppTransitionControllerTest extends WindowTestsBase { activity.allDrawn = true; // Skip manipulate the SurfaceControl. doNothing().when(activity).setDropInputMode(anyInt()); + // Assume the activity contains a window. + doReturn(true).when(activity).hasChild(); // Make sure activity can create remote animation target. doReturn(mock(RemoteAnimationTarget.class)).when(activity).createRemoteAnimationTarget( any()); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java index f61effa284ef..32c95fa7b6c7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java @@ -16,8 +16,6 @@ package com.android.server.wm; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.TRANSIT_CHANGE; @@ -27,7 +25,6 @@ import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE; import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; import static android.view.WindowManager.TRANSIT_NONE; -import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN; import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE; @@ -321,7 +318,6 @@ public class AppTransitionTests extends WindowTestsBase { final ActivityRecord activity2 = createActivityRecord(dc2); activity1.allDrawn = true; - activity1.startingDisplayed = true; activity1.startingMoved = true; // Simulate activity resume / finish flows to prepare app transition & set visibility, @@ -412,50 +408,38 @@ public class AppTransitionTests extends WindowTestsBase { } @Test - public void testExcludeLauncher() { + public void testDelayWhileRecents() { final DisplayContent dc = createNewDisplay(Display.STATE_ON); doReturn(false).when(dc).onDescendantOrientationChanged(any()); final Task task = createTask(dc); - // Simulate activity1 launches activity2 + // Simulate activity1 launches activity2. final ActivityRecord activity1 = createActivityRecord(task); activity1.setVisible(true); activity1.mVisibleRequested = false; activity1.allDrawn = true; - dc.mClosingApps.add(activity1); final ActivityRecord activity2 = createActivityRecord(task); activity2.setVisible(false); activity2.mVisibleRequested = true; activity2.allDrawn = true; + + dc.mClosingApps.add(activity1); dc.mOpeningApps.add(activity2); dc.prepareAppTransition(TRANSIT_OPEN); - - // Simulate start recents - final ActivityRecord homeActivity = createActivityRecord(dc, WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_HOME); - homeActivity.setVisible(false); - homeActivity.mVisibleRequested = true; - homeActivity.allDrawn = true; - dc.mOpeningApps.add(homeActivity); - dc.prepareAppTransition(TRANSIT_NONE); - doReturn(true).when(task) - .isSelfAnimating(anyInt(), eq(ANIMATION_TYPE_RECENTS)); + assertTrue(dc.mAppTransition.containsTransitRequest(TRANSIT_OPEN)); // Wait until everything in animation handler get executed to prevent the exiting window // from being removed during WindowSurfacePlacer Traversal. waitUntilHandlersIdle(); + // Start recents + doReturn(true).when(task) + .isSelfAnimating(anyInt(), eq(ANIMATION_TYPE_RECENTS)); + dc.mAppTransitionController.handleAppTransitionReady(); - verify(activity1).commitVisibility(eq(false), anyBoolean(), anyBoolean()); - verify(activity1).applyAnimation(any(), eq(TRANSIT_OLD_ACTIVITY_OPEN), eq(false), - anyBoolean(), any()); - verify(activity2).commitVisibility(eq(true), anyBoolean(), anyBoolean()); - verify(activity2).applyAnimation(any(), eq(TRANSIT_OLD_ACTIVITY_OPEN), eq(true), - anyBoolean(), any()); - verify(homeActivity).commitVisibility(eq(true), anyBoolean(), anyBoolean()); - verify(homeActivity, never()).applyAnimation(any(), anyInt(), anyBoolean(), anyBoolean(), - any()); + verify(activity1, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean()); + verify(activity2, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean()); } @Test diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 352d8d115b6a..bc5c9ec7a29c 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -172,11 +172,11 @@ public class VoiceInteractionManagerService extends SystemService { mAmInternal.setVoiceInteractionManagerProvider( new ActivityManagerInternal.VoiceInteractionManagerProvider() { @Override - public void notifyActivityEventChanged() { + public void notifyActivityDestroyed(IBinder activityToken) { if (DEBUG) { - Slog.d(TAG, "call notifyActivityEventChanged"); + Slog.d(TAG, "notifyActivityDestroyed activityToken=" + activityToken); } - mServiceStub.notifyActivityEventChanged(); + mServiceStub.notifyActivityDestroyed(activityToken); } }); } @@ -447,11 +447,12 @@ public class VoiceInteractionManagerService extends SystemService { return mImpl.supportsLocalVoiceInteraction(); } - void notifyActivityEventChanged() { + void notifyActivityDestroyed(@NonNull IBinder activityToken) { synchronized (this) { - if (mImpl == null) return; + if (mImpl == null || activityToken == null) return; - Binder.withCleanCallingIdentity(() -> mImpl.notifyActivityEventChangedLocked()); + Binder.withCleanCallingIdentity( + () -> mImpl.notifyActivityDestroyedLocked(activityToken)); } } @@ -1223,6 +1224,16 @@ public class VoiceInteractionManagerService extends SystemService { } } + @Override + public void notifyActivityEventChanged(@NonNull IBinder activityToken, int type) { + synchronized (this) { + if (mImpl == null || activityToken == null) { + return; + } + Binder.withCleanCallingIdentity( + () -> mImpl.notifyActivityEventChangedLocked(activityToken, type)); + } + } //----------------- Hotword Detection/Validation APIs --------------------------------// @Override diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index b9793cafb6fe..fabab2524e83 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -520,9 +520,23 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mActiveSession.stopListeningVisibleActivityChangedLocked(); } - public void notifyActivityEventChangedLocked() { + public void notifyActivityDestroyedLocked(@NonNull IBinder activityToken) { if (DEBUG) { - Slog.d(TAG, "notifyActivityEventChangedLocked"); + Slog.d(TAG, "notifyActivityDestroyedLocked activityToken=" + activityToken); + } + if (mActiveSession == null || !mActiveSession.mShown) { + if (DEBUG) { + Slog.d(TAG, "notifyActivityDestroyedLocked not allowed on no session or" + + " hidden session"); + } + return; + } + mActiveSession.notifyActivityDestroyedLocked(activityToken); + } + + public void notifyActivityEventChangedLocked(@NonNull IBinder activityToken, int type) { + if (DEBUG) { + Slog.d(TAG, "notifyActivityEventChangedLocked type=" + type); } if (mActiveSession == null || !mActiveSession.mShown) { if (DEBUG) { @@ -531,7 +545,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } return; } - mActiveSession.notifyActivityEventChangedLocked(); + mActiveSession.notifyActivityEventChangedLocked(activityToken, type); } public void updateStateLocked( diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java index ae9be8c09e60..b24337fd54c9 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java @@ -29,6 +29,7 @@ import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_DATA; import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE; import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_TASK_ID; +import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.AppOpsManager; @@ -59,6 +60,7 @@ import android.service.voice.IVoiceInteractionSessionService; import android.service.voice.VisibleActivityInfo; import android.service.voice.VoiceInteractionService; import android.service.voice.VoiceInteractionSession; +import android.util.ArrayMap; import android.util.Slog; import android.view.IWindowManager; @@ -128,7 +130,11 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, private boolean mListeningVisibleActivity; private final ScheduledExecutorService mScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); - private final List<VisibleActivityInfo> mVisibleActivityInfos = new ArrayList<>(); + // Records the visible activity information the system has already called onVisible, without + // confirming the result of callback. When activity visible state is changed, we use this to + // determine to call onVisible or onInvisible to assistant application. + private final ArrayMap<IBinder, VisibleActivityInfo> mVisibleActivityInfoForToken = + new ArrayMap<>(); private final PowerManagerInternal mPowerManagerInternal; private final LowPowerStandbyControllerInternal mLowPowerStandbyControllerInternal; private final Runnable mRemoveFromLowPowerStandbyAllowlistRunnable = @@ -530,7 +536,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, public void cancelLocked(boolean finishTask) { mListeningVisibleActivity = false; - mVisibleActivityInfos.clear(); + mVisibleActivityInfoForToken.clear(); hideLocked(); mCanceled = true; if (mBound) { @@ -608,17 +614,24 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, if (DEBUG) { Slog.d(TAG, "startListeningVisibleActivityChangedLocked"); } + + if (!mShown || mCanceled || mSession == null) { + return; + } + mListeningVisibleActivity = true; - mVisibleActivityInfos.clear(); + mVisibleActivityInfoForToken.clear(); - mScheduledExecutorService.execute(() -> { - if (DEBUG) { - Slog.d(TAG, "call handleVisibleActivitiesLocked from enable listening"); - } - synchronized (mLock) { - handleVisibleActivitiesLocked(); - } - }); + // It should only need to report which activities are visible + final ArrayMap<IBinder, VisibleActivityInfo> newVisibleActivityInfos = + getTopVisibleActivityInfosLocked(); + + if (newVisibleActivityInfos == null || newVisibleActivityInfos.isEmpty()) { + return; + } + notifyVisibleActivitiesChangedLocked(newVisibleActivityInfos, + VisibleActivityInfo.TYPE_ACTIVITY_ADDED); + mVisibleActivityInfoForToken.putAll(newVisibleActivityInfos); } void stopListeningVisibleActivityChangedLocked() { @@ -626,12 +639,13 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, Slog.d(TAG, "stopListeningVisibleActivityChangedLocked"); } mListeningVisibleActivity = false; - mVisibleActivityInfos.clear(); + mVisibleActivityInfoForToken.clear(); } - void notifyActivityEventChangedLocked() { + void notifyActivityEventChangedLocked(@NonNull IBinder activityToken, int type) { if (DEBUG) { - Slog.d(TAG, "notifyActivityEventChangedLocked"); + Slog.d(TAG, "notifyActivityEventChangedLocked activityToken=" + activityToken + + ", type=" + type); } if (!mListeningVisibleActivity) { if (DEBUG) { @@ -640,99 +654,139 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, return; } mScheduledExecutorService.execute(() -> { - if (DEBUG) { - Slog.d(TAG, "call handleVisibleActivitiesLocked from activity event"); - } synchronized (mLock) { - handleVisibleActivitiesLocked(); + handleVisibleActivitiesLocked(activityToken, type); } }); } - private List<VisibleActivityInfo> getVisibleActivityInfosLocked() { + private ArrayMap<IBinder, VisibleActivityInfo> getTopVisibleActivityInfosLocked() { if (DEBUG) { - Slog.d(TAG, "getVisibleActivityInfosLocked"); + Slog.d(TAG, "getTopVisibleActivityInfosLocked"); } List<ActivityAssistInfo> allVisibleActivities = LocalServices.getService(ActivityTaskManagerInternal.class) .getTopVisibleActivities(); if (DEBUG) { - Slog.d(TAG, - "getVisibleActivityInfosLocked: allVisibleActivities=" + allVisibleActivities); + Slog.d(TAG, "getTopVisibleActivityInfosLocked: allVisibleActivities=" + + allVisibleActivities); } - if (allVisibleActivities == null || allVisibleActivities.isEmpty()) { + if (allVisibleActivities.isEmpty()) { Slog.w(TAG, "no visible activity"); return null; } final int count = allVisibleActivities.size(); - final List<VisibleActivityInfo> visibleActivityInfos = new ArrayList<>(count); + final ArrayMap<IBinder, VisibleActivityInfo> visibleActivityInfoArrayMap = + new ArrayMap<>(count); for (int i = 0; i < count; i++) { ActivityAssistInfo info = allVisibleActivities.get(i); if (DEBUG) { - Slog.d(TAG, " : activityToken=" + info.getActivityToken() + Slog.d(TAG, "ActivityAssistInfo : activityToken=" + info.getActivityToken() + ", assistToken=" + info.getAssistToken() + ", taskId=" + info.getTaskId()); } - visibleActivityInfos.add( + visibleActivityInfoArrayMap.put(info.getActivityToken(), new VisibleActivityInfo(info.getTaskId(), info.getAssistToken())); } - return visibleActivityInfos; + return visibleActivityInfoArrayMap; } - private void handleVisibleActivitiesLocked() { + // TODO(b/242359988): Split this method up + private void handleVisibleActivitiesLocked(@NonNull IBinder activityToken, int type) { if (DEBUG) { - Slog.d(TAG, "handleVisibleActivitiesLocked"); + Slog.d(TAG, "handleVisibleActivitiesLocked activityToken=" + activityToken + + ", type=" + type); } - if (mSession == null) { + + if (!mListeningVisibleActivity) { + if (DEBUG) { + Slog.d(TAG, "not enable listening visible activity"); + } return; } - if (!mShown || !mListeningVisibleActivity || mCanceled) { + if (!mShown || mCanceled || mSession == null) { return; } - final List<VisibleActivityInfo> newVisibleActivityInfos = getVisibleActivityInfosLocked(); - if (newVisibleActivityInfos == null || newVisibleActivityInfos.isEmpty()) { - notifyVisibleActivitiesChangedLocked(mVisibleActivityInfos, - VisibleActivityInfo.TYPE_ACTIVITY_REMOVED); - mVisibleActivityInfos.clear(); - return; - } - if (mVisibleActivityInfos.isEmpty()) { - notifyVisibleActivitiesChangedLocked(newVisibleActivityInfos, - VisibleActivityInfo.TYPE_ACTIVITY_ADDED); - mVisibleActivityInfos.addAll(newVisibleActivityInfos); + // We use this local variable to determine to call onVisible or onInvisible. + boolean notifyOnVisible = false; + VisibleActivityInfo notifyVisibleActivityInfo = null; + + if (type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_START + || type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_RESUME) { + // It seems that the onStart is unnecessary. But if we have it, the assistant + // application can request the directActions early. Even if we have the onStart, + // we still need the onResume because it is possible that the activity goes to + // onResume from onPause with invisible before the activity goes to onStop from + // onPause. + + // Check if we have reported this activity as visible. If we have reported it as + // visible, do nothing. + if (mVisibleActivityInfoForToken.containsKey(activityToken)) { + return; + } + + // Before reporting this activity as visible, we need to make sure the activity + // is really visible. + notifyVisibleActivityInfo = getVisibleActivityInfoFromTopVisibleActivity( + activityToken); + if (notifyVisibleActivityInfo == null) { + return; + } + notifyOnVisible = true; + } else if (type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_PAUSE) { + // For the onPause stage, the Activity is not necessarily invisible now, so we need + // to check its state. + // Note: After syncing with Activity owner, before the onPause is called, the + // visibility state has been updated. + notifyVisibleActivityInfo = getVisibleActivityInfoFromTopVisibleActivity( + activityToken); + if (notifyVisibleActivityInfo != null) { + return; + } + + // Also make sure we previously reported this Activity as visible. + notifyVisibleActivityInfo = mVisibleActivityInfoForToken.get(activityToken); + if (notifyVisibleActivityInfo == null) { + return; + } + } else if (type == VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_STOP) { + // For the onStop stage, the activity is in invisible state. We only need to consider if + // we have reported this activity as visible. If we have reported it as visible, we + // need to report it as invisible. + // Why we still need onStop? Because it is possible that the activity is in a visible + // state during onPause stage, when the activity enters onStop from onPause, we may + // need to notify onInvisible. + // Note: After syncing with Activity owner, before the onStop is called, the + // visibility state has been updated. + notifyVisibleActivityInfo = mVisibleActivityInfoForToken.get(activityToken); + if (notifyVisibleActivityInfo == null) { + return; + } + } else { + Slog.w(TAG, "notifyActivityEventChangedLocked unexpected type=" + type); return; } - final List<VisibleActivityInfo> addedActivities = new ArrayList<>(); - final List<VisibleActivityInfo> removedActivities = new ArrayList<>(); - - removedActivities.addAll(mVisibleActivityInfos); - for (int i = 0; i < newVisibleActivityInfos.size(); i++) { - final VisibleActivityInfo candidateVisibleActivityInfo = newVisibleActivityInfos.get(i); - if (!removedActivities.isEmpty() && removedActivities.contains( - candidateVisibleActivityInfo)) { - removedActivities.remove(candidateVisibleActivityInfo); - } else { - addedActivities.add(candidateVisibleActivityInfo); + try { + mSession.notifyVisibleActivityInfoChanged(notifyVisibleActivityInfo, + notifyOnVisible ? VisibleActivityInfo.TYPE_ACTIVITY_ADDED + : VisibleActivityInfo.TYPE_ACTIVITY_REMOVED); + } catch (RemoteException e) { + if (DEBUG) { + Slog.w(TAG, "handleVisibleActivitiesLocked RemoteException : " + e); } } - if (!addedActivities.isEmpty()) { - notifyVisibleActivitiesChangedLocked(addedActivities, - VisibleActivityInfo.TYPE_ACTIVITY_ADDED); - } - if (!removedActivities.isEmpty()) { - notifyVisibleActivitiesChangedLocked(removedActivities, - VisibleActivityInfo.TYPE_ACTIVITY_REMOVED); + if (notifyOnVisible) { + mVisibleActivityInfoForToken.put(activityToken, notifyVisibleActivityInfo); + } else { + mVisibleActivityInfoForToken.remove(activityToken); } - - mVisibleActivityInfos.clear(); - mVisibleActivityInfos.addAll(newVisibleActivityInfos); } private void notifyVisibleActivitiesChangedLocked( - List<VisibleActivityInfo> visibleActivityInfos, int type) { + ArrayMap<IBinder, VisibleActivityInfo> visibleActivityInfos, int type) { if (visibleActivityInfos == null || visibleActivityInfos.isEmpty()) { return; } @@ -741,7 +795,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, } try { for (int i = 0; i < visibleActivityInfos.size(); i++) { - mSession.notifyVisibleActivityInfoChanged(visibleActivityInfos.get(i), type); + mSession.notifyVisibleActivityInfoChanged(visibleActivityInfos.valueAt(i), type); } } catch (RemoteException e) { if (DEBUG) { @@ -754,6 +808,51 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, } } + private VisibleActivityInfo getVisibleActivityInfoFromTopVisibleActivity( + @NonNull IBinder activityToken) { + final ArrayMap<IBinder, VisibleActivityInfo> visibleActivityInfos = + getTopVisibleActivityInfosLocked(); + if (visibleActivityInfos == null) { + return null; + } + return visibleActivityInfos.get(activityToken); + } + + void notifyActivityDestroyedLocked(@NonNull IBinder activityToken) { + if (DEBUG) { + Slog.d(TAG, "notifyActivityDestroyedLocked activityToken=" + activityToken); + } + if (!mListeningVisibleActivity) { + if (DEBUG) { + Slog.d(TAG, "not enable listening visible activity"); + } + return; + } + mScheduledExecutorService.execute(() -> { + synchronized (mLock) { + if (!mListeningVisibleActivity) { + return; + } + if (!mShown || mCanceled || mSession == null) { + return; + } + + VisibleActivityInfo visibleActivityInfo = mVisibleActivityInfoForToken.remove( + activityToken); + if (visibleActivityInfo != null) { + try { + mSession.notifyVisibleActivityInfoChanged(visibleActivityInfo, + VisibleActivityInfo.TYPE_ACTIVITY_REMOVED); + } catch (RemoteException e) { + if (DEBUG) { + Slog.w(TAG, "notifyVisibleActivityInfoChanged RemoteException : " + e); + } + } + } + } + }); + } + private void removeFromLowPowerStandbyAllowlist() { synchronized (mLock) { if (mLowPowerStandbyAllowlisted) { |