diff options
186 files changed, 3365 insertions, 1265 deletions
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java index 53a388913118..c572f82ac1d9 100644 --- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java +++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java @@ -32,8 +32,6 @@ import android.annotation.SystemService; import android.annotation.UserHandleAware; import android.content.Context; -import com.android.internal.util.FrameworkStatsLog; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Collections; @@ -453,62 +451,6 @@ public class PowerExemptionManager { @Retention(RetentionPolicy.SOURCE) public @interface ReasonCode {} - private static final int EXEMPTION_REASON_SYSTEM_UID = FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_SYSTEM_UID; - private static final int EXEMPTION_REASON_ALLOWLISTED_PACKAGE = FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_ALLOWLISTED_PACKAGE; - private static final int EXEMPTION_REASON_COMPANION_DEVICE_MANAGER = FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_COMPANION_DEVICE_MANAGER; - private static final int EXEMPTION_REASON_DEVICE_DEMO_MODE = FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_DEVICE_DEMO_MODE; - private static final int EXEMPTION_REASON_DEVICE_OWNER = FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_DEVICE_OWNER; - private static final int EXEMPTION_REASON_PROFILE_OWNER = FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_PROFILE_OWNER; - private static final int EXEMPTION_REASON_PROC_STATE_PERSISTENT = FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_PROC_STATE_PERSISTENT; - private static final int EXEMPTION_REASON_PROC_STATE_PERSISTENT_UI = FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_PROC_STATE_PERSISTENT_UI; - private static final int EXEMPTION_REASON_OP_ACTIVATE_VPN = FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_OP_ACTIVATE_VPN; - private static final int EXEMPTION_REASON_OP_ACTIVATE_PLATFORM_VPN = FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_OP_ACTIVATE_PLATFORM_VPN; - private static final int EXEMPTION_REASON_SYSTEM_MODULE = FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_SYSTEM_MODULE; - private static final int EXEMPTION_REASON_CARRIER_PRIVILEGED_APP = FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_CARRIER_PRIVILEGED_APP; - private static final int EXEMPTION_REASON_SYSTEM_ALLOW_LISTED = FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_SYSTEM_ALLOW_LISTED; - private static final int EXEMPTION_REASON_ROLE_DIALER = FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_ROLE_DIALER; - private static final int EXEMPTION_REASON_ROLE_EMERGENCY = FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_ROLE_EMERGENCY; - private static final int EXEMPTION_REASON_DENIED = FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_DENIED; - /** - * List of exemption reason codes used for statsd logging in AppBackgroundRestrictionsInfo atom. - * @hide - */ - @IntDef(prefix = { "EXEMPTION_REASON_" }, value = { - EXEMPTION_REASON_SYSTEM_UID, - EXEMPTION_REASON_ALLOWLISTED_PACKAGE, - EXEMPTION_REASON_COMPANION_DEVICE_MANAGER, - EXEMPTION_REASON_DEVICE_DEMO_MODE, - EXEMPTION_REASON_DEVICE_OWNER, - EXEMPTION_REASON_PROFILE_OWNER, - EXEMPTION_REASON_PROC_STATE_PERSISTENT, - EXEMPTION_REASON_PROC_STATE_PERSISTENT_UI, - EXEMPTION_REASON_OP_ACTIVATE_VPN, - EXEMPTION_REASON_OP_ACTIVATE_PLATFORM_VPN, - EXEMPTION_REASON_SYSTEM_MODULE, - EXEMPTION_REASON_CARRIER_PRIVILEGED_APP, - EXEMPTION_REASON_SYSTEM_ALLOW_LISTED, - EXEMPTION_REASON_ROLE_DIALER, - EXEMPTION_REASON_ROLE_EMERGENCY, - EXEMPTION_REASON_DENIED, - }) - public @interface ExemptionReason {} - /** * @hide */ @@ -679,40 +621,40 @@ public class PowerExemptionManager { * @hide * @return the reason code mapped to statsd for the AppBackgroundRestrictionsInfo atom. */ - public static @ExemptionReason int getExemptionReasonForStatsd(@ReasonCode int reasonCode) { + public static int getExemptionReasonForStatsd(@ReasonCode int reasonCode) { switch (reasonCode) { case REASON_SYSTEM_UID: - return EXEMPTION_REASON_SYSTEM_UID; + return AppBackgroundRestrictionsInfo.REASON_SYSTEM_UID; case REASON_ALLOWLISTED_PACKAGE: - return EXEMPTION_REASON_ALLOWLISTED_PACKAGE; + return AppBackgroundRestrictionsInfo.REASON_ALLOWLISTED_PACKAGE; case REASON_COMPANION_DEVICE_MANAGER: - return EXEMPTION_REASON_COMPANION_DEVICE_MANAGER; + return AppBackgroundRestrictionsInfo.REASON_COMPANION_DEVICE_MANAGER; case REASON_DEVICE_DEMO_MODE: - return EXEMPTION_REASON_DEVICE_DEMO_MODE; + return AppBackgroundRestrictionsInfo.REASON_DEVICE_DEMO_MODE; case REASON_DEVICE_OWNER: - return EXEMPTION_REASON_DEVICE_OWNER; + return AppBackgroundRestrictionsInfo.REASON_DEVICE_OWNER; case REASON_PROFILE_OWNER: - return EXEMPTION_REASON_PROFILE_OWNER; + return AppBackgroundRestrictionsInfo.REASON_PROFILE_OWNER; case REASON_PROC_STATE_PERSISTENT: - return EXEMPTION_REASON_PROC_STATE_PERSISTENT; + return AppBackgroundRestrictionsInfo.REASON_PROC_STATE_PERSISTENT; case REASON_PROC_STATE_PERSISTENT_UI: - return EXEMPTION_REASON_PROC_STATE_PERSISTENT_UI; + return AppBackgroundRestrictionsInfo.REASON_PROC_STATE_PERSISTENT_UI; case REASON_OP_ACTIVATE_VPN: - return EXEMPTION_REASON_OP_ACTIVATE_VPN; + return AppBackgroundRestrictionsInfo.REASON_OP_ACTIVATE_VPN; case REASON_OP_ACTIVATE_PLATFORM_VPN: - return EXEMPTION_REASON_OP_ACTIVATE_PLATFORM_VPN; + return AppBackgroundRestrictionsInfo.REASON_OP_ACTIVATE_PLATFORM_VPN; case REASON_SYSTEM_MODULE: - return EXEMPTION_REASON_SYSTEM_MODULE; + return AppBackgroundRestrictionsInfo.REASON_SYSTEM_MODULE; case REASON_CARRIER_PRIVILEGED_APP: - return EXEMPTION_REASON_CARRIER_PRIVILEGED_APP; + return AppBackgroundRestrictionsInfo.REASON_CARRIER_PRIVILEGED_APP; case REASON_SYSTEM_ALLOW_LISTED: - return EXEMPTION_REASON_SYSTEM_ALLOW_LISTED; + return AppBackgroundRestrictionsInfo.REASON_SYSTEM_ALLOW_LISTED; case REASON_ROLE_DIALER: - return EXEMPTION_REASON_ROLE_DIALER; + return AppBackgroundRestrictionsInfo.REASON_ROLE_DIALER; case REASON_ROLE_EMERGENCY: - return EXEMPTION_REASON_ROLE_EMERGENCY; + return AppBackgroundRestrictionsInfo.REASON_ROLE_EMERGENCY; default: - return EXEMPTION_REASON_DENIED; + return AppBackgroundRestrictionsInfo.REASON_DENIED; } } diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 881453fe237d..0e4887d27b53 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -612,8 +612,7 @@ public class AlarmManagerService extends SystemService { private static final boolean DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED = true; - // TODO(b/226439802): Flip to true. - private static final boolean DEFAULT_SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = false; + private static final boolean DEFAULT_SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = true; // Minimum futurity of a new alarm public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY; diff --git a/core/java/android/accessibilityservice/InputMethod.java b/core/java/android/accessibilityservice/InputMethod.java index a3936040f2af..79bac9bce620 100644 --- a/core/java/android/accessibilityservice/InputMethod.java +++ b/core/java/android/accessibilityservice/InputMethod.java @@ -62,7 +62,6 @@ public class InputMethod { private final AccessibilityService mService; private InputBinding mInputBinding; - private InputConnection mInputConnection; private boolean mInputStarted; private InputConnection mStartedInputConnection; private EditorInfo mInputEditorInfo; @@ -81,12 +80,8 @@ public class InputMethod { */ @Nullable public final AccessibilityInputConnection getCurrentInputConnection() { - InputConnection ic = mStartedInputConnection; - if (ic != null) { - return new AccessibilityInputConnection(ic); - } - if (mInputConnection != null) { - return new AccessibilityInputConnection(mInputConnection); + if (mStartedInputConnection != null) { + return new AccessibilityInputConnection(mStartedInputConnection); } return null; } @@ -136,10 +131,8 @@ public class InputMethod { * to perform whatever behavior you would like. */ public void onFinishInput() { - InputConnection ic = mStartedInputConnection != null ? mStartedInputConnection - : mInputConnection; - if (ic != null) { - ic.finishComposingText(); + if (mStartedInputConnection != null) { + mStartedInputConnection.finishComposingText(); } } @@ -176,38 +169,38 @@ public class InputMethod { final void bindInput(@NonNull InputBinding binding) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AccessibilityService.bindInput"); mInputBinding = binding; - mInputConnection = binding.getConnection(); - Log.v(LOG_TAG, "bindInput(): binding=" + binding - + " ic=" + mInputConnection); + Log.v(LOG_TAG, "bindInput(): binding=" + binding); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } final void unbindInput() { - Log.v(LOG_TAG, "unbindInput(): binding=" + mInputBinding - + " ic=" + mInputConnection); + Log.v(LOG_TAG, "unbindInput(): binding=" + mInputBinding); // Unbind input is per process per display. mInputBinding = null; - mInputConnection = null; } final void startInput(@Nullable InputConnection ic, @NonNull EditorInfo attribute) { Log.v(LOG_TAG, "startInput(): editor=" + attribute); - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.startInput"); + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AccessibilityService.startInput"); doStartInput(ic, attribute, false /* restarting */); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } final void restartInput(@Nullable InputConnection ic, @NonNull EditorInfo attribute) { Log.v(LOG_TAG, "restartInput(): editor=" + attribute); - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.restartInput"); + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AccessibilityService.restartInput"); doStartInput(ic, attribute, true /* restarting */); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } final void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) { - if (!restarting && mInputStarted) { + if ((ic == null || !restarting) && mInputStarted) { doFinishInput(); + if (ic == null) { + // Unlike InputMethodService, A11y IME should not observe fallback InputConnection. + return; + } } mInputStarted = true; mStartedInputConnection = ic; @@ -224,6 +217,7 @@ public class InputMethod { } mInputStarted = false; mStartedInputConnection = null; + mInputEditorInfo = null; } private InputMethodSession onCreateInputMethodSessionInterface() { diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 852dd974ae42..7d68eb92f206 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -4114,8 +4114,8 @@ public final class ActivityThread extends ClientTransactionHandler @NonNull SurfaceControl startingWindowLeash) { final SplashScreenView.Builder builder = new SplashScreenView.Builder(r.activity); final SplashScreenView view = builder.createFromParcel(parcelable).build(); + view.attachHostWindow(r.window); decorView.addView(view); - view.attachHostActivityAndSetSystemUIColors(r.activity, r.window); view.requestLayout(); view.getViewTreeObserver().addOnDrawListener(new ViewTreeObserver.OnDrawListener() { diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index bfb11682e89c..56d655c9cc6f 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2813,8 +2813,8 @@ public class Notification implements Parcelable } if (extras != null) { - visitIconUri(visitor, extras.getParcelable(EXTRA_LARGE_ICON_BIG)); - visitIconUri(visitor, extras.getParcelable(EXTRA_PICTURE_ICON)); + visitIconUri(visitor, extras.getParcelable(EXTRA_LARGE_ICON_BIG, Icon.class)); + visitIconUri(visitor, extras.getParcelable(EXTRA_PICTURE_ICON, Icon.class)); // NOTE: The documentation of EXTRA_AUDIO_CONTENTS_URI explicitly says that it is a // String representation of a Uri, but the previous implementation (and unit test) of @@ -2838,7 +2838,7 @@ public class Notification implements Parcelable } } - final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON); + final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class); if (person != null) { visitor.accept(person.getIconUri()); } @@ -6473,7 +6473,7 @@ public class Notification implements Parcelable public static Notification.Builder recoverBuilder(Context context, Notification n) { // Re-create notification context so we can access app resources. ApplicationInfo applicationInfo = n.extras.getParcelable( - EXTRA_BUILDER_APPLICATION_INFO); + EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class); Context builderContext; if (applicationInfo != null) { try { @@ -6925,9 +6925,8 @@ public class Notification implements Parcelable boolean isMediaStyle = (MediaStyle.class.equals(style) || DecoratedMediaCustomViewStyle.class.equals(style)); - boolean hasMediaSession = (extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) != null - && extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) - instanceof MediaSession.Token); + boolean hasMediaSession = extras.getParcelable(Notification.EXTRA_MEDIA_SESSION, + MediaSession.Token.class) != null; return isMediaStyle && hasMediaSession; } @@ -7522,7 +7521,7 @@ public class Notification implements Parcelable if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) { mBigLargeIconSet = true; - mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG); + mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG, Icon.class); } if (extras.containsKey(EXTRA_PICTURE_CONTENT_DESCRIPTION)) { @@ -7542,11 +7541,11 @@ public class Notification implements Parcelable // When this style adds a picture, we only add one of the keys. If both were added, // it would most likely be a legacy app trying to override the picture in some way. // Because of that case it's better to give precedence to the legacy field. - Bitmap bitmapPicture = extras.getParcelable(EXTRA_PICTURE); + Bitmap bitmapPicture = extras.getParcelable(EXTRA_PICTURE, Bitmap.class); if (bitmapPicture != null) { return Icon.createWithBitmap(bitmapPicture); } else { - return extras.getParcelable(EXTRA_PICTURE_ICON); + return extras.getParcelable(EXTRA_PICTURE_ICON, Icon.class); } } @@ -8165,7 +8164,7 @@ public class Notification implements Parcelable protected void restoreFromExtras(Bundle extras) { super.restoreFromExtras(extras); - mUser = extras.getParcelable(EXTRA_MESSAGING_PERSON); + mUser = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class); if (mUser == null) { CharSequence displayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME); mUser = new Person.Builder().setName(displayName).build(); @@ -8177,7 +8176,7 @@ public class Notification implements Parcelable mHistoricMessages = Message.getMessagesFromBundleArray(histMessages); mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION); mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT); - mShortcutIcon = extras.getParcelable(EXTRA_CONVERSATION_ICON); + mShortcutIcon = extras.getParcelable(EXTRA_CONVERSATION_ICON, Icon.class); } /** @@ -8731,7 +8730,7 @@ public class Notification implements Parcelable return null; } else { - Person senderPerson = bundle.getParcelable(KEY_SENDER_PERSON); + Person senderPerson = bundle.getParcelable(KEY_SENDER_PERSON, Person.class); if (senderPerson == null) { // Legacy apps that use compat don't actually provide the sender objects // We need to fix the compat version to provide people / use @@ -8748,7 +8747,7 @@ public class Notification implements Parcelable if (bundle.containsKey(KEY_DATA_MIME_TYPE) && bundle.containsKey(KEY_DATA_URI)) { message.setData(bundle.getString(KEY_DATA_MIME_TYPE), - (Uri) bundle.getParcelable(KEY_DATA_URI)); + bundle.getParcelable(KEY_DATA_URI, Uri.class)); } if (bundle.containsKey(KEY_EXTRAS_BUNDLE)) { message.getExtras().putAll(bundle.getBundle(KEY_EXTRAS_BUNDLE)); @@ -9154,7 +9153,7 @@ public class Notification implements Parcelable super.restoreFromExtras(extras); if (extras.containsKey(EXTRA_MEDIA_SESSION)) { - mToken = extras.getParcelable(EXTRA_MEDIA_SESSION); + mToken = extras.getParcelable(EXTRA_MEDIA_SESSION, MediaSession.Token.class); } if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) { mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS); @@ -9166,7 +9165,8 @@ public class Notification implements Parcelable mDeviceIcon = extras.getInt(EXTRA_MEDIA_REMOTE_ICON); } if (extras.containsKey(EXTRA_MEDIA_REMOTE_INTENT)) { - mDeviceIntent = extras.getParcelable(EXTRA_MEDIA_REMOTE_INTENT); + mDeviceIntent = extras.getParcelable( + EXTRA_MEDIA_REMOTE_INTENT, PendingIntent.class); } } @@ -9758,12 +9758,12 @@ public class Notification implements Parcelable super.restoreFromExtras(extras); mCallType = extras.getInt(EXTRA_CALL_TYPE); mIsVideo = extras.getBoolean(EXTRA_CALL_IS_VIDEO); - mPerson = extras.getParcelable(EXTRA_CALL_PERSON); + mPerson = extras.getParcelable(EXTRA_CALL_PERSON, Person.class); mVerificationIcon = extras.getParcelable(EXTRA_VERIFICATION_ICON); mVerificationText = extras.getCharSequence(EXTRA_VERIFICATION_TEXT); - mAnswerIntent = extras.getParcelable(EXTRA_ANSWER_INTENT); - mDeclineIntent = extras.getParcelable(EXTRA_DECLINE_INTENT); - mHangUpIntent = extras.getParcelable(EXTRA_HANG_UP_INTENT); + mAnswerIntent = extras.getParcelable(EXTRA_ANSWER_INTENT, PendingIntent.class); + mDeclineIntent = extras.getParcelable(EXTRA_DECLINE_INTENT, PendingIntent.class); + mHangUpIntent = extras.getParcelable(EXTRA_HANG_UP_INTENT, PendingIntent.class); mAnswerButtonColor = extras.containsKey(EXTRA_ANSWER_COLOR) ? extras.getInt(EXTRA_ANSWER_COLOR) : null; mDeclineButtonColor = extras.containsKey(EXTRA_DECLINE_COLOR) @@ -10824,9 +10824,9 @@ public class Notification implements Parcelable // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options. private static final String KEY_ACTIONS = "actions"; private static final String KEY_FLAGS = "flags"; - private static final String KEY_DISPLAY_INTENT = "displayIntent"; + static final String KEY_DISPLAY_INTENT = "displayIntent"; private static final String KEY_PAGES = "pages"; - private static final String KEY_BACKGROUND = "background"; + static final String KEY_BACKGROUND = "background"; private static final String KEY_CONTENT_ICON = "contentIcon"; private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity"; private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex"; @@ -10883,7 +10883,8 @@ public class Notification implements Parcelable } mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); - mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT); + mDisplayIntent = wearableBundle.getParcelable( + KEY_DISPLAY_INTENT, PendingIntent.class); Notification[] pages = getParcelableArrayFromBundle( wearableBundle, KEY_PAGES, Notification.class); @@ -10891,7 +10892,7 @@ public class Notification implements Parcelable Collections.addAll(mPages, pages); } - mBackground = wearableBundle.getParcelable(KEY_BACKGROUND); + mBackground = wearableBundle.getParcelable(KEY_BACKGROUND, Bitmap.class); mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON); mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY, DEFAULT_CONTENT_ICON_GRAVITY); @@ -11604,7 +11605,7 @@ public class Notification implements Parcelable Bundle carBundle = notif.extras == null ? null : notif.extras.getBundle(EXTRA_CAR_EXTENDER); if (carBundle != null) { - mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON); + mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON, Bitmap.class); mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT); Bundle b = carBundle.getBundle(EXTRA_CONVERSATION); @@ -11710,9 +11711,9 @@ public class Notification implements Parcelable private static final String KEY_AUTHOR = "author"; private static final String KEY_TEXT = "text"; private static final String KEY_MESSAGES = "messages"; - private static final String KEY_REMOTE_INPUT = "remote_input"; - private static final String KEY_ON_REPLY = "on_reply"; - private static final String KEY_ON_READ = "on_read"; + static final String KEY_REMOTE_INPUT = "remote_input"; + static final String KEY_ON_REPLY = "on_reply"; + static final String KEY_ON_READ = "on_read"; private static final String KEY_PARTICIPANTS = "participants"; private static final String KEY_TIMESTAMP = "timestamp"; @@ -11837,10 +11838,10 @@ public class Notification implements Parcelable } } - PendingIntent onRead = b.getParcelable(KEY_ON_READ); - PendingIntent onReply = b.getParcelable(KEY_ON_REPLY); + PendingIntent onRead = b.getParcelable(KEY_ON_READ, PendingIntent.class); + PendingIntent onReply = b.getParcelable(KEY_ON_REPLY, PendingIntent.class); - RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT); + RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT, RemoteInput.class); String[] participants = b.getStringArray(KEY_PARTICIPANTS); if (participants == null || participants.length != 1) { @@ -11982,8 +11983,8 @@ public class Notification implements Parcelable private static final String EXTRA_TV_EXTENDER = "android.tv.EXTENSIONS"; private static final String EXTRA_FLAGS = "flags"; - private static final String EXTRA_CONTENT_INTENT = "content_intent"; - private static final String EXTRA_DELETE_INTENT = "delete_intent"; + static final String EXTRA_CONTENT_INTENT = "content_intent"; + static final String EXTRA_DELETE_INTENT = "delete_intent"; private static final String EXTRA_CHANNEL_ID = "channel_id"; private static final String EXTRA_SUPPRESS_SHOW_OVER_APPS = "suppressShowOverApps"; @@ -12015,8 +12016,8 @@ public class Notification implements Parcelable mFlags = bundle.getInt(EXTRA_FLAGS); mChannelId = bundle.getString(EXTRA_CHANNEL_ID); mSuppressShowOverApps = bundle.getBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS); - mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT); - mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT); + mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT, PendingIntent.class); + mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT, PendingIntent.class); } } diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 0a18588e0131..e022ca306674 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -67,7 +67,6 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.StrictMode; import android.os.SystemProperties; -import android.service.wallpaper.WallpaperService; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -559,23 +558,26 @@ public class WallpaperManager { } mCachedWallpaper = null; mCachedWallpaperUserId = 0; - try { - mCachedWallpaper = getCurrentWallpaperLocked( - context, userId, hardware, cmProxy); - mCachedWallpaperUserId = userId; - } catch (OutOfMemoryError e) { - Log.w(TAG, "Out of memory loading the current wallpaper: " + e); - } catch (SecurityException e) { - if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O_MR1) { - Log.w(TAG, "No permission to access wallpaper, suppressing" - + " exception to avoid crashing legacy app."); - } else { - // Post-O apps really most sincerely need the permission. - throw e; + } + try { + Bitmap currentWallpaper = getCurrentWallpaperLocked( + context, userId, hardware, cmProxy); + if (currentWallpaper != null) { + synchronized (this) { + mCachedWallpaper = currentWallpaper; + mCachedWallpaperUserId = userId; + return mCachedWallpaper; } } - if (mCachedWallpaper != null) { - return mCachedWallpaper; + } catch (OutOfMemoryError e) { + Log.w(TAG, "Out of memory loading the current wallpaper: " + e); + } catch (SecurityException e) { + if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O_MR1) { + Log.w(TAG, "No permission to access wallpaper, suppressing" + + " exception to avoid crashing legacy app."); + } else { + // Post-O apps really most sincerely need the permission. + throw e; } } if (returnDefault) { @@ -2472,7 +2474,7 @@ public class WallpaperManager { * * @param colors Wallpaper color info, {@code null} when not available. * @param which A combination of {@link #FLAG_LOCK} and {@link #FLAG_SYSTEM} - * @see WallpaperService.Engine#onComputeColors() + * @see android.service.wallpaper.WallpaperService.Engine#onComputeColors() */ void onColorsChanged(@Nullable WallpaperColors colors, int which); @@ -2484,7 +2486,7 @@ public class WallpaperManager { * @param colors Wallpaper color info, {@code null} when not available. * @param which A combination of {@link #FLAG_LOCK} and {@link #FLAG_SYSTEM} * @param userId Owner of the wallpaper - * @see WallpaperService.Engine#onComputeColors() + * @see android.service.wallpaper.WallpaperService.Engine#onComputeColors() * @hide */ default void onColorsChanged(@Nullable WallpaperColors colors, int which, int userId) { diff --git a/core/java/android/service/dreams/DreamActivity.java b/core/java/android/service/dreams/DreamActivity.java index 96bbf8ebc2cb..cf4e6a6da96e 100644 --- a/core/java/android/service/dreams/DreamActivity.java +++ b/core/java/android/service/dreams/DreamActivity.java @@ -19,6 +19,7 @@ package android.service.dreams; import android.annotation.Nullable; import android.app.Activity; import android.os.Bundle; +import android.text.TextUtils; import com.android.internal.R; @@ -44,6 +45,7 @@ import com.android.internal.R; */ public class DreamActivity extends Activity { static final String EXTRA_CALLBACK = "binder"; + static final String EXTRA_DREAM_TITLE = "title"; public DreamActivity() {} @@ -51,6 +53,11 @@ public class DreamActivity extends Activity { public void onCreate(@Nullable Bundle bundle) { super.onCreate(bundle); + final String title = getIntent().getStringExtra(EXTRA_DREAM_TITLE); + if (!TextUtils.isEmpty(title)) { + setTitle(title); + } + DreamService.DreamServiceWrapper callback = (DreamService.DreamServiceWrapper) getIntent().getIBinderExtra(EXTRA_CALLBACK); diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index d4f8a3beb89b..95eae6cada9f 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -1280,6 +1280,9 @@ public class DreamService extends Service implements Window.Callback { i.setPackage(getApplicationContext().getPackageName()); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); i.putExtra(DreamActivity.EXTRA_CALLBACK, mDreamServiceWrapper); + final ServiceInfo serviceInfo = fetchServiceInfo(this, + new ComponentName(this, getClass())); + i.putExtra(DreamActivity.EXTRA_DREAM_TITLE, fetchDreamLabel(this, serviceInfo)); try { if (!ActivityTaskManager.getService().startDreamActivity(i)) { diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 5131e4e58f54..425dbb9cb204 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -1685,13 +1685,12 @@ public abstract class WallpaperService extends Service { void updatePage(EngineWindowPage currentPage, int pageIndx, int numPages, float xOffsetStep) { - // to save creating a runnable, check twice - long current = System.currentTimeMillis(); + // in case the clock is zero, we start with negative time + long current = SystemClock.elapsedRealtime() - DEFAULT_UPDATE_SCREENSHOT_DURATION; long lapsed = current - currentPage.getLastUpdateTime(); // Always update the page when the last update time is <= 0 // This is important especially when the device first boots - if (lapsed < DEFAULT_UPDATE_SCREENSHOT_DURATION - && currentPage.getLastUpdateTime() > 0) { + if (lapsed < DEFAULT_UPDATE_SCREENSHOT_DURATION) { return; } Surface surface = mSurfaceHolder.getSurface(); diff --git a/core/java/android/transparency/OWNERS b/core/java/android/transparency/OWNERS index 75bf84c727df..ed0e5e5f36ad 100644 --- a/core/java/android/transparency/OWNERS +++ b/core/java/android/transparency/OWNERS @@ -1,4 +1,5 @@ -# Bug component: 36824 +# Bug component: 1164579 billylau@google.com -vishwath@google.com mpgroover@google.com +vishwath@google.com +wenhaowang@google.com diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 6be6f9d110ad..96b1abb62dbf 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1274,6 +1274,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private class SurfaceViewPositionUpdateListener implements RenderNode.PositionUpdateListener { private final int mRtSurfaceWidth; private final int mRtSurfaceHeight; + private boolean mRtFirst = true; private final SurfaceControl.Transaction mPositionChangedTransaction = new SurfaceControl.Transaction(); @@ -1284,14 +1285,15 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @Override public void positionChanged(long frameNumber, int left, int top, int right, int bottom) { - if (mRTLastReportedPosition.left == left + if (!mRtFirst && (mRTLastReportedPosition.left == left && mRTLastReportedPosition.top == top && mRTLastReportedPosition.right == right && mRTLastReportedPosition.bottom == bottom && mRTLastReportedSurfaceSize.x == mRtSurfaceWidth - && mRTLastReportedSurfaceSize.y == mRtSurfaceHeight) { + && mRTLastReportedSurfaceSize.y == mRtSurfaceHeight)) { return; } + mRtFirst = false; try { if (DEBUG_POSITION) { Log.d(TAG, String.format( diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java index 14985c987f97..1024e2e50c3e 100644 --- a/core/java/android/window/BackEvent.java +++ b/core/java/android/window/BackEvent.java @@ -46,8 +46,8 @@ public class BackEvent implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface SwipeEdge{} - private final int mTouchX; - private final int mTouchY; + private final float mTouchX; + private final float mTouchY; private final float mProgress; @SwipeEdge @@ -58,14 +58,14 @@ public class BackEvent implements Parcelable { /** * Creates a new {@link BackEvent} instance. * - * @param touchX Absolute X location of the touch point. - * @param touchY Absolute Y location of the touch point. + * @param touchX Absolute X location of the touch point of this event. + * @param touchY Absolute Y location of the touch point of this event. * @param progress Value between 0 and 1 on how far along the back gesture is. * @param swipeEdge Indicates which edge the swipe starts from. * @param departingAnimationTarget The remote animation target of the departing application * window. */ - public BackEvent(int touchX, int touchY, float progress, @SwipeEdge int swipeEdge, + public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge, @Nullable RemoteAnimationTarget departingAnimationTarget) { mTouchX = touchX; mTouchY = touchY; @@ -75,8 +75,8 @@ public class BackEvent implements Parcelable { } private BackEvent(@NonNull Parcel in) { - mTouchX = in.readInt(); - mTouchY = in.readInt(); + mTouchX = in.readFloat(); + mTouchY = in.readFloat(); mProgress = in.readFloat(); mSwipeEdge = in.readInt(); mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR); @@ -101,8 +101,8 @@ public class BackEvent implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeInt(mTouchX); - dest.writeInt(mTouchY); + dest.writeFloat(mTouchX); + dest.writeFloat(mTouchY); dest.writeFloat(mProgress); dest.writeInt(mSwipeEdge); dest.writeTypedObject(mDepartingAnimationTarget, flags); @@ -118,14 +118,14 @@ public class BackEvent implements Parcelable { /** * Returns the absolute X location of the touch point. */ - public int getTouchX() { + public float getTouchX() { return mTouchX; } /** * Returns the absolute Y location of the touch point. */ - public int getTouchY() { + public float getTouchY() { return mTouchY; } diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java index 2d1deb2a57bb..bc9f74ecec99 100644 --- a/core/java/android/window/SplashScreenView.java +++ b/core/java/android/window/SplashScreenView.java @@ -16,9 +16,6 @@ package android.window; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; -import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; -import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; -import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLASHSCREEN_AVD; @@ -29,11 +26,9 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.annotation.UiThread; -import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; @@ -52,15 +47,12 @@ import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; import android.view.Window; -import android.view.WindowInsetsController; -import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.ImageView; import com.android.internal.R; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.policy.DecorView; -import com.android.internal.util.ContrastColorUtil; import java.time.Duration; import java.time.Instant; @@ -87,12 +79,6 @@ public final class SplashScreenView extends FrameLayout { private static final String TAG = SplashScreenView.class.getSimpleName(); private static final boolean DEBUG = Build.IS_DEBUGGABLE; - private static final int LIGHT_BARS_MASK = - WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS - | WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; - private static final int WINDOW_FLAG_MASK = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS - | FLAG_TRANSLUCENT_NAVIGATION | FLAG_TRANSLUCENT_STATUS; - private boolean mNotCopyable; private boolean mIsCopied; private int mInitBackgroundColor; @@ -107,9 +93,6 @@ public final class SplashScreenView extends FrameLayout { private final Rect mTmpRect = new Rect(); private final int[] mTmpPos = new int[2]; - // The host activity when transfer view to it. - private Activity mHostActivity; - @Nullable private SurfaceControlViewHost.SurfacePackage mSurfacePackageCopy; @Nullable @@ -123,14 +106,7 @@ public final class SplashScreenView extends FrameLayout { // cache original window and status private Window mWindow; - private int mAppWindowFlags; - private int mStatusBarColor; - private int mNavigationBarColor; - private int mSystemBarsAppearance; private boolean mHasRemoved; - private boolean mNavigationContrastEnforced; - private boolean mStatusContrastEnforced; - private boolean mDecorFitsSystemWindows; /** * Internal builder to create a SplashScreenView object. @@ -570,7 +546,6 @@ public final class SplashScreenView extends FrameLayout { if (decorView != null) { decorView.removeView(this); } - restoreSystemUIColors(); mWindow = null; } mHasRemoved = true; @@ -651,56 +626,12 @@ public final class SplashScreenView extends FrameLayout { } /** - * Called when this view is attached to an activity. This also makes SystemUI colors - * transparent so the content of splash screen view can draw fully. + * Called when this view is attached to a window of an activity. * * @hide */ - public void attachHostActivityAndSetSystemUIColors(Activity activity, Window window) { - mHostActivity = activity; + public void attachHostWindow(Window window) { mWindow = window; - final WindowManager.LayoutParams attr = window.getAttributes(); - mAppWindowFlags = attr.flags; - mStatusBarColor = window.getStatusBarColor(); - mNavigationBarColor = window.getNavigationBarColor(); - mSystemBarsAppearance = window.getInsetsController().getSystemBarsAppearance(); - mNavigationContrastEnforced = window.isNavigationBarContrastEnforced(); - mStatusContrastEnforced = window.isStatusBarContrastEnforced(); - mDecorFitsSystemWindows = window.decorFitsSystemWindows(); - - applySystemBarsContrastColor(window.getInsetsController(), mInitBackgroundColor); - // Let app draw the background of bars. - window.addFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - // Use specified bar colors instead of window background. - window.clearFlags(FLAG_TRANSLUCENT_STATUS | FLAG_TRANSLUCENT_NAVIGATION); - window.setStatusBarColor(Color.TRANSPARENT); - window.setNavigationBarColor(Color.TRANSPARENT); - window.setDecorFitsSystemWindows(false); - window.setStatusBarContrastEnforced(false); - window.setNavigationBarContrastEnforced(false); - } - - /** Called when this view is removed from the host activity. */ - private void restoreSystemUIColors() { - mWindow.setFlags(mAppWindowFlags, WINDOW_FLAG_MASK); - mWindow.setStatusBarColor(mStatusBarColor); - mWindow.setNavigationBarColor(mNavigationBarColor); - mWindow.getInsetsController().setSystemBarsAppearance(mSystemBarsAppearance, - LIGHT_BARS_MASK); - mWindow.setDecorFitsSystemWindows(mDecorFitsSystemWindows); - mWindow.setStatusBarContrastEnforced(mStatusContrastEnforced); - mWindow.setNavigationBarContrastEnforced(mNavigationContrastEnforced); - } - - /** - * Makes the icon color of system bars contrast. - * @hide - */ - public static void applySystemBarsContrastColor(WindowInsetsController windowInsetsController, - int backgroundColor) { - final int lightBarAppearance = ContrastColorUtil.isColorLight(backgroundColor) - ? LIGHT_BARS_MASK : 0; - windowInsetsController.setSystemBarsAppearance(lightBarAppearance, LIGHT_BARS_MASK); } /** diff --git a/core/java/com/android/internal/policy/ForceShowNavigationBarSettingsObserver.java b/core/java/com/android/internal/policy/KidsModeSettingsObserver.java index 75dce5a278ff..8a1d407382bc 100644 --- a/core/java/com/android/internal/policy/ForceShowNavigationBarSettingsObserver.java +++ b/core/java/com/android/internal/policy/KidsModeSettingsObserver.java @@ -24,17 +24,17 @@ import android.os.UserHandle; import android.provider.Settings; /** - * A ContentObserver for listening force show navigation bar relative setting keys: + * A ContentObserver for listening kids mode relative setting keys: * - {@link Settings.Secure#NAVIGATION_MODE} - * - {@link Settings.Secure#NAV_BAR_FORCE_VISIBLE} + * - {@link Settings.Secure#NAV_BAR_KIDS_MODE} * * @hide */ -public class ForceShowNavigationBarSettingsObserver extends ContentObserver { +public class KidsModeSettingsObserver extends ContentObserver { private Context mContext; private Runnable mOnChangeRunnable; - public ForceShowNavigationBarSettingsObserver(Handler handler, Context context) { + public KidsModeSettingsObserver(Handler handler, Context context) { super(handler); mContext = context; } @@ -52,7 +52,7 @@ public class ForceShowNavigationBarSettingsObserver extends ContentObserver { Settings.Secure.getUriFor(Settings.Secure.NAVIGATION_MODE), false, this, UserHandle.USER_ALL); r.registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_FORCE_VISIBLE), + Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), false, this, UserHandle.USER_ALL); } @@ -78,6 +78,6 @@ public class ForceShowNavigationBarSettingsObserver extends ContentObserver { return Settings.Secure.getIntForUser(mContext.getContentResolver(), Settings.Secure.NAVIGATION_MODE, 0, UserHandle.USER_CURRENT) == 0 && Settings.Secure.getIntForUser(mContext.getContentResolver(), - Settings.Secure.NAV_BAR_FORCE_VISIBLE, 0, UserHandle.USER_CURRENT) == 1; + Settings.Secure.NAV_BAR_KIDS_MODE, 0, UserHandle.USER_CURRENT) == 1; } } diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 0e3840a39e25..757f40963bf7 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -674,6 +674,10 @@ <!-- Indicates whether to enable hinge angle sensor when using unfold animation --> <bool name="config_unfoldTransitionHingeAngle">false</bool> + <!-- Indicates the time needed to time out the fold animation if the device stops in half folded + mode. --> + <integer name="config_unfoldTransitionHalfFoldedTimeout">600</integer> + <!-- Indicates that the device supports having more than one internal display on at the same time. Only applicable to devices with more than one internal display. If this option is set to false, DisplayManager will make additional effort to ensure no more than 1 internal @@ -5678,8 +5682,8 @@ restricted level. --> <array name="config_bg_current_drain_threshold_to_bg_restricted"> - <item>10.0</item> <!-- regular device --> - <item>20.0</item> <!-- low ram device --> + <item>4.0</item> <!-- regular device --> + <item>8.0</item> <!-- low ram device --> </array> <!-- The background current drain monitoring window size. --> @@ -5740,6 +5744,12 @@ --> <bool name="config_bg_prompt_fgs_with_noti_to_bg_restricted">false</bool> + <!-- The behavior when the system detects it's abusive, should the system prompt the user + to put it into the bg restricted level. + True - we'll show the prompt to user, False - we'll not show it. + --> + <bool name="config_bg_prompt_abusive_apps_to_bg_restricted">false</bool> + <!-- The types of state where we'll exempt its battery usage during that state. The state here must be one or a combination of STATE_TYPE_* in BaseAppStateTracker. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a9b95da8982a..333482272789 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3957,6 +3957,7 @@ <java-symbol type="bool" name="config_supportsConcurrentInternalDisplays" /> <java-symbol type="bool" name="config_unfoldTransitionEnabled" /> <java-symbol type="bool" name="config_unfoldTransitionHingeAngle" /> + <java-symbol type="integer" name="config_unfoldTransitionHalfFoldedTimeout" /> <java-symbol type="array" name="config_perDeviceStateRotationLockDefaults" /> @@ -4764,6 +4765,7 @@ <java-symbol type="integer" name="config_bg_current_drain_media_playback_min_duration" /> <java-symbol type="integer" name="config_bg_current_drain_location_min_duration" /> <java-symbol type="bool" name="config_bg_prompt_fgs_with_noti_to_bg_restricted" /> + <java-symbol type="bool" name="config_bg_prompt_abusive_apps_to_bg_restricted" /> <java-symbol type="integer" name="config_bg_current_drain_exempted_types" /> <java-symbol type="bool" name="config_bg_current_drain_high_threshold_by_bg_location" /> <java-symbol type="drawable" name="ic_swap_horiz" /> diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index a5da4424f722..f9f3b4c8ead1 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -17,6 +17,30 @@ package android.app; import static android.app.Notification.Builder.ensureColorSpanContrast; +import static android.app.Notification.CarExtender.UnreadConversation.KEY_ON_READ; +import static android.app.Notification.CarExtender.UnreadConversation.KEY_ON_REPLY; +import static android.app.Notification.CarExtender.UnreadConversation.KEY_REMOTE_INPUT; +import static android.app.Notification.EXTRA_ANSWER_INTENT; +import static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO; +import static android.app.Notification.EXTRA_CALL_PERSON; +import static android.app.Notification.EXTRA_CONVERSATION_ICON; +import static android.app.Notification.EXTRA_DECLINE_INTENT; +import static android.app.Notification.EXTRA_HANG_UP_INTENT; +import static android.app.Notification.EXTRA_LARGE_ICON; +import static android.app.Notification.EXTRA_LARGE_ICON_BIG; +import static android.app.Notification.EXTRA_MEDIA_REMOTE_INTENT; +import static android.app.Notification.EXTRA_MEDIA_SESSION; +import static android.app.Notification.EXTRA_MESSAGING_PERSON; +import static android.app.Notification.EXTRA_PICTURE; +import static android.app.Notification.EXTRA_PICTURE_ICON; +import static android.app.Notification.MessagingStyle.Message.KEY_DATA_URI; +import static android.app.Notification.MessagingStyle.Message.KEY_SENDER_PERSON; +import static android.app.Notification.MessagingStyle.Message.KEY_TEXT; +import static android.app.Notification.MessagingStyle.Message.KEY_TIMESTAMP; +import static android.app.Notification.TvExtender.EXTRA_CONTENT_INTENT; +import static android.app.Notification.TvExtender.EXTRA_DELETE_INTENT; +import static android.app.Notification.WearableExtender.KEY_BACKGROUND; +import static android.app.Notification.WearableExtender.KEY_DISPLAY_INTENT; import static com.android.compatibility.common.util.SystemUtil.runShellCommand; import static com.android.internal.util.ContrastColorUtilTest.assertContrastIsAtLeast; @@ -32,6 +56,7 @@ import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.spy; import android.annotation.Nullable; import android.content.Context; @@ -43,6 +68,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.drawable.Icon; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Parcel; @@ -170,7 +196,7 @@ public class NotificationTest { assertNotSame(q.getLargeIcon(), n.getLargeIcon()); assertTrue(q.getLargeIcon().getBitmap().sameAs(n.getLargeIcon().getBitmap())); - assertSame(q.getLargeIcon(), q.extras.getParcelable(Notification.EXTRA_LARGE_ICON)); + assertSame(q.getLargeIcon(), q.extras.getParcelable(EXTRA_LARGE_ICON)); } @Test @@ -179,12 +205,12 @@ public class NotificationTest { mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96)); Notification n = new Notification.Builder(mContext).build(); - n.extras.putParcelable(Notification.EXTRA_LARGE_ICON, originalIcon); + n.extras.putParcelable(EXTRA_LARGE_ICON, originalIcon); assertSame(n.getLargeIcon(), null); Notification q = writeAndReadParcelable(n); assertSame(q.getLargeIcon(), null); - assertTrue(((Icon) q.extras.getParcelable(Notification.EXTRA_LARGE_ICON)).getBitmap() + assertTrue(((Icon) q.extras.getParcelable(EXTRA_LARGE_ICON)).getBitmap() .sameAs(originalIcon.getBitmap())); } @@ -641,7 +667,7 @@ public class NotificationTest { public void testIsMediaNotification_invalidSession_returnsFalse() { // Extra was set manually to an invalid type Bundle extras = new Bundle(); - extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, new Intent()); + extras.putParcelable(EXTRA_MEDIA_SESSION, new Intent()); Notification.MediaStyle mediaStyle = new Notification.MediaStyle(); Notification notification = new Notification.Builder(mContext, "test id") .setStyle(mediaStyle) @@ -650,6 +676,7 @@ public class NotificationTest { assertFalse(notification.isMediaNotification()); } + @Test public void validateColorizedPaletteForColor(int rawColor) { Notification.Colors cDay = new Notification.Colors(); Notification.Colors cNight = new Notification.Colors(); @@ -681,6 +708,174 @@ public class NotificationTest { } } + @Test + public void testVisitUris_invalidExtra_noCrash() { + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(0) + .build(); + Bundle fakeTypes = new Bundle(); + fakeTypes.putParcelable(EXTRA_LARGE_ICON_BIG, new Bundle()); + fakeTypes.putParcelable(EXTRA_PICTURE_ICON, new Bundle()); + fakeTypes.putParcelable(EXTRA_MESSAGING_PERSON, new Bundle()); + + Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); + n.visitUris(visitor); + + // no crash, good + } + + @Test + public void testRecoverBuilder_invalidExtra_noCrash() { + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(0) + .build(); + Bundle fakeTypes = new Bundle(); + fakeTypes.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, new Bundle()); + + Notification.Builder.recoverBuilder(mContext, n); + + // no crash, good + } + + @Test + public void testIsMediaNotification_invalidExtra_noCrash() { + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(0) + .setStyle(new Notification.MediaStyle()) + .build(); + Bundle fakeTypes = new Bundle(); + fakeTypes.putParcelable(EXTRA_MEDIA_SESSION, new Bundle()); + + n.isMediaNotification(); + + // no crash, good + } + + @Test + public void testRestoreFromExtras_BigText_invalidExtra_noCrash() { + Notification.Style style = new Notification.BigTextStyle(); + Bundle fakeTypes = new Bundle(); + fakeTypes.putParcelable(EXTRA_LARGE_ICON_BIG, new Bundle()); + + style.restoreFromExtras(fakeTypes); + + // no crash, good + } + + @Test + public void testRestoreFromExtras_Messaging_invalidExtra_noCrash() { + Notification.Style style = new Notification.MessagingStyle(); + Bundle fakeTypes = new Bundle(); + fakeTypes.putParcelable(EXTRA_MESSAGING_PERSON, new Bundle()); + fakeTypes.putParcelable(EXTRA_CONVERSATION_ICON, new Bundle()); + + style.restoreFromExtras(fakeTypes); + + // no crash, good + } + + @Test + public void testRestoreFromExtras_Media_invalidExtra_noCrash() { + Notification.Style style = new Notification.MediaStyle(); + Bundle fakeTypes = new Bundle(); + fakeTypes.putParcelable(EXTRA_MEDIA_SESSION, new Bundle()); + fakeTypes.putParcelable(EXTRA_MEDIA_REMOTE_INTENT, new Bundle()); + + style.restoreFromExtras(fakeTypes); + + // no crash, good + } + + @Test + public void testRestoreFromExtras_Call_invalidExtra_noCrash() { + Notification.Style style = new Notification.CallStyle(); + Bundle fakeTypes = new Bundle(); + fakeTypes.putParcelable(EXTRA_CALL_PERSON, new Bundle()); + fakeTypes.putParcelable(EXTRA_ANSWER_INTENT, new Bundle()); + fakeTypes.putParcelable(EXTRA_DECLINE_INTENT, new Bundle()); + fakeTypes.putParcelable(EXTRA_HANG_UP_INTENT, new Bundle()); + + style.restoreFromExtras(fakeTypes); + + // no crash, good + } + + @Test + public void testGetPictureIcon_invalidExtra_noCrash() { + Bundle fakeTypes = new Bundle(); + fakeTypes.putParcelable(EXTRA_PICTURE, new Bundle()); + fakeTypes.putParcelable(EXTRA_PICTURE_ICON, new Bundle()); + + Notification.BigPictureStyle.getPictureIcon(fakeTypes); + + // no crash, good + } + + @Test + public void testWearableExtender_invalidExtra_noCrash() { + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(0) + .setStyle(new Notification.MediaStyle()) + .build(); + Bundle fakeTypes = new Bundle(); + fakeTypes.putParcelable(KEY_DISPLAY_INTENT, new Bundle()); + fakeTypes.putParcelable(KEY_BACKGROUND, new Bundle()); + Notification.WearableExtender extender = new Notification.WearableExtender(n); + + // no crash, good + } + + @Test + public void testCarExtender_invalidExtra_noCrash() { + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(0) + .setStyle(new Notification.MediaStyle()) + .build(); + Bundle fakeTypes = new Bundle(); + fakeTypes.putParcelable(EXTRA_LARGE_ICON, new Bundle()); + Notification.CarExtender extender = new Notification.CarExtender(n); + + // no crash, good + } + + @Test + public void testTvExtender_invalidExtra_noCrash() { + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(0) + .setStyle(new Notification.MediaStyle()) + .build(); + Bundle fakeTypes = new Bundle(); + fakeTypes.putParcelable(EXTRA_CONTENT_INTENT, new Bundle()); + fakeTypes.putParcelable(EXTRA_DELETE_INTENT, new Bundle()); + Notification.TvExtender extender = new Notification.TvExtender(n); + + // no crash, good + } + + @Test + public void testGetUnreadConversationFromBundle_invalidExtra_noCrash() { + Bundle fakeTypes = new Bundle(); + fakeTypes.putParcelable(KEY_ON_READ, new Bundle()); + fakeTypes.putParcelable(KEY_ON_REPLY, new Bundle()); + fakeTypes.putParcelable(KEY_REMOTE_INPUT, new Bundle()); + Notification.CarExtender.UnreadConversation.getUnreadConversationFromBundle(fakeTypes); + + // no crash, good + } + + @Test + public void testGetMessageFromBundle_invalidExtra_noCrash() { + Bundle fakeTypes = new Bundle(); + fakeTypes.putParcelable(KEY_SENDER_PERSON, new Bundle()); + fakeTypes.putString(KEY_TEXT, "text"); + fakeTypes.putLong(KEY_TIMESTAMP, 0); + fakeTypes.putParcelable(KEY_REMOTE_INPUT, new Bundle()); + fakeTypes.putParcelable(KEY_DATA_URI, new Bundle()); + Notification.MessagingStyle.Message.getMessageFromBundle(fakeTypes); + + // no crash, good + } + private void assertValid(Notification.Colors c) { // Assert that all colors are populated assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID); 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 33a41ecd49fa..e20cef2bec4e 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -60,7 +60,8 @@ import java.util.function.Consumer; public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback, ActivityEmbeddingComponent { - private final SplitPresenter mPresenter; + @VisibleForTesting + final SplitPresenter mPresenter; // Currently applied split configuration. private final List<EmbeddingRule> mSplitRules = new ArrayList<>(); @@ -149,6 +150,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return; } + final WindowContainerTransaction wct = new WindowContainerTransaction(); final boolean wasInPip = isInPictureInPicture(container); container.setInfo(taskFragmentInfo); final boolean isInPip = isInPictureInPicture(container); @@ -159,13 +161,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Do not finish the dependents if the last activity is reparented to PiP. // Instead, the original split should be cleanup, and the dependent may be expanded // to fullscreen. - cleanupForEnterPip(container); - mPresenter.cleanupContainer(container, false /* shouldFinishDependent */); + cleanupForEnterPip(wct, container); + mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct); } else { // Do not finish the dependents if this TaskFragment was cleared due to launching // activity in the Task. final boolean shouldFinishDependent = !taskFragmentInfo.isTaskClearedForReuse(); - mPresenter.cleanupContainer(container, shouldFinishDependent); + mPresenter.cleanupContainer(container, shouldFinishDependent, wct); } } else if (wasInPip && isInPip) { // No update until exit PIP. @@ -174,12 +176,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Enter PIP. // All overrides will be cleanup. container.setLastRequestedBounds(null /* bounds */); - cleanupForEnterPip(container); + cleanupForEnterPip(wct, container); } else if (wasInPip) { // Exit PIP. // Updates the presentation of the container. Expand or launch placeholder if needed. - mPresenter.updateContainer(container); + updateContainer(wct, container); } + mPresenter.applyTransaction(wct); updateCallbackIfNecessary(); } @@ -188,7 +191,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); if (container != null) { // Cleanup if the TaskFragment vanished is not requested by the organizer. - mPresenter.cleanupContainer(container, true /* shouldFinishDependent */); + removeContainer(container); + // Make sure the top container is updated. + final TaskFragmentContainer newTopContainer = getTopActiveContainer( + container.getTaskId()); + if (newTopContainer != null) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + updateContainer(wct, newTopContainer); + mPresenter.applyTransaction(wct); + } updateCallbackIfNecessary(); } cleanupTaskFragment(taskFragmentInfo.getFragmentToken()); @@ -452,7 +463,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } /** Cleanups all the dependencies when the TaskFragment is entering PIP. */ - private void cleanupForEnterPip(@NonNull TaskFragmentContainer container) { + private void cleanupForEnterPip(@NonNull WindowContainerTransaction wct, + @NonNull TaskFragmentContainer container) { final int taskId = container.getTaskId(); final TaskContainer taskContainer = mTaskContainers.get(taskId); if (taskContainer == null) { @@ -482,7 +494,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // If there is any TaskFragment split with the PIP TaskFragment, update their presentations // since the split is dismissed. // We don't want to close any of them even if they are dependencies of the PIP TaskFragment. - mPresenter.updateContainers(containersToUpdate); + for (TaskFragmentContainer containerToUpdate : containersToUpdate) { + updateContainer(wct, containerToUpdate); + } } /** @@ -502,6 +516,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // TaskFragment there. taskContainer.mFinishedContainer.add(container.getTaskFragmentToken()); + // Cleanup any split references. final List<SplitContainer> containersToRemove = new ArrayList<>(); for (SplitContainer splitContainer : taskContainer.mSplitContainers) { if (container.equals(splitContainer.getSecondaryContainer()) @@ -510,6 +525,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } taskContainer.mSplitContainers.removeAll(containersToRemove); + + // Cleanup any dependent references. + for (TaskFragmentContainer containerToUpdate : taskContainer.mContainers) { + containerToUpdate.removeContainerToFinishOnExit(container); + } } /** 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 b55c16e3b45d..1b49585ed7dc 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -36,7 +36,6 @@ import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import java.util.Collection; import java.util.concurrent.Executor; /** @@ -73,16 +72,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { } /** - * Updates the presentation of the provided containers. + * Deletes the specified container and all other associated and dependent containers in the same + * transaction. */ - void updateContainers(@NonNull Collection<TaskFragmentContainer> containers) { - if (containers.isEmpty()) { - return; - } + void cleanupContainer(@NonNull TaskFragmentContainer container, boolean shouldFinishDependent) { final WindowContainerTransaction wct = new WindowContainerTransaction(); - for (TaskFragmentContainer container : containers) { - mController.updateContainer(wct, container); - } + cleanupContainer(container, shouldFinishDependent, wct); applyTransaction(wct); } @@ -90,9 +85,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * Deletes the specified container and all other associated and dependent containers in the same * transaction. */ - void cleanupContainer(@NonNull TaskFragmentContainer container, boolean shouldFinishDependent) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - + void cleanupContainer(@NonNull TaskFragmentContainer container, boolean shouldFinishDependent, + @NonNull WindowContainerTransaction wct) { container.finish(shouldFinishDependent, this, wct, mController); final TaskFragmentContainer newTopContainer = mController.getTopActiveContainer( @@ -100,8 +94,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { if (newTopContainer != null) { mController.updateContainer(wct, newTopContainer); } - - applyTransaction(wct); } /** diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index 20c929b26acb..871f545d203a 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -257,7 +257,8 @@ class TaskFragmentContainer { // Finish dependent containers for (TaskFragmentContainer container : mContainersToFinishOnExit) { - if (controller.shouldRetainAssociatedContainer(this, container)) { + if (container.mIsFinished + || controller.shouldRetainAssociatedContainer(this, container)) { continue; } container.finish(true /* shouldFinishDependent */, presenter, @@ -267,18 +268,13 @@ class TaskFragmentContainer { // Finish associated activities for (Activity activity : mActivitiesToFinishOnExit) { - if (controller.shouldRetainAssociatedActivity(this, activity)) { + if (activity.isFinishing() + || controller.shouldRetainAssociatedActivity(this, activity)) { continue; } activity.finish(); } mActivitiesToFinishOnExit.clear(); - - // Finish activities that were being re-parented to this container. - for (Activity activity : mPendingAppearedActivities) { - activity.finish(); - } - mPendingAppearedActivities.clear(); } boolean isFinished() { diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index a26a4b657d68..72519dc6da5f 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -17,13 +17,20 @@ package androidx.window.extensions.embedding; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import android.app.Activity; +import android.content.res.Configuration; +import android.content.res.Resources; import android.platform.test.annotations.Presubmit; +import android.window.TaskFragmentInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -32,12 +39,14 @@ import androidx.window.extensions.embedding.SplitController.TaskContainer; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; /** * Test class for {@link SplitController}. * * Build/Install/Run: - * atest WMJetpackUnitTests:SplitController + * atest WMJetpackUnitTests:SplitControllerTest */ @Presubmit @SmallTest @@ -45,12 +54,24 @@ import org.junit.runner.RunWith; public class SplitControllerTest { private static final int TASK_ID = 10; + @Mock + private Activity mActivity; + @Mock + private Resources mActivityResources; + @Mock + private TaskFragmentInfo mInfo; private SplitController mSplitController; + private SplitPresenter mSplitPresenter; @Before public void setUp() { + MockitoAnnotations.initMocks(this); mSplitController = new SplitController(); + mSplitPresenter = mSplitController.mPresenter; spyOn(mSplitController); + spyOn(mSplitPresenter); + doReturn(mActivityResources).when(mActivity).getResources(); + doReturn(new Configuration()).when(mActivityResources).getConfiguration(); } @Test @@ -83,4 +104,17 @@ public class SplitControllerTest { assertWithMessage("Must return null because tf1 has no running activity.") .that(mSplitController.getTopActiveContainer(TASK_ID)).isNull(); } + + @Test + public void testOnTaskFragmentVanished() { + final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); + doReturn(tf.getTaskFragmentToken()).when(mInfo).getFragmentToken(); + + // The TaskFragment has been removed in the server, we only need to cleanup the reference. + mSplitController.onTaskFragmentVanished(mInfo); + + verify(mSplitPresenter, never()).deleteTaskFragment(any(), any()); + verify(mSplitController).removeContainer(tf); + verify(mActivity, never()).finish(); + } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java new file mode 100644 index 000000000000..97896c2c0a57 --- /dev/null +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java @@ -0,0 +1,97 @@ +/* + * 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 androidx.window.extensions.embedding; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; + +import android.app.Activity; +import android.platform.test.annotations.Presubmit; +import android.window.TaskFragmentInfo; +import android.window.WindowContainerTransaction; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; + +/** + * Test class for {@link TaskFragmentContainer}. + * + * Build/Install/Run: + * atest WMJetpackUnitTests:TaskFragmentContainerTest + */ +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class TaskFragmentContainerTest { + private static final int TASK_ID = 10; + + @Mock + private SplitPresenter mPresenter; + @Mock + private SplitController mController; + @Mock + private Activity mActivity; + @Mock + private TaskFragmentInfo mInfo; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testFinish() { + final TaskFragmentContainer container = new TaskFragmentContainer(mActivity, TASK_ID); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + + // Only remove the activity, but not clear the reference until appeared. + container.finish(true /* shouldFinishDependent */, mPresenter, wct, mController); + + verify(mActivity).finish(); + verify(mPresenter, never()).deleteTaskFragment(any(), any()); + verify(mController, never()).removeContainer(any()); + + // Calling twice should not finish activity again. + clearInvocations(mActivity); + container.finish(true /* shouldFinishDependent */, mPresenter, wct, mController); + + verify(mActivity, never()).finish(); + verify(mPresenter, never()).deleteTaskFragment(any(), any()); + verify(mController, never()).removeContainer(any()); + + // Remove all references after the container has appeared in server. + doReturn(new ArrayList<>()).when(mInfo).getActivities(); + container.setInfo(mInfo); + container.finish(true /* shouldFinishDependent */, mPresenter, wct, mController); + + verify(mActivity, never()).finish(); + verify(mPresenter).deleteTaskFragment(wct, container.getTaskFragmentToken()); + verify(mController).removeContainer(container); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 42ac19509693..cfd06243da3c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -301,7 +301,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont int backType = mBackNavigationInfo.getType(); RemoteAnimationTarget animationTarget = mBackNavigationInfo.getDepartingAnimationTarget(); - BackEvent backEvent = new BackEvent(0, 0, progress, swipeEdge, animationTarget); + BackEvent backEvent = new BackEvent( + event.getX(), event.getY(), progress, swipeEdge, animationTarget); IOnBackInvokedCallback targetCallback = null; if (shouldDispatchToLauncher(backType)) { targetCallback = mBackToLauncherCallback; 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 b6fb82852c2f..5ef24133ca11 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 @@ -385,7 +385,9 @@ public class BubbleController { mMainExecutor.execute(() -> { int expandedId = INVALID_TASK_ID; if (mStackView != null && mStackView.getExpandedBubble() != null - && isStackExpanded() && !mStackView.isExpansionAnimating()) { + && isStackExpanded() + && !mStackView.isExpansionAnimating() + && !mStackView.isSwitchAnimating()) { expandedId = mStackView.getExpandedBubble().getTaskId(); } if (expandedId != INVALID_TASK_ID && expandedId != taskId) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index 6eb8d8aec417..7cfacbcc92f8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -329,6 +329,11 @@ public class BubblePositioner { : mBubbleSize; } + /** Size of the visible (non-overlapping) part of the pointer. */ + public int getPointerSize() { + return mPointerHeight - mPointerOverlap; + } + /** The maximum number of bubbles that can be displayed comfortably on screen. */ public int getMaxBubbles() { return mMaxBubbles; @@ -367,7 +372,7 @@ public class BubblePositioner { * padding is added. */ public int[] getExpandedViewContainerPadding(boolean onLeft, boolean isOverflow) { - final int pointerTotalHeight = mPointerHeight - mPointerOverlap; + final int pointerTotalHeight = getPointerSize(); final int expandedViewLargeScreenInsetFurthestEdge = getExpandedViewLargeScreenInsetFurthestEdge(isOverflow); if (mIsLargeScreen) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 322c0bf80816..b7c5eb06fbfa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -1608,6 +1608,13 @@ public class BubbleStackView extends FrameLayout } /** + * Whether the stack of bubbles is animating a switch between bubbles. + */ + public boolean isSwitchAnimating() { + return mIsBubbleSwitchAnimating; + } + + /** * The {@link Bubble} that is expanded, null if one does not exist. */ @VisibleForTesting @@ -2467,6 +2474,10 @@ public class BubbleStackView extends FrameLayout private void dismissBubbleIfExists(@Nullable BubbleViewProvider bubble) { if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) { + if (mIsExpanded && mBubbleData.getBubbles().size() > 1) { + // If we have more than 1 bubble we will perform the switch animation + mIsBubbleSwitchAnimating = true; + } mBubbleData.dismissBubbleWithKey(bubble.getKey(), Bubbles.DISMISS_USER_GESTURE); } } @@ -2887,7 +2898,10 @@ public class BubbleStackView extends FrameLayout PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel(); mAnimatingOutSurfaceContainer.setScaleX(1f); mAnimatingOutSurfaceContainer.setScaleY(1f); - mAnimatingOutSurfaceContainer.setTranslationX(mExpandedViewContainer.getPaddingLeft()); + final float translationX = mPositioner.showBubblesVertically() && mStackOnLeftOrWillBe + ? mExpandedViewContainer.getPaddingLeft() + mPositioner.getPointerSize() + : mExpandedViewContainer.getPaddingLeft(); + mAnimatingOutSurfaceContainer.setTranslationX(translationX); mAnimatingOutSurfaceContainer.setTranslationY(0); final int[] taskViewLocation = diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java index 003c55977841..4bb580571145 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java @@ -40,7 +40,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.policy.ForceShowNavigationBarSettingsObserver; +import com.android.internal.policy.KidsModeSettingsObserver; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayInsetsController; @@ -85,7 +85,7 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { private int mDisplayWidth; private int mDisplayHeight; - private ForceShowNavigationBarSettingsObserver mForceShowNavigationBarSettingsObserver; + private KidsModeSettingsObserver mKidsModeSettingsObserver; private boolean mEnabled; DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener = @@ -138,14 +138,14 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { DisplayController displayController, DisplayInsetsController displayInsetsController, Optional<RecentTasksController> recentTasks, - ForceShowNavigationBarSettingsObserver forceShowNavigationBarSettingsObserver) { + KidsModeSettingsObserver kidsModeSettingsObserver) { super(taskOrganizerController, mainExecutor, context, /* compatUI= */ null, recentTasks); mContext = context; mMainHandler = mainHandler; mSyncQueue = syncTransactionQueue; mDisplayController = displayController; mDisplayInsetsController = displayInsetsController; - mForceShowNavigationBarSettingsObserver = forceShowNavigationBarSettingsObserver; + mKidsModeSettingsObserver = kidsModeSettingsObserver; } public KidsModeTaskOrganizer( @@ -169,13 +169,13 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { */ public void initialize(StartingWindowController startingWindowController) { initStartingWindow(startingWindowController); - if (mForceShowNavigationBarSettingsObserver == null) { - mForceShowNavigationBarSettingsObserver = new ForceShowNavigationBarSettingsObserver( + if (mKidsModeSettingsObserver == null) { + mKidsModeSettingsObserver = new KidsModeSettingsObserver( mMainHandler, mContext); } - mForceShowNavigationBarSettingsObserver.setOnChangeRunnable(() -> updateKidsModeState()); + mKidsModeSettingsObserver.setOnChangeRunnable(() -> updateKidsModeState()); updateKidsModeState(); - mForceShowNavigationBarSettingsObserver.register(); + mKidsModeSettingsObserver.register(); } @Override @@ -211,7 +211,7 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { @VisibleForTesting void updateKidsModeState() { - final boolean enabled = mForceShowNavigationBarSettingsObserver.isEnabled(); + final boolean enabled = mKidsModeSettingsObserver.isEnabled(); if (mEnabled == enabled) { return; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index b6635f3b8dd7..9dc861c72510 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -924,6 +924,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, removeContentOverlay(mSwipePipToHomeOverlay, null /* callback */); mSwipePipToHomeOverlay = null; } + resetShadowRadius(); mPipTransitionState.setInSwipePipToHomeTransition(false); mPictureInPictureParams = null; mPipTransitionState.setTransitionState(PipTransitionState.UNDEFINED); @@ -1569,13 +1570,28 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // Avoid double removal, which is fatal. return; } - final SurfaceControl.Transaction tx = - mSurfaceControlTransactionFactory.getTransaction(); + final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); tx.remove(surface); tx.apply(); if (callback != null) callback.run(); } + private void resetShadowRadius() { + if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) { + // mLeash is undefined when in PipTransitionState.UNDEFINED + return; + } + final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); + tx.setShadowRadius(mLeash, 0f); + tx.apply(); + } + + @VisibleForTesting + public void setSurfaceControlTransactionFactory( + PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) { + mSurfaceControlTransactionFactory = factory; + } + /** * Dumps internal states. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 10dfdc30d0d6..dd2634ca36d9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -414,7 +414,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, RemoteAnimationTarget[] apps) { - if (ENABLE_SHELL_TRANSITIONS || apps.length < 2) return null; + if (ENABLE_SHELL_TRANSITIONS || !isSplitScreenVisible()) return null; // TODO(b/206487881): Integrate this with shell transition. SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); if (mSplitTasksContainerLayer != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java index 75a999bcb292..d89ddd2074f0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java @@ -56,7 +56,6 @@ import android.util.ArrayMap; import android.util.Slog; import android.view.ContextThemeWrapper; import android.view.SurfaceControl; -import android.view.View; import android.window.SplashScreenView; import android.window.StartingWindowInfo; import android.window.StartingWindowInfo.StartingWindowType; @@ -543,22 +542,6 @@ public class SplashscreenContentDrawer { mBrandingImageHeight); } final SplashScreenView splashScreenView = builder.build(); - if (mSuggestType != STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) { - splashScreenView.addOnAttachStateChangeListener( - new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View v) { - SplashScreenView.applySystemBarsContrastColor( - v.getWindowInsetsController(), - splashScreenView.getInitBackgroundColor()); - } - - @Override - public void onViewDetachedFromWindow(View v) { - } - }); - } - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); return splashScreenView; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index 11f23e3e941b..464ab1ae2a8c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -50,6 +50,7 @@ import android.view.Choreographer; import android.view.Display; import android.view.SurfaceControlViewHost; import android.view.View; +import android.view.WindowInsetsController; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.widget.FrameLayout; @@ -63,6 +64,7 @@ import android.window.TaskSnapshot; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.util.ContrastColorUtil; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; @@ -121,6 +123,9 @@ public class StartingSurfaceDrawer { private StartingSurface.SysuiProxy mSysuiProxy; private final StartingWindowRemovalInfo mTmpRemovalInfo = new StartingWindowRemovalInfo(); + private static final int LIGHT_BARS_MASK = + WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS + | WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; /** * The minimum duration during which the splash screen is shown when the splash screen icon is * animated. @@ -361,9 +366,27 @@ public class StartingSurfaceDrawer { // the window before first round relayoutWindow, which will happen after insets // animation. mChoreographer.postCallback(CALLBACK_INSETS_ANIMATION, setViewSynchronized, null); - // Block until we get the background color. final StartingWindowRecord record = mStartingWindowRecords.get(taskId); + record.parseAppSystemBarColor(context); + // Block until we get the background color. final SplashScreenView contentView = viewSupplier.get(); + if (suggestType != STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) { + contentView.addOnAttachStateChangeListener( + new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + final int lightBarAppearance = ContrastColorUtil.isColorLight( + contentView.getInitBackgroundColor()) + ? LIGHT_BARS_MASK : 0; + contentView.getWindowInsetsController().setSystemBarsAppearance( + lightBarAppearance, LIGHT_BARS_MASK); + } + + @Override + public void onViewDetachedFromWindow(View v) { + } + }); + } record.mBGColor = contentView.getInitBackgroundColor(); } else { // release the icon view host @@ -613,6 +636,7 @@ public class StartingSurfaceDrawer { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, "Removing splash screen window for task: %d", taskId); if (record.mContentView != null) { + record.clearSystemBarColor(); if (immediately || record.mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) { removeWindowInner(record.mDecorView, false); @@ -670,6 +694,8 @@ public class StartingSurfaceDrawer { private @StartingWindowType int mSuggestType; private int mBGColor; private final long mCreateTime; + private int mSystemBarAppearance; + private boolean mDrawsSystemBarBackgrounds; StartingWindowRecord(IBinder appToken, View decorView, TaskSnapshotWindow taskSnapshotWindow, @StartingWindowType int suggestType) { @@ -690,5 +716,37 @@ public class StartingSurfaceDrawer { mContentView = splashScreenView; mSetSplashScreen = true; } + + private void parseAppSystemBarColor(Context context) { + final TypedArray a = context.obtainStyledAttributes(R.styleable.Window); + mDrawsSystemBarBackgrounds = a.getBoolean( + R.styleable.Window_windowDrawsSystemBarBackgrounds, false); + if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) { + mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; + } + if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) { + mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; + } + a.recycle(); + } + + // Reset the system bar color which set by splash screen, make it align to the app. + private void clearSystemBarColor() { + if (mDecorView == null) { + return; + } + if (mDecorView.getLayoutParams() instanceof WindowManager.LayoutParams) { + final WindowManager.LayoutParams lp = + (WindowManager.LayoutParams) mDecorView.getLayoutParams(); + if (mDrawsSystemBarBackgrounds) { + lp.flags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + } else { + lp.flags &= ~WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + } + mDecorView.setLayoutParams(lp); + } + mDecorView.getWindowInsetsController().setSystemBarsAppearance( + mSystemBarAppearance, LIGHT_BARS_MASK); + } } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java index ff6dfdb748c4..5526d5be7594 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java @@ -44,7 +44,7 @@ import android.window.WindowContainerTransaction; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.android.internal.policy.ForceShowNavigationBarSettingsObserver; +import com.android.internal.policy.KidsModeSettingsObserver; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; @@ -72,7 +72,7 @@ public class KidsModeTaskOrganizerTest { @Mock private SurfaceControl mLeash; @Mock private WindowContainerToken mToken; @Mock private WindowContainerTransaction mTransaction; - @Mock private ForceShowNavigationBarSettingsObserver mObserver; + @Mock private KidsModeSettingsObserver mObserver; @Mock private StartingWindowController mStartingWindowController; @Mock private DisplayInsetsController mDisplayInsetsController; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java index 8ef1df606b43..c685fdc1f09c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java @@ -30,7 +30,6 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import android.app.TaskInfo; -import android.graphics.Matrix; import android.graphics.Rect; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -104,7 +103,7 @@ public class PipAnimationControllerTest extends ShellTestCase { final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null, TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0); - oldAnimator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new); + oldAnimator.setSurfaceControlTransactionFactory(PipDummySurfaceControlTx::new); oldAnimator.start(); final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController @@ -134,7 +133,7 @@ public class PipAnimationControllerTest extends ShellTestCase { @Test public void pipTransitionAnimator_rotatedEndValue() { - final DummySurfaceControlTx tx = new DummySurfaceControlTx(); + final PipDummySurfaceControlTx tx = new PipDummySurfaceControlTx(); final Rect startBounds = new Rect(200, 700, 400, 800); final Rect endBounds = new Rect(0, 0, 500, 1000); // Fullscreen to PiP. @@ -184,7 +183,7 @@ public class PipAnimationControllerTest extends ShellTestCase { final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null, TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0); - animator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new); + animator.setSurfaceControlTransactionFactory(PipDummySurfaceControlTx::new); animator.setPipAnimationCallback(mPipAnimationCallback); @@ -201,44 +200,4 @@ public class PipAnimationControllerTest extends ShellTestCase { verify(mPipAnimationCallback).onPipAnimationEnd(eq(mTaskInfo), any(SurfaceControl.Transaction.class), eq(animator)); } - - /** - * A dummy {@link SurfaceControl.Transaction} class. - * This is created as {@link Mock} does not support method chaining. - */ - public static class DummySurfaceControlTx extends SurfaceControl.Transaction { - @Override - public SurfaceControl.Transaction setAlpha(SurfaceControl leash, float alpha) { - return this; - } - - @Override - public SurfaceControl.Transaction setPosition(SurfaceControl leash, float x, float y) { - return this; - } - - @Override - public SurfaceControl.Transaction setWindowCrop(SurfaceControl leash, int w, int h) { - return this; - } - - @Override - public SurfaceControl.Transaction setCornerRadius(SurfaceControl leash, float radius) { - return this; - } - - @Override - public SurfaceControl.Transaction setMatrix(SurfaceControl leash, Matrix matrix, - float[] float9) { - return this; - } - - @Override - public SurfaceControl.Transaction setFrameTimelineVsync(long frameTimelineVsyncId) { - return this; - } - - @Override - public void apply() {} - } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipDummySurfaceControlTx.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipDummySurfaceControlTx.java new file mode 100644 index 000000000000..ccf8f6e03844 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipDummySurfaceControlTx.java @@ -0,0 +1,66 @@ +/* + * 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.pip; + +import android.graphics.Matrix; +import android.view.SurfaceControl; + +/** + * A dummy {@link SurfaceControl.Transaction} class for testing purpose and supports + * method chaining. + */ +public class PipDummySurfaceControlTx extends SurfaceControl.Transaction { + @Override + public SurfaceControl.Transaction setAlpha(SurfaceControl leash, float alpha) { + return this; + } + + @Override + public SurfaceControl.Transaction setPosition(SurfaceControl leash, float x, float y) { + return this; + } + + @Override + public SurfaceControl.Transaction setWindowCrop(SurfaceControl leash, int w, int h) { + return this; + } + + @Override + public SurfaceControl.Transaction setCornerRadius(SurfaceControl leash, float radius) { + return this; + } + + @Override + public SurfaceControl.Transaction setShadowRadius(SurfaceControl leash, float radius) { + return this; + } + + @Override + public SurfaceControl.Transaction setMatrix(SurfaceControl leash, Matrix matrix, + float[] float9) { + return this; + } + + @Override + public SurfaceControl.Transaction setFrameTimelineVsync(long frameTimelineVsyncId) { + return this; + } + + @Override + public void apply() {} +} + diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index 14d9fb9babc4..def9ad20a632 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -242,6 +242,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { mPipBoundsState.setDisplayLayout(new DisplayLayout(info, mContext.getResources(), true, true)); mSpiedPipTaskOrganizer.setOneShotAnimationType(PipAnimationController.ANIM_TYPE_ALPHA); + mSpiedPipTaskOrganizer.setSurfaceControlTransactionFactory(PipDummySurfaceControlTx::new); doNothing().when(mSpiedPipTaskOrganizer).enterPipWithAlphaAnimation(any(), anyLong()); doNothing().when(mSpiedPipTaskOrganizer).scheduleAnimateResizePip(any(), anyInt(), any()); } diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java index a3e731b0ffa1..d0973f4eb11f 100644 --- a/media/java/android/media/tv/tuner/filter/Filter.java +++ b/media/java/android/media/tv/tuner/filter/Filter.java @@ -560,6 +560,11 @@ public class Filter implements AutoCloseable { */ @Override public void close() { + synchronized (mCallbackLock) { + mCallback = null; + mExecutor = null; + } + synchronized (mLock) { if (mIsClosed) { return; @@ -568,8 +573,6 @@ public class Filter implements AutoCloseable { if (res != Tuner.RESULT_SUCCESS) { TunerUtils.throwExceptionForResult(res, "Failed to close filter."); } else { - mCallback = null; - mExecutor = null; mIsStarted = false; mIsClosed = true; } diff --git a/packages/SettingsLib/Utils/Android.bp b/packages/SettingsLib/Utils/Android.bp index d64fe7fb0009..7d5eb69190b6 100644 --- a/packages/SettingsLib/Utils/Android.bp +++ b/packages/SettingsLib/Utils/Android.bp @@ -15,7 +15,6 @@ android_library { static_libs: [ "androidx.annotation_annotation", - "modules-utils-build", ], sdk_version: "system_current", diff --git a/packages/SettingsLib/Utils/src/com/android/settingslib/utils/BuildCompatUtils.java b/packages/SettingsLib/Utils/src/com/android/settingslib/utils/BuildCompatUtils.java index fc945040fc83..ddbc907018c8 100644 --- a/packages/SettingsLib/Utils/src/com/android/settingslib/utils/BuildCompatUtils.java +++ b/packages/SettingsLib/Utils/src/com/android/settingslib/utils/BuildCompatUtils.java @@ -17,11 +17,10 @@ package com.android.settingslib.utils; import android.os.Build; +import android.os.Build.VERSION; import androidx.annotation.ChecksSdkIntAtLeast; -import com.android.modules.utils.build.SdkLevel; - /** * An util class to check whether the current OS version is higher or equal to sdk version of * device. @@ -35,7 +34,7 @@ public final class BuildCompatUtils { */ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S) public static boolean isAtLeastS() { - return SdkLevel.isAtLeastS(); + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S; } /** @@ -45,7 +44,7 @@ public final class BuildCompatUtils { */ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S_V2) public static boolean isAtLeastSV2() { - return SdkLevel.isAtLeastSv2(); + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S_V2; } /** @@ -55,7 +54,7 @@ public final class BuildCompatUtils { */ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU) public static boolean isAtLeastT() { - return SdkLevel.isAtLeastT(); + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU; } private BuildCompatUtils() {} diff --git a/packages/SettingsLib/res/drawable/broadcast_dialog_btn_bg.xml b/packages/SettingsLib/res/drawable/broadcast_dialog_btn_bg.xml new file mode 100644 index 000000000000..5fd7ee29d838 --- /dev/null +++ b/packages/SettingsLib/res/drawable/broadcast_dialog_btn_bg.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:shape="rectangle"> + <solid android:color="?androidprv:attr/colorAccentPrimary" /> + <corners android:radius="@dimen/broadcast_dialog_btn_radius" /> +</shape> diff --git a/packages/SettingsLib/res/layout/broadcast_dialog.xml b/packages/SettingsLib/res/layout/broadcast_dialog.xml new file mode 100644 index 000000000000..ec69aa597180 --- /dev/null +++ b/packages/SettingsLib/res/layout/broadcast_dialog.xml @@ -0,0 +1,84 @@ +<?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. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:id="@+id/dialog_bg" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/broadcast_dialog_margin" + android:orientation="vertical"> + + <ImageView + android:id="@+id/dialog_icon" + android:layout_width="@dimen/broadcast_dialog_icon_size" + android:layout_height="@dimen/broadcast_dialog_icon_size" + android:layout_marginTop="@dimen/broadcast_dialog_icon_margin_top" + android:layout_marginBottom="@dimen/broadcast_dialog_title_img_margin_top" + android:layout_gravity="center" + android:src="@drawable/settings_input_antenna"/> + + <TextView + style="@style/BroadcastDialogTitleStyle" + android:id="@+id/dialog_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:layout_gravity="center"/> + + <TextView + style="@style/BroadcastDialogBodyStyle" + android:id="@+id/dialog_subtitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:layout_gravity="center"/> + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginHorizontal="@dimen/broadcast_dialog_margin" + android:layout_marginBottom="@dimen/broadcast_dialog_margin" + android:orientation="vertical"> + + <Button + android:layout_marginBottom="@dimen/broadcast_dialog_btn_margin_bottom" + android:id="@+id/positive_btn" + style="@style/BroadcastDialogButtonStyle"/> + + <Button + android:layout_marginBottom="@dimen/broadcast_dialog_btn_margin_bottom" + android:id="@+id/negative_btn" + android:text="@string/bt_le_audio_broadcast_dialog_different_output" + style="@style/BroadcastDialogButtonStyle"/> + + <Button + android:id="@+id/neutral_btn" + android:text="@android:string/cancel" + style="@style/BroadcastDialogButtonStyle"/> + </LinearLayout> + + </LinearLayout> +</FrameLayout> diff --git a/packages/SettingsLib/res/layout/qrcode_scanner_fragment.xml b/packages/SettingsLib/res/layout/qrcode_scanner_fragment.xml index e071f4c469a6..0a7fe0903348 100644 --- a/packages/SettingsLib/res/layout/qrcode_scanner_fragment.xml +++ b/packages/SettingsLib/res/layout/qrcode_scanner_fragment.xml @@ -92,6 +92,7 @@ android:layout_marginStart="?attr/sudMarginStart" android:layout_marginEnd="?attr/sudMarginEnd" android:gravity="center" + android:layout_gravity="center" android:visibility="invisible"/> </LinearLayout> diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml index 8315e8b069c8..cbc79d2d5d93 100644 --- a/packages/SettingsLib/res/values/dimens.xml +++ b/packages/SettingsLib/res/values/dimens.xml @@ -120,4 +120,18 @@ <dimen name="qrcode_preview_margin">40dp</dimen> <dimen name="qrcode_preview_radius">30dp</dimen> <dimen name="qrcode_icon_size">27dp</dimen> + + <!-- Broadcast dialog --> + <dimen name="broadcast_dialog_title_img_margin_top">18dp</dimen> + <dimen name="broadcast_dialog_title_text_size">24sp</dimen> + <dimen name="broadcast_dialog_title_text_margin">16dp</dimen> + <dimen name="broadcast_dialog_title_text_margin_top">18dp</dimen> + <dimen name="broadcast_dialog_subtitle_text_size">14sp</dimen> + <dimen name="broadcast_dialog_icon_size">24dp</dimen> + <dimen name="broadcast_dialog_icon_margin_top">25dp</dimen> + <dimen name="broadcast_dialog_btn_radius">100dp</dimen> + <dimen name="broadcast_dialog_btn_margin_bottom">4dp</dimen> + <dimen name="broadcast_dialog_btn_text_size">16sp</dimen> + <dimen name="broadcast_dialog_btn_minHeight">44dp</dimen> + <dimen name="broadcast_dialog_margin">16dp</dimen> </resources> diff --git a/packages/SettingsLib/res/values/styles.xml b/packages/SettingsLib/res/values/styles.xml index 28cd27bf0cad..32345150531d 100644 --- a/packages/SettingsLib/res/values/styles.xml +++ b/packages/SettingsLib/res/values/styles.xml @@ -39,4 +39,49 @@ <item name="android:textColor">?android:attr/textColorPrimary</item> <item name="android:textDirection">locale</item> </style> + + <style name="BroadcastDialogTitleStyle"> + <item name="android:textAppearance">@style/TextAppearanceBroadcastDialogTitle</item> + <item name="android:layout_marginStart">@dimen/broadcast_dialog_title_text_margin</item> + <item name="android:layout_marginEnd">@dimen/broadcast_dialog_title_text_margin</item> + <item name="android:layout_marginTop">@dimen/broadcast_dialog_title_text_margin_top</item> + <item name="android:layout_marginBottom">18dp</item> + </style> + + <style name="TextAppearanceBroadcastDialogTitle" parent="@android:TextAppearance.DeviceDefault.Headline"> + <item name="android:textSize">@dimen/broadcast_dialog_title_text_size</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textDirection">locale</item> + <item name="android:ellipsize">end</item> + </style> + + <style name="BroadcastDialogBodyStyle"> + <item name="android:textAppearance">@style/TextAppearanceBroadcastDialogSubTitle</item> + <item name="android:layout_margin">@dimen/broadcast_dialog_title_text_margin</item> + </style> + + <style name="TextAppearanceBroadcastDialogSubTitle" parent="@android:TextAppearance.DeviceDefault.Headline"> + <item name="android:textSize">@dimen/broadcast_dialog_subtitle_text_size</item> + <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:textDirection">locale</item> + <item name="android:ellipsize">end</item> + </style> + + <style name="BroadcastDialogButtonStyle"> + <item name="android:textAppearance">@style/TextAppearanceBroadcastDialogButton</item> + <item name="android:layout_width">match_parent</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:layout_gravity">center</item> + <item name="android:gravity">center</item> + <item name="android:stateListAnimator">@null</item> + <item name="android:elevation">0dp</item> + <item name="android:minHeight">@dimen/broadcast_dialog_btn_minHeight</item> + <item name="android:background">@drawable/broadcast_dialog_btn_bg</item> + </style> + + <style name="TextAppearanceBroadcastDialogButton" parent="@android:TextAppearance.DeviceDefault.Headline"> + <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textSize">@dimen/broadcast_dialog_btn_text_size</item> + </style> + </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java index 5c9ec4b3e6f1..f627bcb5db95 100644 --- a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java @@ -37,7 +37,7 @@ import android.util.Log; import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; -import com.android.modules.utils.build.SdkLevel; +import com.android.settingslib.utils.BuildCompatUtils; import java.io.BufferedReader; import java.io.FileReader; @@ -221,7 +221,7 @@ public class DeviceInfoUtils { } private static String getRawPhoneNumber(Context context, int subscriptionId) { - if (SdkLevel.isAtLeastT()) { + if (BuildCompatUtils.isAtLeastT()) { return getRawPhoneNumberFromT(context, subscriptionId); } else { final TelephonyManager telephonyManager = context.getSystemService( diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java index 08aeeaec8bb0..284da73efb6f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java @@ -34,7 +34,7 @@ import androidx.annotation.RequiresApi; import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; -import com.android.modules.utils.build.SdkLevel; +import com.android.settingslib.utils.BuildCompatUtils; /** * Helper class for managing settings preferences that can be disabled @@ -106,7 +106,7 @@ public class RestrictedPreferenceHelper { if (mDisabledSummary) { final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary); if (summaryView != null) { - final CharSequence disabledText = SdkLevel.isAtLeastT() + final CharSequence disabledText = BuildCompatUtils.isAtLeastT() ? getDisabledByAdminUpdatableString() : mContext.getString(R.string.disabled_by_admin_summary_text); if (mDisabledByAdmin) { diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index a7f12022e172..feb4212035bc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -50,9 +50,9 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.UserIcons; import com.android.launcher3.icons.BaseIconFactory.IconOptions; import com.android.launcher3.icons.IconFactory; -import com.android.modules.utils.build.SdkLevel; import com.android.settingslib.drawable.UserIconDrawable; import com.android.settingslib.fuelgauge.BatteryStatus; +import com.android.settingslib.utils.BuildCompatUtils; import java.text.NumberFormat; @@ -130,7 +130,7 @@ public class Utils { String name = info != null ? info.name : null; if (info.isManagedProfile()) { // We use predefined values for managed profiles - return SdkLevel.isAtLeastT() + return BuildCompatUtils.isAtLeastT() ? getUpdatableManagedUserTitle(context) : context.getString(R.string.managed_user_title); } else if (info.isGuest()) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastDialog.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastDialog.java new file mode 100644 index 000000000000..cb4eba422366 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastDialog.java @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.bluetooth; + +import android.app.AlertDialog; +import android.content.Context; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.Window; +import android.widget.Button; +import android.widget.TextView; + +import com.android.settingslib.R; + +public class BroadcastDialog extends AlertDialog { + + private static final String TAG = "BroadcastDialog"; + + private String mCurrentApp; + private String mSwitchApp; + private Context mContext; + + public BroadcastDialog(Context context) { + super(context); + mContext = context; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + + View layout = View.inflate(mContext, R.layout.broadcast_dialog, null); + final Window window = getWindow(); + window.setContentView(layout); + window.setWindowAnimations(R.style.Theme_AlertDialog_SettingsLib); + + TextView title = layout.findViewById(R.id.dialog_title); + TextView subTitle = layout.findViewById(R.id.dialog_subtitle); + title.setText(mContext.getString(R.string.bt_le_audio_broadcast_dialog_title, mCurrentApp)); + subTitle.setText( + mContext.getString(R.string.bt_le_audio_broadcast_dialog_sub_title, mSwitchApp)); + Button positiveBtn = layout.findViewById(R.id.positive_btn); + Button negativeBtn = layout.findViewById(R.id.negative_btn); + Button neutralBtn = layout.findViewById(R.id.neutral_btn); + positiveBtn.setText(mContext.getString( + R.string.bt_le_audio_broadcast_dialog_switch_app, mSwitchApp), null); + neutralBtn.setOnClickListener((view) -> { + Log.d(TAG, "BroadcastDialog dismiss."); + dismiss(); + }); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java index 17ac4307d6a6..11fae24aa677 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java @@ -48,7 +48,7 @@ import android.os.UserHandle; import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; -import com.android.modules.utils.build.SdkLevel; +import com.android.settingslib.utils.BuildCompatUtils; /** * Converts the user avatar icon to a circularly clipped one with an optional badge and frame @@ -88,7 +88,7 @@ public class UserIconDrawable extends Drawable implements Drawable.Callback { * @return drawable containing just the badge */ public static Drawable getManagedUserDrawable(Context context) { - if (SdkLevel.isAtLeastT()) { + if (BuildCompatUtils.isAtLeastT()) { return getUpdatableManagedUserDrawable(context); } else { return getDrawableForDisplayDensity( @@ -227,7 +227,7 @@ public class UserIconDrawable extends Drawable implements Drawable.Callback { } private static Drawable getManagementBadge(Context context) { - if (SdkLevel.isAtLeastT()) { + if (BuildCompatUtils.isAtLeastT()) { return getUpdatableManagementBadge(context); } else { return getDrawableForDisplayDensity( diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeActivity.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeActivity.java index 9021fcbc3f99..15a910e13aa8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeActivity.java +++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeActivity.java @@ -26,8 +26,8 @@ import android.util.Log; import androidx.fragment.app.FragmentTransaction; -import com.android.settingslib.bluetooth.BluetoothBroadcastUtils; import com.android.settingslib.R; +import com.android.settingslib.bluetooth.BluetoothBroadcastUtils; import com.android.settingslib.bluetooth.BluetoothUtils; public class QrCodeScanModeActivity extends QrCodeScanModeBaseActivity { diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeBaseActivity.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeBaseActivity.java index 9aaec41e721a..361fd5b57556 100644 --- a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeBaseActivity.java +++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeBaseActivity.java @@ -1,4 +1,4 @@ -/* +/** * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,8 +21,8 @@ import android.os.Bundle; import androidx.fragment.app.FragmentManager; -import com.android.settingslib.core.lifecycle.ObservableActivity; import com.android.settingslib.R; +import com.android.settingslib.core.lifecycle.ObservableActivity; public abstract class QrCodeScanModeBaseActivity extends ObservableActivity { diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeController.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeController.java index d7640bb1b564..153d2d20e801 100644 --- a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeController.java +++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeController.java @@ -1,4 +1,4 @@ -/* +/** * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeFragment.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeFragment.java index 81165aa3330c..069b9507ccef 100644 --- a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeFragment.java +++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeScanModeFragment.java @@ -1,4 +1,4 @@ -/* +/** * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,9 +35,9 @@ import android.view.ViewOutlineProvider; import android.view.accessibility.AccessibilityEvent; import android.widget.TextView; +import com.android.settingslib.R; import com.android.settingslib.bluetooth.BluetoothBroadcastUtils; import com.android.settingslib.bluetooth.BluetoothUtils; -import com.android.settingslib.R; import com.android.settingslib.core.lifecycle.ObservableFragment; import androidx.annotation.NonNull; diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java index 7005d3638f9e..51cf59c25502 100644 --- a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java +++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java @@ -1,4 +1,4 @@ -/* +/** * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrYuvLuminanceSource.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrYuvLuminanceSource.java index 421cf5c40b69..33f0cddb22e0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrYuvLuminanceSource.java +++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrYuvLuminanceSource.java @@ -1,4 +1,4 @@ -/* +/** * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtils.java index 6535665b7653..d97c819e88ef 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtils.java @@ -18,7 +18,6 @@ package com.android.settingslib.wifi; import android.content.Context; import android.os.Build; -import android.os.Bundle; import android.os.UserManager; import android.util.Log; @@ -37,13 +36,9 @@ public class WifiEnterpriseRestrictionUtils { * @return whether the device is permitted to use Wi-Fi Tethering */ public static boolean isWifiTetheringAllowed(Context context) { - final UserManager userManager = context.getSystemService(UserManager.class); - final Bundle restrictions = userManager.getUserRestrictions(); - if (isAtLeastT() && restrictions.getBoolean(UserManager.DISALLOW_WIFI_TETHERING)) { - Log.i(TAG, "Wi-Fi Tethering isn't available due to user restriction."); - return false; - } - return true; + if (!hasUserRestrictionFromT(context, UserManager.DISALLOW_WIFI_TETHERING)) return true; + Log.w(TAG, "Wi-Fi Tethering isn't available due to user restriction."); + return false; } /** @@ -53,13 +48,9 @@ public class WifiEnterpriseRestrictionUtils { * @return whether the device is permitted to use Wi-Fi Direct */ public static boolean isWifiDirectAllowed(Context context) { - final UserManager userManager = context.getSystemService(UserManager.class); - final Bundle restrictions = userManager.getUserRestrictions(); - if (isAtLeastT() && restrictions.getBoolean(UserManager.DISALLOW_WIFI_DIRECT)) { - Log.i(TAG, "Wi-Fi Direct isn't available due to user restriction."); - return false; - } - return true; + if (!hasUserRestrictionFromT(context, UserManager.DISALLOW_WIFI_DIRECT)) return true; + Log.w(TAG, "Wi-Fi Direct isn't available due to user restriction."); + return false; } /** @@ -69,13 +60,9 @@ public class WifiEnterpriseRestrictionUtils { * @return whether the device is permitted to add new Wi-Fi config */ public static boolean isAddWifiConfigAllowed(Context context) { - final UserManager userManager = context.getSystemService(UserManager.class); - final Bundle restrictions = userManager.getUserRestrictions(); - if (isAtLeastT() && restrictions.getBoolean(UserManager.DISALLOW_ADD_WIFI_CONFIG)) { - Log.i(TAG, "Wi-Fi Add network isn't available due to user restriction."); - return false; - } - return true; + if (!hasUserRestrictionFromT(context, UserManager.DISALLOW_ADD_WIFI_CONFIG)) return true; + Log.w(TAG, "Wi-Fi Add network isn't available due to user restriction."); + return false; } /** @@ -98,7 +85,7 @@ public class WifiEnterpriseRestrictionUtils { return userManager.hasUserRestriction(restrictionKey); } - @ChecksSdkIntAtLeast(api=Build.VERSION_CODES.TIRAMISU) + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU) private static boolean isAtLeastT() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU; } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtilsTest.java index e9326dd39faa..af9e69a8270c 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtilsTest.java @@ -15,122 +15,91 @@ */ package com.android.settingslib.wifi; +import static android.os.UserManager.DISALLOW_ADD_WIFI_CONFIG; import static android.os.UserManager.DISALLOW_CHANGE_WIFI_STATE; +import static android.os.UserManager.DISALLOW_WIFI_DIRECT; +import static android.os.UserManager.DISALLOW_WIFI_TETHERING; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.content.Context; import android.os.Build; -import android.os.Bundle; import android.os.UserManager; import androidx.test.core.app.ApplicationProvider; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; import org.robolectric.util.ReflectionHelpers; @RunWith(RobolectricTestRunner.class) public class WifiEnterpriseRestrictionUtilsTest { - private Context mContext; + static final String SDK_INT = "SDK_INT"; + static final int VERSION_CODES_S = Build.VERSION_CODES.S; + static final int VERSION_CODES_T = Build.VERSION_CODES.TIRAMISU; + + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Spy + Context mContext = ApplicationProvider.getApplicationContext(); @Mock private UserManager mUserManager; - @Mock - private Bundle mBundle; @Before public void setUp() { - MockitoAnnotations.initMocks(this); - mContext = spy(ApplicationProvider.getApplicationContext()); when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); - when(mUserManager.getUserRestrictions()).thenReturn(mBundle); - ReflectionHelpers.setStaticField( - Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.TIRAMISU); + ReflectionHelpers.setStaticField(Build.VERSION.class, SDK_INT, VERSION_CODES_T); } @Test - public void isWifiTetheringAllowed_setSDKForS_shouldReturnTrue() { - ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.S); - when(mBundle.getBoolean(UserManager.DISALLOW_WIFI_TETHERING)).thenReturn(true); - - assertThat(WifiEnterpriseRestrictionUtils.isWifiTetheringAllowed(mContext)).isTrue(); - } - - @Test - public void isWifiTetheringAllowed_setSDKForTAndDisallowForRestriction_shouldReturnFalse() { - ReflectionHelpers.setStaticField( - Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.TIRAMISU); - when(mBundle.getBoolean(UserManager.DISALLOW_WIFI_TETHERING)).thenReturn(true); + public void isWifiTetheringAllowed_hasDisallowRestriction_shouldReturnFalse() { + when(mUserManager.hasUserRestriction(DISALLOW_WIFI_TETHERING)).thenReturn(true); assertThat(WifiEnterpriseRestrictionUtils.isWifiTetheringAllowed(mContext)).isFalse(); } @Test - public void isWifiTetheringAllowed_setSDKForTAndAllowForRestriction_shouldReturnTrue() { - ReflectionHelpers.setStaticField( - Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.TIRAMISU); - when(mBundle.getBoolean(UserManager.DISALLOW_WIFI_TETHERING)).thenReturn(false); + public void isWifiTetheringAllowed_noDisallowRestriction_shouldReturnTrue() { + when(mUserManager.hasUserRestriction(DISALLOW_WIFI_TETHERING)).thenReturn(false); assertThat(WifiEnterpriseRestrictionUtils.isWifiTetheringAllowed(mContext)).isTrue(); } @Test - public void isWifiDirectAllowed_setSDKForS_shouldReturnTrue() { - ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.S); - when(mBundle.getBoolean(UserManager.DISALLOW_WIFI_DIRECT)).thenReturn(true); - - assertThat(WifiEnterpriseRestrictionUtils.isWifiDirectAllowed(mContext)).isTrue(); - } - - @Test - public void isWifiDirectAllowed_setSDKForTAndDisallowForRestriction_shouldReturnFalse() { - ReflectionHelpers.setStaticField( - Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.TIRAMISU); - when(mBundle.getBoolean(UserManager.DISALLOW_WIFI_DIRECT)).thenReturn(true); + public void isWifiDirectAllowed_hasDisallowRestriction_shouldReturnFalse() { + when(mUserManager.hasUserRestriction(DISALLOW_WIFI_DIRECT)).thenReturn(true); assertThat(WifiEnterpriseRestrictionUtils.isWifiDirectAllowed(mContext)).isFalse(); } @Test - public void isWifiDirectAllowed_setSDKForTAndAllowForRestriction_shouldReturnTrue() { - ReflectionHelpers.setStaticField( - Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.TIRAMISU); - when(mBundle.getBoolean(UserManager.DISALLOW_WIFI_DIRECT)).thenReturn(false); + public void isWifiDirectAllowed_noDisallowRestriction_shouldReturnTrue() { + when(mUserManager.hasUserRestriction(DISALLOW_WIFI_DIRECT)).thenReturn(false); assertThat(WifiEnterpriseRestrictionUtils.isWifiDirectAllowed(mContext)).isTrue(); } @Test - public void isAddWifiConfigAllowed_setSDKForS_shouldReturnTrue() { - ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.S); - when(mBundle.getBoolean(UserManager.DISALLOW_ADD_WIFI_CONFIG)).thenReturn(true); - - assertThat(WifiEnterpriseRestrictionUtils.isAddWifiConfigAllowed(mContext)).isTrue(); - } - - @Test - public void isAddWifiConfigAllowed_setSDKForTAndDisallowForRestriction_shouldReturnFalse() { - ReflectionHelpers.setStaticField( - Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.TIRAMISU); - when(mBundle.getBoolean(UserManager.DISALLOW_ADD_WIFI_CONFIG)).thenReturn(true); + public void isAddWifiConfigAllowed_hasDisallowRestriction_shouldReturnFalse() { + when(mUserManager.hasUserRestriction(DISALLOW_ADD_WIFI_CONFIG)).thenReturn(true); assertThat(WifiEnterpriseRestrictionUtils.isAddWifiConfigAllowed(mContext)).isFalse(); } @Test - public void isAddWifiConfigAllowed_setSDKForTAndAllowForRestriction_shouldReturnTrue() { - ReflectionHelpers.setStaticField( - Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.TIRAMISU); - when(mBundle.getBoolean(UserManager.DISALLOW_ADD_WIFI_CONFIG)).thenReturn(false); + public void isAddWifiConfigAllowed_noDisallowRestriction_shouldReturnTrue() { + when(mUserManager.hasUserRestriction(DISALLOW_ADD_WIFI_CONFIG)).thenReturn(false); assertThat(WifiEnterpriseRestrictionUtils.isAddWifiConfigAllowed(mContext)).isTrue(); } @@ -143,7 +112,7 @@ public class WifiEnterpriseRestrictionUtilsTest { } @Test - public void isChangeWifiStateAllowed_hasNoDisallowRestriction_shouldReturnTrue() { + public void isChangeWifiStateAllowed_noDisallowRestriction_shouldReturnTrue() { when(mUserManager.hasUserRestriction(DISALLOW_CHANGE_WIFI_STATE)).thenReturn(false); assertThat(WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(mContext)).isTrue(); @@ -151,7 +120,7 @@ public class WifiEnterpriseRestrictionUtilsTest { @Test public void hasUserRestrictionFromT_setSDKForS_shouldReturnTrue() { - ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.S); + ReflectionHelpers.setStaticField(Build.VERSION.class, SDK_INT, VERSION_CODES_S); assertThat(WifiEnterpriseRestrictionUtils.hasUserRestrictionFromT(mContext, "key")) .isFalse(); @@ -159,8 +128,7 @@ public class WifiEnterpriseRestrictionUtilsTest { @Test public void hasUserRestrictionFromT_setSDKForT_shouldReturnHasUserRestriction() { - ReflectionHelpers.setStaticField( - Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.TIRAMISU); + ReflectionHelpers.setStaticField(Build.VERSION.class, SDK_INT, VERSION_CODES_T); when(mUserManager.hasUserRestriction(anyString())).thenReturn(false); assertThat(WifiEnterpriseRestrictionUtils.hasUserRestrictionFromT(mContext, "key")) diff --git a/packages/SystemUI/docs/usb_audio.md b/packages/SystemUI/docs/usb_audio.md new file mode 100644 index 000000000000..66e2df944fbc --- /dev/null +++ b/packages/SystemUI/docs/usb_audio.md @@ -0,0 +1,30 @@ +# USB audio Permission and Confirmation warning dialog resource string id matrix table +### go/support-usb-access-aoc-offload-feature + + |---|------------|----------------|------------------|-----------------|--------------------| + | # | Permission |isUsbAudioDevice| hasAudioPlayback | hasAudioCapture | string resource ID | + |---|------------|----------------|------------------|-----------------|--------------------| + | 1 | TRUE | TRUE | TRUE | FALSE | usb_audio_device_ + permission_prompt | + |---|------------|----------------|------------------|-----------------|--------------------| + | 2 | TRUE | TRUE | FALSE | TRUE | usb_audio_device_ + permission_prompt | + |---|------------|----------------|------------------|-----------------|--------------------| + | 3 | TRUE | TRUE | TRUE | TRUE | usb_audio_device_ + permission_prompt | + |---|------------|----------------|------------------|-----------------|--------------------| + | 4 | TRUE | FALSE | N/A | N/A | usb_device_ + permission_prompt | + |---|------------|----------------|------------------|-----------------|--------------------| + | 5 | FALSE | TRUE | TRUE | FALSE | usb_audio_device_ + permission_prompt | + |---|------------|----------------|------------------|-----------------|--------------------| + | 6 | FALSE | TRUE | FALSE | TRUE | usb_audio_device_ + permission_prompt_warn + |---|------------|----------------|------------------|-----------------|--------------------| + | 7 | FALSE | TRUE | TRUE | TRUE | usb_audio_device_ + permission_prompt_warn + |---|------------|----------------|------------------|-----------------|--------------------| + | 8 | FALSE | FALSE | N/A | N/A | usb_device_ + permission_prompt | + |---|------------|----------------|------------------|-----------------|--------------------| diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml index f72a8dc08c9c..5b961595fbd3 100644 --- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml +++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml @@ -198,20 +198,29 @@ android:clickable="false" android:focusable="false"> - <FrameLayout + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="match_parent" android:layout_weight="1" + android:gravity="start|center_vertical" android:orientation="vertical" - android:clickable="false" - android:layout_width="wrap_content" - android:layout_height="match_parent"> + android:clickable="false"> <TextView android:id="@+id/wifi_toggle_title" android:text="@string/turn_on_wifi" android:layout_width="wrap_content" - android:layout_height="match_parent" + android:layout_height="wrap_content" android:gravity="start|center_vertical" android:textAppearance="@style/TextAppearance.InternetDialog"/> - </FrameLayout> + <TextView + android:id="@+id/wifi_toggle_summary" + android:text="@string/wifitrackerlib_admin_restricted_network" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="start|center_vertical" + android:textAppearance="@style/TextAppearance.InternetDialog.Secondary" + android:visibility="gone"/> + </LinearLayout> <FrameLayout android:layout_width="@dimen/settingslib_switch_track_width" diff --git a/packages/SystemUI/res/layout/media_output_list_item.xml b/packages/SystemUI/res/layout/media_output_list_item.xml index d39b0d53c743..b85ea598dbb4 100644 --- a/packages/SystemUI/res/layout/media_output_list_item.xml +++ b/packages/SystemUI/res/layout/media_output_list_item.xml @@ -33,7 +33,7 @@ android:layout_height="match_parent" android:background="@drawable/media_output_item_background" android:layout_gravity="center_vertical|start"> - <com.android.systemui.media.dialog.MediaOutputSeekbar + <SeekBar android:id="@+id/volume_seekbar" android:splitTrack="false" android:visibility="gone" @@ -119,15 +119,24 @@ android:importantForAccessibility="no" android:visibility="gone"/> + <LinearLayout + android:id="@+id/end_action_area" + android:visibility="gone" + android:orientation="vertical" + android:layout_width="48dp" + android:layout_height="64dp" + android:layout_gravity="right|center" + android:gravity="center_vertical"> <CheckBox android:id="@+id/check_box" android:layout_width="24dp" android:layout_height="24dp" android:layout_marginEnd="16dp" - android:layout_gravity="right|center" + android:layout_gravity="right" android:button="@drawable/ic_circle_check_box" android:visibility="gone" - android:clickable="false" /> + + </LinearLayout> </FrameLayout> </LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml index 3c3bc63906f0..6611c59651fc 100644 --- a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml +++ b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml @@ -42,10 +42,10 @@ android:id="@+id/media_cover1_container" style="@style/MediaPlayer.Recommendation.AlbumContainer" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@+id/media_title1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/media_cover2_container" - android:layout_marginEnd="@dimen/qs_media_rec_album_margin" + android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin" app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintHorizontal_bias="1.0" app:layout_constraintVertical_bias="0.5" @@ -63,16 +63,33 @@ android:scaleType="centerCrop"/> </FrameLayout> - <!-- TODO(b/223603970): Add title and subtitle below each album cover. --> + <TextView + android:id="@+id/media_title1" + style="@style/MediaPlayer.Recommendation.Text.Title" + app:layout_constraintStart_toStartOf="@+id/media_cover1_container" + app:layout_constraintEnd_toEndOf="@+id/media_cover1_container" + app:layout_constraintTop_toBottomOf="@+id/media_cover1_container" + app:layout_constraintBottom_toTopOf="@+id/media_subtitle1" + /> + + <TextView + android:id="@+id/media_subtitle1" + style="@style/MediaPlayer.Recommendation.Text.Subtitle" + app:layout_constraintStart_toStartOf="@+id/media_cover1_container" + app:layout_constraintEnd_toEndOf="@+id/media_cover1_container" + app:layout_constraintTop_toBottomOf="@+id/media_title1" + app:layout_constraintBottom_toBottomOf="parent" + android:layout_marginBottom="@dimen/qs_media_padding" + /> <FrameLayout android:id="@+id/media_cover2_container" style="@style/MediaPlayer.Recommendation.AlbumContainer" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@id/media_title2" app:layout_constraintStart_toEndOf="@id/media_cover1_container" app:layout_constraintEnd_toStartOf="@id/media_cover3_container" - android:layout_marginEnd="@dimen/qs_media_rec_album_margin" + android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin" app:layout_constraintVertical_bias="0.5" > <ImageView @@ -86,11 +103,30 @@ android:scaleType="centerCrop"/> </FrameLayout> + <TextView + android:id="@+id/media_title2" + style="@style/MediaPlayer.Recommendation.Text.Title" + app:layout_constraintStart_toStartOf="@+id/media_cover2_container" + app:layout_constraintEnd_toEndOf="@+id/media_cover2_container" + app:layout_constraintTop_toBottomOf="@+id/media_cover2_container" + app:layout_constraintBottom_toTopOf="@+id/media_subtitle2" + /> + + <TextView + android:id="@+id/media_subtitle2" + style="@style/MediaPlayer.Recommendation.Text.Subtitle" + app:layout_constraintStart_toStartOf="@+id/media_cover2_container" + app:layout_constraintEnd_toEndOf="@+id/media_cover2_container" + app:layout_constraintTop_toBottomOf="@+id/media_title2" + app:layout_constraintBottom_toBottomOf="parent" + android:layout_marginBottom="@dimen/qs_media_padding" + /> + <FrameLayout android:id="@+id/media_cover3_container" style="@style/MediaPlayer.Recommendation.AlbumContainer" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@id/media_title3" app:layout_constraintStart_toEndOf="@id/media_cover2_container" app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="@dimen/qs_media_padding" @@ -107,6 +143,25 @@ android:scaleType="centerCrop"/> </FrameLayout> + <TextView + android:id="@+id/media_title3" + style="@style/MediaPlayer.Recommendation.Text.Title" + app:layout_constraintStart_toStartOf="@+id/media_cover3_container" + app:layout_constraintEnd_toEndOf="@+id/media_cover3_container" + app:layout_constraintTop_toBottomOf="@+id/media_cover3_container" + app:layout_constraintBottom_toTopOf="@+id/media_subtitle3" + /> + + <TextView + android:id="@+id/media_subtitle3" + style="@style/MediaPlayer.Recommendation.Text.Subtitle" + app:layout_constraintStart_toStartOf="@+id/media_cover3_container" + app:layout_constraintEnd_toEndOf="@+id/media_cover3_container" + app:layout_constraintTop_toBottomOf="@+id/media_title3" + app:layout_constraintBottom_toBottomOf="parent" + android:layout_marginBottom="@dimen/qs_media_padding" + /> + <!-- Long press menu --> <TextView android:layout_width="match_parent" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 0bf6e2805f0c..a0115e8865fc 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -561,6 +561,9 @@ <!-- The height of the gap between adjacent notification sections. --> <dimen name="notification_section_divider_height">@dimen/notification_side_paddings</dimen> + <!-- The height of the gap between adjacent notification sections on lockscreen. --> + <dimen name="notification_section_divider_height_lockscreen">4dp</dimen> + <!-- Size of the face pile shown on one-line (children of a group) conversation notifications --> <dimen name="conversation_single_line_face_pile_size">24dp</dimen> @@ -980,7 +983,8 @@ <!-- Size of Smartspace media recommendations cards in the QSPanel carousel --> <dimen name="qs_media_rec_album_size">88dp</dimen> - <dimen name="qs_media_rec_album_margin">16dp</dimen> + <dimen name="qs_media_rec_album_side_margin">16dp</dimen> + <dimen name="qs_media_rec_album_bottom_margin">8dp</dimen> <dimen name="qs_media_rec_icon_size">24dp</dimen> <!-- Media tap-to-transfer chip for sender device --> @@ -1364,7 +1368,7 @@ <dimen name="keyguard_unfold_translation_x">16dp</dimen> - <dimen name="fgs_manager_min_width_minor">100%</dimen> + <dimen name="fgs_manager_list_top_spacing">12dp</dimen> <!-- Dream overlay related dimensions --> <dimen name="dream_overlay_status_bar_height">60dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 0f5115b5c0f5..f7acda7ec379 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2440,14 +2440,16 @@ <!-- Label for the entry point to open the dialog which shows currently running applications [CHAR LIMIT=NONE]--> <plurals name="fgs_manager_footer_label"> - <item quantity="one"><xliff:g id="count" example="1">%s</xliff:g> active app</item> - <item quantity="other"><xliff:g id="count" example="2">%s</xliff:g> active apps</item> + <item quantity="one"><xliff:g id="count" example="1">%s</xliff:g> app is active</item> + <item quantity="other"><xliff:g id="count" example="2">%s</xliff:g> apps are active</item> </plurals> <!-- Content description for a dot indicator in the running application indicating that there is new information [CHAR LIMIT=NONE] --> <string name="fgs_dot_content_description">New information</string> <!-- Title for dialog listing applications currently running [CHAR LIMIT=NONE]--> <string name="fgs_manager_dialog_title">Active apps</string> + <!-- Detailed message for dialog listing applications currently running [CHAR LIMIT=NONE]--> + <string name="fgs_manager_dialog_message">Even if you\u2019re not using these apps, they\u2019re still active and might affect battery life</string> <!-- Label of the button to stop an app from running [CHAR LIMIT=12]--> <string name="fgs_manager_app_item_stop_button_label">Stop</string> <!-- Label of the button to stop an app from running but the app is already stopped and the button is disabled [CHAR LIMIT=12]--> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 5c658947c3b0..827631b7eb53 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -672,13 +672,31 @@ <item name="android:layout_height">@dimen/qs_media_rec_album_size</item> <item name="android:background">@drawable/qs_media_light_source</item> <item name="android:layout_marginTop">@dimen/qs_media_padding</item> - <item name="android:layout_marginBottom">@dimen/qs_media_padding</item> + <item name="android:layout_marginBottom">@dimen/qs_media_rec_album_bottom_margin</item> </style> <style name="MediaPlayer.Recommendation.Album"> <item name="android:backgroundTint">@color/media_player_album_bg</item> </style> + <style name="MediaPlayer.Recommendation.Text"> + <item name="android:layout_width">@dimen/qs_media_rec_album_size</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:maxLines">1</item> + <item name="android:ellipsize">end</item> + <item name="android:textSize">14sp</item> + <item name="android:gravity">start</item> + </style> + + <style name="MediaPlayer.Recommendation.Text.Title"> + <item name="android:textColor">?android:attr/textColorPrimary</item> + </style> + + <style name="MediaPlayer.Recommendation.Text.Subtitle"> + <item name="android:textColor">?android:attr/textColorSecondary</item> + </style> + + <!-- Used to style charging animation AVD animation --> <style name="ChargingAnim" /> @@ -1101,6 +1119,7 @@ <style name="FgsManagerAppLabel" parent="TextAppearance.Dialog.Body"> <item name="android:textDirection">locale</item> + <item name="android:textStyle">bold</item> </style> <style name="FgsManagerAppDuration"> diff --git a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml b/packages/SystemUI/res/xml/media_recommendation_collapsed.xml index c9d76870ebbd..a6113473b065 100644 --- a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml +++ b/packages/SystemUI/res/xml/media_recommendation_collapsed.xml @@ -15,7 +15,8 @@ ~ limitations under the License --> <ConstraintSet - xmlns:android="http://schemas.android.com/apk/res/android" > + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" > <Constraint android:id="@+id/sizing_view" @@ -23,4 +24,76 @@ android:layout_height="@dimen/qs_media_session_height_collapsed" /> + <!-- Only the constraintBottom and marginBottom are different. The rest of the constraints are + the same as the constraints in media_smartspace_recommendations. But due to how + ConstraintSets work, all the constraints need to be in the same place. + Ditto for the other cover containers. --> + <Constraint + android:id="@+id/media_cover1_container" + app:layout_constraintBottom_toBottomOf="parent" + android:layout_marginBottom="@dimen/qs_media_padding" + style="@style/MediaPlayer.Recommendation.AlbumContainer" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toStartOf="@id/media_cover2_container" + android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin" + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintHorizontal_bias="1.0" + app:layout_constraintVertical_bias="0.5" + /> + + <Constraint + android:id="@+id/media_title1" + android:visibility="gone" + /> + + <Constraint + android:id="@+id/media_subtitle1" + android:visibility="gone" + /> + + <Constraint + android:id="@+id/media_cover2_container" + app:layout_constraintBottom_toBottomOf="parent" + android:layout_marginBottom="@dimen/qs_media_padding" + style="@style/MediaPlayer.Recommendation.AlbumContainer" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toEndOf="@id/media_cover1_container" + app:layout_constraintEnd_toStartOf="@id/media_cover3_container" + android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin" + app:layout_constraintVertical_bias="0.5" + /> + + <Constraint + android:id="@+id/media_title2" + android:visibility="gone" + /> + + <Constraint + android:id="@+id/media_subtitle2" + android:visibility="gone" + /> + + <Constraint + android:id="@+id/media_cover3_container" + app:layout_constraintBottom_toBottomOf="parent" + android:layout_marginBottom="@dimen/qs_media_padding" + style="@style/MediaPlayer.Recommendation.AlbumContainer" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toEndOf="@id/media_cover2_container" + app:layout_constraintEnd_toEndOf="parent" + android:layout_marginEnd="@dimen/qs_media_padding" + app:layout_constraintVertical_bias="0.5" + /> + + <Constraint + android:id="@+id/media_title3" + android:visibility="gone" + /> + + <Constraint + android:id="@+id/media_subtitle3" + android:visibility="gone" + /> + </ConstraintSet> diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt index ed973d6a6854..83b72e84a169 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt @@ -57,6 +57,15 @@ constructor( private val foldStateListener = FoldStateListener(context) private val timeoutRunnable = TimeoutRunnable() + /** + * Time after which [FOLD_UPDATE_FINISH_HALF_OPEN] is emitted following a + * [FOLD_UPDATE_START_CLOSING] or [FOLD_UPDATE_START_OPENING] event, if an end state is not + * reached. + */ + private val halfOpenedTimeoutMillis: Int = + context.resources.getInteger( + com.android.internal.R.integer.config_unfoldTransitionHalfFoldedTimeout) + private var isFolded = false private var isUnfoldHandled = true @@ -171,7 +180,7 @@ constructor( if (isTransitionInProgess) { cancelTimeout() } - handler.postDelayed(timeoutRunnable, HALF_OPENED_TIMEOUT_MILLIS) + handler.postDelayed(timeoutRunnable, halfOpenedTimeoutMillis.toLong()) } private fun cancelTimeout() { @@ -222,12 +231,6 @@ private fun stateToString(@FoldUpdate update: Int): String { private const val TAG = "DeviceFoldProvider" private const val DEBUG = false -/** - * Time after which [FOLD_UPDATE_FINISH_HALF_OPEN] is emitted following a - * [FOLD_UPDATE_START_CLOSING] or [FOLD_UPDATE_START_OPENING] event, if an end state is not reached. - */ -@VisibleForTesting const val HALF_OPENED_TIMEOUT_MILLIS = 600L - /** Threshold after which we consider the device fully unfolded. */ @VisibleForTesting const val FULLY_OPEN_THRESHOLD_DEGREES = 15f diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt index 5953611b454a..db2b4ac2c669 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt @@ -63,7 +63,8 @@ data class KeyguardFaceListenModel( val primaryUser: Boolean, val scanningAllowedByStrongAuth: Boolean, val secureCameraLaunched: Boolean, - val switchingUser: Boolean + val switchingUser: Boolean, + val udfpsBouncerShowing: Boolean ) : KeyguardListenModel() /** * Verbose debug information associated with [KeyguardUpdateMonitor.shouldTriggerActiveUnlock]. @@ -73,6 +74,7 @@ data class KeyguardActiveUnlockModel( override val userId: Int, override val listening: Boolean, // keep sorted + val awakeKeyguard: Boolean, val authInterruptActive: Boolean, val encryptedOrTimedOut: Boolean, val fpLockout: Boolean, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 3103219d8978..53d7b8cee3be 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -700,6 +700,7 @@ public class KeyguardSecurityContainer extends FrameLayout { } public void reset() { + mViewMode.reset(); mDisappearAnimRunning = false; } @@ -798,9 +799,6 @@ public class KeyguardSecurityContainer extends FrameLayout { mUserSwitcherViewGroup = mView.findViewById(R.id.keyguard_bouncer_user_switcher); } - Drawable userIcon = findUserIcon(KeyguardUpdateMonitor.getCurrentUser()); - ((ImageView) mView.findViewById(R.id.user_icon)).setImageDrawable(userIcon); - updateSecurityViewLocation(); mUserSwitcher = mView.findViewById(R.id.user_switcher_header); @@ -813,6 +811,7 @@ public class KeyguardSecurityContainer extends FrameLayout { mPopup.dismiss(); mPopup = null; } + setupUserSwitcher(); } private Drawable findUserIcon(int userId) { @@ -858,6 +857,12 @@ public class KeyguardSecurityContainer extends FrameLayout { private void setupUserSwitcher() { final UserRecord currentUser = mUserSwitcherController.getCurrentUserRecord(); + if (currentUser == null) { + Log.wtf(TAG, "Current user in user switcher is null."); + return; + } + Drawable userIcon = findUserIcon(currentUser.info.id); + ((ImageView) mView.findViewById(R.id.user_icon)).setImageDrawable(userIcon); mUserSwitcher.setText(mUserSwitcherController.getCurrentUserName()); ViewGroup anchor = mView.findViewById(R.id.user_switcher_anchor); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 1a325d3586f4..ce4aad882df9 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -219,6 +219,11 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mKeyguardSecurityCallback.userActivity(); showMessage(null, null); } + if (mUpdateMonitor.isFaceEnrolled() + && mUpdateMonitor.mRequestActiveUnlockOnUnlockIntent) { + mUpdateMonitor.requestActiveUnlock("unlock-intent, reason=swipeUpOnBouncer", + true); + } } }; private ConfigurationController.ConfigurationListener mConfigurationListener = diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 2a6247cca3a8..a6feedb50a12 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -56,6 +56,7 @@ import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.hardware.SensorPrivacyManager; +import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricSourceType; @@ -88,6 +89,7 @@ import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -246,6 +248,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + public final boolean mRequestActiveUnlockOnAssistant; + public final boolean mRequestActiveUnlockOnWakeup; + public final boolean mInitiateActiveUnlockOnWakeup; + public final boolean mRequestActiveUnlockOnUnlockIntent; + public final boolean mRequestActiveUnlockOnBioFail; + private final Context mContext; private final boolean mIsPrimaryUser; private final boolean mIsAutomotive; @@ -281,6 +289,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean mGoingToSleep; private boolean mBouncerFullyShown; private boolean mBouncerIsOrWillBeShowing; + private boolean mUdfpsBouncerShowing; private boolean mAuthInterruptActive; private boolean mNeedsSlowUnlockTransition; private boolean mAssistantVisible; @@ -451,10 +460,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - if (KeyguardUpdateMonitor.getCurrentUser() == userId && getUserHasTrust(userId)) { + if (KeyguardUpdateMonitor.getCurrentUser() == userId) { CharSequence message = null; - if (trustGrantedMessages != null && trustGrantedMessages.size() > 0) { - message = trustGrantedMessages.get(0); // for now only shows the first in the list + final boolean userHasTrust = getUserHasTrust(userId); + if (userHasTrust && trustGrantedMessages != null) { + for (String msg : trustGrantedMessages) { + if (!TextUtils.isEmpty(msg)) { + message = msg; + break; + } + } } for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); @@ -463,6 +478,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } } + } @Override @@ -1354,8 +1370,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab void setAssistantVisible(boolean assistantVisible) { mAssistantVisible = assistantVisible; updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); - if (mAssistantVisible) { - requestActiveUnlock(); + if (mAssistantVisible && mRequestActiveUnlockOnAssistant) { + requestActiveUnlock("assistant", false); } } @@ -1502,11 +1518,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onAuthenticationFailed() { + if (mRequestActiveUnlockOnBioFail) { + requestActiveUnlock("biometric-failure, extra=fingerprintFailure", + true); + } handleFingerprintAuthFailed(); - - // TODO(b/225231929): Refactor as needed, add tests, etc. - mTrustManager.reportUserRequestedUnlock( - KeyguardUpdateMonitor.getCurrentUser(), true); } @Override @@ -1564,6 +1580,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onAuthenticationFailed() { + if (shouldRequestActiveUnlockOnFaceError()) { + String reason = + mKeyguardBypassController.canBypass() ? "bypass" + : mUdfpsBouncerShowing ? "udfpsBouncer" : + mBouncerFullyShown ? "bouncer" : "udfpsFpDown"; + requestActiveUnlock("biometric-failure" + + ", extra=faceFailure-" + reason, true); + } + handleFaceAuthFailed(); if (mKeyguardBypassController != null) { mKeyguardBypassController.setUserHasDeviceEntryIntent(false); @@ -1592,12 +1617,23 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (mKeyguardBypassController != null) { mKeyguardBypassController.setUserHasDeviceEntryIntent(false); } + if (errMsgId == BiometricFaceConstants.FACE_ERROR_TIMEOUT + && shouldRequestActiveUnlockOnFaceError()) { + requestActiveUnlock("biometric-failure" + + ", extra=faceError-" + errMsgId, true); + } } @Override public void onAuthenticationAcquired(int acquireInfo) { handleFaceAcquired(acquireInfo); } + + private boolean shouldRequestActiveUnlockOnFaceError() { + return mRequestActiveUnlockOnBioFail + && (mKeyguardBypassController.canBypass() || mBouncerFullyShown + || mUdfpsBouncerShowing || mAuthController.isUdfpsFingerDown()); + } }; @VisibleForTesting @@ -1713,7 +1749,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp"); Assert.isMainThread(); updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); - requestActiveUnlock(); + if (mRequestActiveUnlockOnWakeup) { + requestActiveUnlock("wake-unlock"); + } else if (mInitiateActiveUnlockOnWakeup) { + initiateActiveUnlock("wake-initiate"); + } for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -1860,6 +1900,18 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab dumpManager.registerDumpable(getClass().getName(), this); mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); + // TODO, b/222459888: add official configurable names to Settings.java + mRequestActiveUnlockOnWakeup = Settings.Global.getInt( + mContext.getContentResolver(), "wake-unlock", 0) == 1; + mInitiateActiveUnlockOnWakeup = Settings.Global.getInt( + mContext.getContentResolver(), "wake-initiate", 1) == 1; + mRequestActiveUnlockOnUnlockIntent = Settings.Global.getInt( + mContext.getContentResolver(), "unlock-intent", 0) == 1; + mRequestActiveUnlockOnBioFail = Settings.Global.getInt( + mContext.getContentResolver(), "bio-fail", 0) == 1; + mRequestActiveUnlockOnAssistant = Settings.Global.getInt( + mContext.getContentResolver(), "assistant", 0) == 1; + mHandler = new Handler(mainLooper) { @Override public void handleMessage(Message msg) { @@ -2240,7 +2292,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } mAuthInterruptActive = active; updateFaceListeningState(BIOMETRIC_ACTION_UPDATE); - requestActiveUnlock(); + if (mRequestActiveUnlockOnWakeup) { + requestActiveUnlock("wake-unlock, extra=onReach"); + } else if (mInitiateActiveUnlockOnWakeup) { + initiateActiveUnlock("wake-initiate, extra=onReach"); + } } /** @@ -2290,25 +2346,69 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** - * Attempts to trigger active unlock. + * Initiates active unlock to get the unlock token ready. */ - public void requestActiveUnlock() { + public void initiateActiveUnlock(String reason) { // If this message exists, FP has already authenticated, so wait until that is handled if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) { return; } if (shouldTriggerActiveUnlock()) { - // TODO(b/225231929): Refactor surrounding code to reflect calling of new method + if (DEBUG) { + Log.d("ActiveUnlock", "initiate active unlock triggerReason=" + reason); + } mTrustManager.reportUserMayRequestUnlock(KeyguardUpdateMonitor.getCurrentUser()); } } + /** + * Attempts to trigger active unlock from trust agent. + */ + public void requestActiveUnlock(String reason, boolean dismissKeyguard) { + // If this message exists, FP has already authenticated, so wait until that is handled + if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) { + return; + } + + if (shouldTriggerActiveUnlock()) { + if (DEBUG) { + Log.d("ActiveUnlock", "reportUserRequestedUnlock triggerReason=" + reason + + " dismissKeyguard=" + dismissKeyguard); + } + mTrustManager.reportUserRequestedUnlock(KeyguardUpdateMonitor.getCurrentUser(), + dismissKeyguard); + } + } + + /** + * Attempts to trigger active unlock from trust agent. + * Only dismisses the keyguard if only face is enrolled (no FP) and bypass is enabled. + */ + public void requestActiveUnlock(String reason) { + requestActiveUnlock(reason, isFaceEnrolled() && !isUdfpsEnrolled() + && mKeyguardBypassController.getBypassEnabled()); + } + + /** + * Whether the UDFPS bouncer is showing. + */ + public void setUdfpsBouncerShowing(boolean showing) { + mUdfpsBouncerShowing = showing; + if (mUdfpsBouncerShowing) { + updateFaceListeningState(BIOMETRIC_ACTION_START); + if (mRequestActiveUnlockOnUnlockIntent) { + requestActiveUnlock("unlock-intent, extra=udfpsBouncer", true); + } + } + } + private boolean shouldTriggerActiveUnlock() { // Triggers: final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant(); - final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep - && mStatusBarState != StatusBarState.SHADE_LOCKED; + final boolean awakeKeyguard = mBouncerFullyShown || mUdfpsBouncerShowing + || (mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep + && mStatusBarState != StatusBarState.SHADE_LOCKED); // Gates: final int user = getCurrentUser(); @@ -2341,20 +2441,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && !mSecureCameraLaunched; // Aggregate relevant fields for debug logging. - if (DEBUG_ACTIVE_UNLOCK || DEBUG_SPEW) { - maybeLogListenerModelData( - new KeyguardActiveUnlockModel( - System.currentTimeMillis(), - user, - shouldTriggerActiveUnlock, - mAuthInterruptActive, - isEncryptedOrTimedOut, - fpLockedout, - isLockDown, - mSwitchingUser, - triggerActiveUnlockForAssistant, - userCanDismissLockScreen)); - } + maybeLogListenerModelData( + new KeyguardActiveUnlockModel( + System.currentTimeMillis(), + user, + shouldTriggerActiveUnlock, + awakeKeyguard, + mAuthInterruptActive, + isEncryptedOrTimedOut, + fpLockedout, + isLockDown, + mSwitchingUser, + triggerActiveUnlockForAssistant, + userCanDismissLockScreen)); return shouldTriggerActiveUnlock; } @@ -2507,7 +2606,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab || mOccludingAppRequestingFace || awakeKeyguard || shouldListenForFaceAssistant - || mAuthController.isUdfpsFingerDown()) + || mAuthController.isUdfpsFingerDown() + || mUdfpsBouncerShowing) && !mSwitchingUser && !faceDisabledForUser && becauseCannotSkipBouncer && !mKeyguardGoingAway && biometricEnabledForUser && !mLockIconPressed && strongAuthAllowsScanning && mIsPrimaryUser @@ -2537,7 +2637,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mIsPrimaryUser, strongAuthAllowsScanning, mSecureCameraLaunched, - mSwitchingUser)); + mSwitchingUser, + mUdfpsBouncerShowing)); } return shouldListen; @@ -2550,8 +2651,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } if (DEBUG_ACTIVE_UNLOCK - && model instanceof KeyguardActiveUnlockModel - && model.getListening()) { + && model instanceof KeyguardActiveUnlockModel) { mListenModels.add(model); return; } @@ -3133,6 +3233,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } if (wasBouncerFullyShown != mBouncerFullyShown) { + if (mBouncerFullyShown && mRequestActiveUnlockOnUnlockIntent) { + requestActiveUnlock("unlock-intent, reason=bouncerFullyShown", true); + } for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -3641,6 +3744,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab pw.println(" shouldListenForUdfps=" + shouldListenForFingerprint(true)); pw.println(" mBouncerIsOrWillBeShowing=" + mBouncerIsOrWillBeShowing); pw.println(" mStatusBarState=" + StatusBarState.toString(mStatusBarState)); + pw.println(" mUdfpsBouncerShowing=" + mUdfpsBouncerShowing); } } if (mFaceManager != null && mFaceManager.isHardwareDetected()) { @@ -3667,6 +3771,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } mListenModels.print(pw); + pw.println("Enabled active unlock triggers:"); + pw.println(" mRequestActiveUnlockOnWakeup=" + mRequestActiveUnlockOnWakeup); + pw.println(" mInitiateActiveUnlockOnWakeup=" + mInitiateActiveUnlockOnWakeup); + pw.println(" mRequestActiveUnlockOnUnlockIntent=" + mRequestActiveUnlockOnUnlockIntent); + pw.println(" mRequestActiveUnlockOnBiometricFail=" + mRequestActiveUnlockOnBioFail); + pw.println(" mRequestActiveUnlockOnAssistant=" + mRequestActiveUnlockOnAssistant); + if (mIsAutomotive) { pw.println(" Running on Automotive build"); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java index 5e48ee3d2366..0cc1b2d92e00 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java @@ -28,6 +28,8 @@ import android.view.Display; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.dagger.SysUISingleton; +import javax.inject.Inject; + /** * A class to control {@link MagnificationModeSwitch}. It shows the button UI with following * conditions: @@ -44,6 +46,7 @@ public class ModeSwitchesController implements SwitchListener { private final DisplayIdIndexSupplier<MagnificationModeSwitch> mSwitchSupplier; private SwitchListener mSwitchListenerDelegate; + @Inject public ModeSwitchesController(Context context) { mSwitchSupplier = new SwitchSupplier(context, context.getSystemService(DisplayManager.class), this::onSwitch); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AlternateUdfpsTouchProvider.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AlternateUdfpsTouchProvider.kt new file mode 100644 index 000000000000..f4f39a1df11b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AlternateUdfpsTouchProvider.kt @@ -0,0 +1,42 @@ +/* + * 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.biometrics + +/** + * Interface for controlling the on finger down & on finger up events. + */ +interface AlternateUdfpsTouchProvider { + + /** + * This operation is used to notify the Fingerprint HAL that + * a fingerprint has been detected on the device's screen. + * + * See fingerprint/ISession#onPointerDown for more details. + */ + fun onPointerDown(pointerId: Long, x: Int, y: Int, minor: Float, major: Float) + + /** + * onPointerUp: + * + * This operation can be invoked when the HAL is performing any one of: ISession#authenticate, + * ISession#enroll, ISession#detectInteraction. This operation is used to indicate + * that a fingerprint that was previously down, is now up. + * + * See fingerprint/ISession#onPointerUp for more details. + */ + fun onPointerUp(pointerId: Long) +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 2ac240885faa..284724687abc 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -133,6 +133,7 @@ public class UdfpsController implements DozeReceiver { // sensors, this, in addition to a lot of the code here, will be updated. @VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps; @NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator; + @Nullable private final AlternateUdfpsTouchProvider mAlternateTouchProvider; // Tracks the velocity of a touch to help filter out the touches that move too fast. @Nullable private VelocityTracker mVelocityTracker; @@ -384,6 +385,7 @@ public class UdfpsController implements DozeReceiver { mActivePointerId = event.getPointerId(0); mVelocityTracker.addMovement(event); handled = true; + mAcquiredReceived = false; } if ((withinSensorArea || fromUdfpsView) && shouldTryToDismissKeyguard()) { Log.v(TAG, "onTouch | dismiss keyguard ACTION_DOWN"); @@ -537,7 +539,8 @@ public class UdfpsController implements DozeReceiver { @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, @NonNull SystemUIDialogManager dialogManager, @NonNull LatencyTracker latencyTracker, - @NonNull ActivityLaunchAnimator activityLaunchAnimator) { + @NonNull ActivityLaunchAnimator activityLaunchAnimator, + @NonNull Optional<AlternateUdfpsTouchProvider> aternateTouchProvider) { mContext = context; mExecution = execution; mVibrator = vibrator; @@ -566,6 +569,7 @@ public class UdfpsController implements DozeReceiver { mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; mLatencyTracker = latencyTracker; mActivityLaunchAnimator = activityLaunchAnimator; + mAlternateTouchProvider = aternateTouchProvider.orElse(null); mSensorProps = findFirstUdfps(); // At least one UDFPS sensor exists @@ -782,6 +786,7 @@ public class UdfpsController implements DozeReceiver { private void onFingerDown(long requestId, int x, int y, float minor, float major) { mExecution.assertIsMainThread(); + if (mOverlay == null) { Log.w(TAG, "Null request in onFingerDown"); return; @@ -799,9 +804,18 @@ public class UdfpsController implements DozeReceiver { if (!mKeyguardUpdateMonitor.isFaceDetectionRunning()) { mKeyguardUpdateMonitor.requestFaceAuth(/* userInitiatedRequest */ false); } + + if (mKeyguardUpdateMonitor.mRequestActiveUnlockOnUnlockIntent) { + mKeyguardUpdateMonitor.requestActiveUnlock("unlock-intent extra=udfpsFingerDown", + true); + } } mOnFingerDown = true; - mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, x, y, minor, major); + if (mAlternateTouchProvider != null) { + mAlternateTouchProvider.onPointerDown(requestId, x, y, minor, major); + } else { + mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, x, y, minor, major); + } Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0); final UdfpsView view = mOverlay.getOverlayView(); @@ -822,7 +836,11 @@ public class UdfpsController implements DozeReceiver { mActivePointerId = -1; mAcquiredReceived = false; if (mOnFingerDown) { - mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId); + if (mAlternateTouchProvider != null) { + mAlternateTouchProvider.onPointerUp(requestId); + } else { + mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId); + } for (Callback cb : mCallbacks) { cb.onFingerUp(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java index 1a726ce4448f..4ec479a3230b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java @@ -21,7 +21,6 @@ import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.content.res.Configuration; -import android.util.Log; import android.util.MathUtils; import android.view.MotionEvent; @@ -83,7 +82,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud * {@link KeyguardBouncer#EXPANSION_HIDDEN} (1f) */ private float mInputBouncerHiddenAmount; - private boolean mIsBouncerVisible; + private boolean mIsGenericBouncerShowing; // whether UDFPS bouncer or input bouncer is visible protected UdfpsKeyguardViewController( @NonNull UdfpsKeyguardView view, @@ -151,8 +150,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud mKeyguardStateController.addCallback(mKeyguardStateControllerCallback); mStatusBarState = getStatusBarStateController().getState(); mQsExpanded = mKeyguardViewManager.isQsExpanded(); - mInputBouncerHiddenAmount = KeyguardBouncer.EXPANSION_HIDDEN; - mIsBouncerVisible = mKeyguardViewManager.bouncerIsOrWillBeShowing(); + updateGenericBouncerVisibility(); mConfigurationController.addCallback(mConfigurationListener); getPanelExpansionStateManager().addExpansionListener(mPanelExpansionListener); updateAlpha(); @@ -187,7 +185,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud pw.println("mFaceDetectRunning=" + mFaceDetectRunning); pw.println("mStatusBarState=" + StatusBarState.toString(mStatusBarState)); pw.println("mQsExpanded=" + mQsExpanded); - pw.println("mIsBouncerVisible=" + mIsBouncerVisible); + pw.println("mIsGenericBouncerShowing=" + mIsGenericBouncerShowing); pw.println("mInputBouncerHiddenAmount=" + mInputBouncerHiddenAmount); pw.println("mPanelExpansionFraction=" + mPanelExpansionFraction); pw.println("unpausedAlpha=" + mView.getUnpausedAlpha()); @@ -225,6 +223,8 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud } else { mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false); } + + updateGenericBouncerVisibility(); updateAlpha(); updatePauseAuth(); return true; @@ -241,7 +241,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud } if (mUdfpsRequested && !getNotificationShadeVisible() - && (!mIsBouncerVisible + && (!mIsGenericBouncerShowing || mInputBouncerHiddenAmount != KeyguardBouncer.EXPANSION_VISIBLE) && mKeyguardStateController.isShowing()) { return false; @@ -267,13 +267,8 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud return true; } - if (mInputBouncerHiddenAmount < .5f || mIsBouncerVisible) { - if (!getStatusBarStateController().isDozing()) { - return true; - } else { - Log.e(TAG, "Bouncer state claims visible on doze hiddenAmount=" - + mInputBouncerHiddenAmount + " bouncerVisible=" + mIsBouncerVisible); - } + if (mInputBouncerHiddenAmount < .5f) { + return true; } return false; @@ -351,6 +346,21 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud mView.setUnpausedAlpha(alpha); } + /** + * Updates mIsGenericBouncerShowing (whether any bouncer is showing) and updates the + * mInputBouncerHiddenAmount to reflect whether the input bouncer is fully showing or not. + */ + private void updateGenericBouncerVisibility() { + mIsGenericBouncerShowing = mKeyguardViewManager.isBouncerShowing(); // includes altBouncer + final boolean altBouncerShowing = mKeyguardViewManager.isShowingAlternateAuth(); + if (altBouncerShowing || !mKeyguardViewManager.bouncerIsOrWillBeShowing()) { + mInputBouncerHiddenAmount = 1f; + } else if (mIsGenericBouncerShowing) { + // input bouncer is fully showing + mInputBouncerHiddenAmount = 0f; + } + } + private final StatusBarStateController.StateListener mStateListener = new StatusBarStateController.StateListener() { @Override @@ -434,14 +444,13 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud updatePauseAuth(); } + /** + * Only called on primary auth bouncer changes, not on whether the UDFPS bouncer + * visibility changes. + */ @Override public void onBouncerVisibilityChanged() { - mIsBouncerVisible = mKeyguardViewManager.isBouncerShowing(); - if (!mIsBouncerVisible) { - mInputBouncerHiddenAmount = 1f; - } else if (mKeyguardViewManager.isBouncerShowing()) { - mInputBouncerHiddenAmount = 0f; - } + updateGenericBouncerVisibility(); updateAlpha(); updatePauseAuth(); } diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt index b7aebc198827..d757b629c829 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt @@ -31,10 +31,13 @@ import android.util.SparseArray import com.android.internal.annotations.VisibleForTesting import com.android.systemui.Dumpable import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dump.DumpManager import com.android.systemui.settings.UserTracker import java.io.PrintWriter import java.util.concurrent.Executor +import javax.inject.Inject data class ReceiverData( val receiver: BroadcastReceiver, @@ -63,14 +66,15 @@ private const val DEBUG = true * Broadcast handling may be asynchronous *without* calling goAsync(), as it's running within sysui * and doesn't need to worry about being killed. */ -open class BroadcastDispatcher @JvmOverloads constructor ( +@SysUISingleton +open class BroadcastDispatcher @Inject constructor( private val context: Context, - private val bgLooper: Looper, - private val bgExecutor: Executor, + @Background private val bgLooper: Looper, + @Background private val bgExecutor: Executor, private val dumpManager: DumpManager, private val logger: BroadcastDispatcherLogger, private val userTracker: UserTracker, - private val removalPendingStore: PendingRemovalStore = PendingRemovalStore(logger) + private val removalPendingStore: PendingRemovalStore ) : Dumpable { // Only modify in BG thread diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherModule.java b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherModule.java new file mode 100644 index 000000000000..cc08188ef618 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherModule.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.broadcast; + +import com.android.systemui.CoreStartable; + +import dagger.Binds; +import dagger.Module; +import dagger.multibindings.ClassKey; +import dagger.multibindings.IntoMap; + +/** */ +@Module +public abstract class BroadcastDispatcherModule { + /** Ensures BroadcastDispatcher is initialized. */ + @Binds + @IntoMap + @ClassKey(BroadcastDispatcherStartable.class) + abstract CoreStartable bindsBroadastDispatcherStartable(BroadcastDispatcherStartable s); +} diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt new file mode 100644 index 000000000000..d7b263a323ca --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt @@ -0,0 +1,31 @@ +/* + * 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.broadcast + +import android.content.Context +import com.android.systemui.CoreStartable +import javax.inject.Inject + +class BroadcastDispatcherStartable @Inject constructor( + context: Context, + val broadcastDispatcher: BroadcastDispatcher +) : CoreStartable(context) { + + override fun start() { + broadcastDispatcher.initialize() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/PendingRemovalStore.kt b/packages/SystemUI/src/com/android/systemui/broadcast/PendingRemovalStore.kt index ebf498361c2a..a5d730fc5009 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/PendingRemovalStore.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/PendingRemovalStore.kt @@ -8,6 +8,7 @@ import com.android.systemui.Dumpable import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger import com.android.systemui.util.indentIfPossible import java.io.PrintWriter +import javax.inject.Inject /** * Store information about requests for unregistering receivers from [BroadcastDispatcher], before @@ -15,7 +16,7 @@ import java.io.PrintWriter * * This helps make unregistering a receiver a *sync* operation. */ -class PendingRemovalStore( +class PendingRemovalStore @Inject constructor( private val logger: BroadcastDispatcherLogger ) : Dumpable { @GuardedBy("pendingRemoval") diff --git a/packages/SystemUI/src/com/android/systemui/dagger/AndroidInternalsModule.java b/packages/SystemUI/src/com/android/systemui/dagger/AndroidInternalsModule.java new file mode 100644 index 000000000000..48c54bca69ee --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dagger/AndroidInternalsModule.java @@ -0,0 +1,53 @@ +/* + * 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.dagger; + +import android.content.Context; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.util.NotificationMessagingUtil; +import com.android.internal.widget.LockPatternUtils; + +import dagger.Module; +import dagger.Provides; + +/** + * Provides items imported from com.android.internal. + */ +@Module +public class AndroidInternalsModule { + /** */ + @Provides + @SysUISingleton + public LockPatternUtils provideLockPatternUtils(Context context) { + return new LockPatternUtils(context); + } + + /** */ + @Provides + @SysUISingleton + public MetricsLogger provideMetricsLogger() { + return new MetricsLogger(); + } + + /** */ + @Provides + public NotificationMessagingUtil provideNotificationMessagingUtil(Context context) { + return new NotificationMessagingUtil(context); + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java index 1653e0adadb5..fb016911f913 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java @@ -29,8 +29,10 @@ import com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity; import com.android.systemui.settings.brightness.BrightnessDialog; import com.android.systemui.statusbar.tv.notifications.TvNotificationPanelActivity; import com.android.systemui.tuner.TunerActivity; +import com.android.systemui.usb.UsbConfirmActivity; import com.android.systemui.usb.UsbDebuggingActivity; import com.android.systemui.usb.UsbDebuggingSecondaryUserActivity; +import com.android.systemui.usb.UsbPermissionActivity; import com.android.systemui.user.CreateUserActivity; import dagger.Binds; @@ -80,6 +82,18 @@ public abstract class DefaultActivityBinder { public abstract Activity bindUsbDebuggingSecondaryUserActivity( UsbDebuggingSecondaryUserActivity activity); + /** Inject into UsbPermissionActivity. */ + @Binds + @IntoMap + @ClassKey(UsbPermissionActivity.class) + public abstract Activity bindUsbPermissionActivity(UsbPermissionActivity activity); + + /** Inject into UsbConfirmActivity. */ + @Binds + @IntoMap + @ClassKey(UsbConfirmActivity.class) + public abstract Activity bindUsbConfirmActivity(UsbConfirmActivity activity); + /** Inject into CreateUserActivity. */ @Binds @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java index fa2384268b15..31ad36fb21d8 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java @@ -16,279 +16,24 @@ package com.android.systemui.dagger; -import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; - -import android.annotation.Nullable; -import android.annotation.SuppressLint; -import android.app.INotificationManager; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.om.OverlayManager; -import android.hardware.display.AmbientDisplayConfiguration; -import android.hardware.display.ColorDisplayManager; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.ServiceManager; -import android.os.UserHandle; -import android.view.Choreographer; -import android.view.IWindowManager; -import android.view.LayoutInflater; - -import com.android.internal.logging.MetricsLogger; -import com.android.internal.util.NotificationMessagingUtil; -import com.android.internal.widget.LockPatternUtils; -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.keyguard.ViewMediatorCallback; -import com.android.settingslib.bluetooth.LocalBluetoothManager; -import com.android.systemui.Prefs; -import com.android.systemui.R; -import com.android.systemui.accessibility.AccessibilityButtonModeObserver; -import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver; -import com.android.systemui.accessibility.ModeSwitchesController; -import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger; -import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.doze.AlwaysOnDisplayPolicy; -import com.android.systemui.dump.DumpManager; -import com.android.systemui.keyguard.KeyguardViewMediator; -import com.android.systemui.qs.ReduceBrightColorsController; -import com.android.systemui.settings.UserTracker; -import com.android.systemui.shared.system.ActivityManagerWrapper; -import com.android.systemui.shared.system.DevicePolicyManagerWrapper; -import com.android.systemui.shared.system.TaskStackChangeListeners; -import com.android.systemui.shared.system.WindowManagerWrapper; -import com.android.systemui.statusbar.connectivity.NetworkController; -import com.android.systemui.statusbar.phone.AutoHideController; -import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.DataSaverController; -import com.android.systemui.theme.ThemeOverlayApplier; -import com.android.systemui.util.leak.LeakDetector; -import com.android.systemui.util.settings.SecureSettings; - -import java.util.concurrent.Executor; - -import javax.inject.Named; +import com.android.systemui.broadcast.BroadcastDispatcherModule; +import com.android.systemui.theme.ThemeModule; +import com.android.systemui.util.leak.LeakModule; import dagger.Module; -import dagger.Provides; /** - * Provides dependencies for the root component of sysui injection. - * - * Only SystemUI owned classes and instances should go in here. Other, framework-owned classes - * should go in {@link FrameworkServicesModule}. - * - * See SystemUI/docs/dagger.md + * @deprecated This module is going away. Don't put anything in here. */ -@Module(includes = {NightDisplayListenerModule.class}) +@Deprecated +@Module(includes = { + AndroidInternalsModule.class, + BroadcastDispatcherModule.class, + LeakModule.class, + NightDisplayListenerModule.class, + SharedLibraryModule.class, + SettingsLibraryModule.class, + ThemeModule.class +}) public class DependencyProvider { - - /** */ - @Provides - @SysUISingleton - @Named(TIME_TICK_HANDLER_NAME) - public Handler provideTimeTickHandler() { - HandlerThread thread = new HandlerThread("TimeTick"); - thread.start(); - return new Handler(thread.getLooper()); - } - - /** */ - @Provides - @Main - public SharedPreferences provideSharePreferences(Context context) { - return Prefs.get(context); - } - - /** */ - @Provides - public AmbientDisplayConfiguration provideAmbientDisplayConfiguration(Context context) { - return new AmbientDisplayConfiguration(context); - } - - /** */ - @Provides - public Handler provideHandler() { - return new Handler(); - } - - /** */ - @Provides - @SysUISingleton - public DataSaverController provideDataSaverController(NetworkController networkController) { - return networkController.getDataSaverController(); - } - - @Provides - @SysUISingleton - public INotificationManager provideINotificationManager() { - return INotificationManager.Stub.asInterface( - ServiceManager.getService(Context.NOTIFICATION_SERVICE)); - } - - /** */ - @Provides - @SysUISingleton - public LayoutInflater providerLayoutInflater(Context context) { - return LayoutInflater.from(context); - } - - /** */ - @Provides - @SysUISingleton - public LeakDetector provideLeakDetector(DumpManager dumpManager) { - return LeakDetector.create(dumpManager); - } - - @SuppressLint("MissingPermission") - @SysUISingleton - @Provides - @Nullable - static LocalBluetoothManager provideLocalBluetoothController(Context context, - @Background Handler bgHandler) { - return LocalBluetoothManager.create(context, bgHandler, UserHandle.ALL); - } - - /** */ - @Provides - @SysUISingleton - public MetricsLogger provideMetricsLogger() { - return new MetricsLogger(); - } - - /** */ - @SysUISingleton - @Provides - static ThemeOverlayApplier provideThemeOverlayManager(Context context, - @Background Executor bgExecutor, - @Main Executor mainExecutor, - OverlayManager overlayManager, - DumpManager dumpManager) { - return new ThemeOverlayApplier(overlayManager, bgExecutor, mainExecutor, - context.getString(R.string.launcher_overlayable_package), - context.getString(R.string.themepicker_overlayable_package), dumpManager); - } - - /** */ - @Provides - @SysUISingleton - public AccessibilityFloatingMenuController provideAccessibilityFloatingMenuController( - Context context, AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver, - AccessibilityButtonModeObserver accessibilityButtonModeObserver, - KeyguardUpdateMonitor keyguardUpdateMonitor) { - return new AccessibilityFloatingMenuController(context, accessibilityButtonTargetsObserver, - accessibilityButtonModeObserver, keyguardUpdateMonitor); - } - - /** */ - @Provides - @SysUISingleton - public ConfigurationController provideConfigurationController(Context context) { - return new ConfigurationControllerImpl(context); - } - - /** */ - @SysUISingleton - @Provides - public AutoHideController provideAutoHideController(Context context, - @Main Handler mainHandler, IWindowManager iWindowManager) { - return new AutoHideController(context, mainHandler, iWindowManager); - } - - /** */ - @SysUISingleton - @Provides - public ReduceBrightColorsController provideReduceBrightColorsListener( - @Background Handler bgHandler, UserTracker userTracker, - ColorDisplayManager colorDisplayManager, SecureSettings secureSettings) { - return new ReduceBrightColorsController(userTracker, bgHandler, - colorDisplayManager, secureSettings); - } - - @Provides - @SysUISingleton - public ActivityManagerWrapper provideActivityManagerWrapper() { - return ActivityManagerWrapper.getInstance(); - } - - /** */ - @Provides - @SysUISingleton - public TaskStackChangeListeners provideTaskStackChangeListeners() { - return TaskStackChangeListeners.getInstance(); - } - - /** Provides and initializes the {#link BroadcastDispatcher} for SystemUI */ - @Provides - @SysUISingleton - public BroadcastDispatcher providesBroadcastDispatcher( - Context context, - @Background Looper backgroundLooper, - @Background Executor backgroundExecutor, - DumpManager dumpManager, - BroadcastDispatcherLogger logger, - UserTracker userTracker - ) { - BroadcastDispatcher bD = new BroadcastDispatcher(context, backgroundLooper, - backgroundExecutor, dumpManager, logger, userTracker); - bD.initialize(); - return bD; - } - - /** */ - @Provides - @SysUISingleton - public DevicePolicyManagerWrapper provideDevicePolicyManagerWrapper() { - return DevicePolicyManagerWrapper.getInstance(); - } - - /** */ - @Provides - @SysUISingleton - public LockPatternUtils provideLockPatternUtils(Context context) { - return new LockPatternUtils(context); - } - - /** */ - @Provides - @SysUISingleton - public AlwaysOnDisplayPolicy provideAlwaysOnDisplayPolicy(Context context) { - return new AlwaysOnDisplayPolicy(context); - } - - /***/ - @Provides - public NotificationMessagingUtil provideNotificationMessagingUtil(Context context) { - return new NotificationMessagingUtil(context); - } - - /** */ - @Provides - public ViewMediatorCallback providesViewMediatorCallback(KeyguardViewMediator viewMediator) { - return viewMediator.getViewMediatorCallback(); - } - - /** */ - @Provides - public WindowManagerWrapper providesWindowManagerWrapper() { - return WindowManagerWrapper.getInstance(); - } - - /** */ - @Provides - @SysUISingleton - public Choreographer providesChoreographer() { - return Choreographer.getInstance(); - } - - /** */ - @Provides - @SysUISingleton - public ModeSwitchesController providesModeSwitchesController(Context context) { - return new ModeSwitchesController(context); - } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index 535b54887e12..e512b7cf3a20 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -22,6 +22,7 @@ import android.app.ActivityTaskManager; import android.app.AlarmManager; import android.app.IActivityManager; import android.app.IActivityTaskManager; +import android.app.INotificationManager; import android.app.IWallpaperManager; import android.app.KeyguardManager; import android.app.NotificationManager; @@ -35,6 +36,7 @@ import android.app.trust.TrustManager; import android.content.ClipboardManager; import android.content.ContentResolver; import android.content.Context; +import android.content.SharedPreferences; import android.content.om.OverlayManager; import android.content.pm.IPackageManager; import android.content.pm.LauncherApps; @@ -44,6 +46,7 @@ import android.content.res.Resources; import android.hardware.SensorManager; import android.hardware.SensorPrivacyManager; import android.hardware.devicestate.DeviceStateManager; +import android.hardware.display.AmbientDisplayConfiguration; import android.hardware.display.ColorDisplayManager; import android.hardware.display.DisplayManager; import android.hardware.face.FaceManager; @@ -68,8 +71,10 @@ import android.telecom.TelecomManager; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.view.Choreographer; import android.view.CrossWindowBlurListeners; import android.view.IWindowManager; +import android.view.LayoutInflater; import android.view.ViewConfiguration; import android.view.WindowManager; import android.view.WindowManagerGlobal; @@ -82,6 +87,7 @@ import com.android.internal.appwidget.IAppWidgetService; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.LatencyTracker; +import com.android.systemui.Prefs; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.shared.system.PackageManagerWrapper; @@ -116,6 +122,12 @@ public class FrameworkServicesModule { return context.getSystemService(AlarmManager.class); } + /** */ + @Provides + public AmbientDisplayConfiguration provideAmbientDisplayConfiguration(Context context) { + return new AmbientDisplayConfiguration(context); + } + @Provides @Singleton static AudioManager provideAudioManager(Context context) { @@ -128,6 +140,13 @@ public class FrameworkServicesModule { return context.getSystemService(CaptioningManager.class); } + /** */ + @Provides + @Singleton + public Choreographer providesChoreographer() { + return Choreographer.getInstance(); + } + @Provides @Singleton static ColorDisplayManager provideColorDisplayManager(Context context) { @@ -293,6 +312,13 @@ public class FrameworkServicesModule { return context.getSystemService(LauncherApps.class); } + /** */ + @Provides + @Singleton + public LayoutInflater providerLayoutInflater(Context context) { + return LayoutInflater.from(context); + } + @Provides static MediaRouter2Manager provideMediaRouter2Manager(Context context) { return MediaRouter2Manager.getInstance(context); @@ -315,6 +341,14 @@ public class FrameworkServicesModule { return context.getSystemService(NotificationManager.class); } + /** */ + @Provides + @Singleton + public INotificationManager provideINotificationManager() { + return INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + } + @Provides @Singleton static PackageManager providePackageManager(Context context) { @@ -336,6 +370,13 @@ public class FrameworkServicesModule { /** */ @Provides + @Main + public SharedPreferences provideSharePreferences(Context context) { + return Prefs.get(context); + } + + /** */ + @Provides @Singleton static UiModeManager provideUiModeManager(Context context) { return context.getSystemService(UiModeManager.class); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SettingsLibraryModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SettingsLibraryModule.java new file mode 100644 index 000000000000..14626e1e6515 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dagger/SettingsLibraryModule.java @@ -0,0 +1,44 @@ +/* + * 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.dagger; + +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Handler; +import android.os.UserHandle; + +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.systemui.dagger.qualifiers.Background; + +import dagger.Module; +import dagger.Provides; + +/** */ +@Module +public class SettingsLibraryModule { + + /** */ + @SuppressLint("MissingPermission") + @SysUISingleton + @Provides + @Nullable + static LocalBluetoothManager provideLocalBluetoothController(Context context, + @Background Handler bgHandler) { + return LocalBluetoothManager.create(context, bgHandler, UserHandle.ALL); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SharedLibraryModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SharedLibraryModule.java new file mode 100644 index 000000000000..be156157fb72 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dagger/SharedLibraryModule.java @@ -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.dagger; + +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.DevicePolicyManagerWrapper; +import com.android.systemui.shared.system.TaskStackChangeListeners; +import com.android.systemui.shared.system.WindowManagerWrapper; + +import dagger.Module; +import dagger.Provides; + +/** */ +@Module +public class SharedLibraryModule { + + /** */ + @Provides + @SysUISingleton + public ActivityManagerWrapper provideActivityManagerWrapper() { + return ActivityManagerWrapper.getInstance(); + } + + /** */ + @Provides + @SysUISingleton + public DevicePolicyManagerWrapper provideDevicePolicyManagerWrapper() { + return DevicePolicyManagerWrapper.getInstance(); + } + + /** */ + @Provides + @SysUISingleton + public TaskStackChangeListeners provideTaskStackChangeListeners() { + return TaskStackChangeListeners.getInstance(); + } + + /** */ + @Provides + public WindowManagerWrapper providesWindowManagerWrapper() { + return WindowManagerWrapper.getInstance(); + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 7b65f453815c..bbeb66c5af52 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -29,6 +29,7 @@ import com.android.systemui.BootCompleteCacheImpl; import com.android.systemui.SystemUIFactory; import com.android.systemui.appops.dagger.AppOpsModule; import com.android.systemui.assist.AssistModule; +import com.android.systemui.biometrics.AlternateUdfpsTouchProvider; import com.android.systemui.biometrics.UdfpsHbmProvider; import com.android.systemui.biometrics.dagger.BiometricsModule; import com.android.systemui.classifier.FalsingModule; @@ -182,6 +183,9 @@ public abstract class SystemUIModule { @BindsOptionalOf abstract UdfpsHbmProvider optionalUdfpsHbmProvider(); + @BindsOptionalOf + abstract AlternateUdfpsTouchProvider optionalUdfpsTouchProvider(); + @SysUISingleton @Binds abstract SystemClock bindSystemClock(SystemClockImpl systemClock); diff --git a/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java index 735b3cd4b824..55df7792e2e6 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java @@ -29,11 +29,15 @@ import android.util.KeyValueListParser; import android.util.Log; import com.android.systemui.R; +import com.android.systemui.dagger.SysUISingleton; + +import javax.inject.Inject; /** * Class to store the policy for AOD, which comes from * {@link android.provider.Settings.Global} */ +@SysUISingleton public class AlwaysOnDisplayPolicy { public static final String TAG = "AlwaysOnDisplayPolicy"; @@ -130,6 +134,7 @@ public class AlwaysOnDisplayPolicy { private final Context mContext; private SettingsObserver mSettingsObserver; + @Inject public AlwaysOnDisplayPolicy(Context context) { context = context.getApplicationContext(); mContext = context; diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java index d4909c787d13..e878b22f61ed 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java @@ -16,8 +16,13 @@ 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.annotation.Nullable; import android.app.AlarmManager; +import android.app.StatusBarManager; import android.content.res.Resources; import android.hardware.SensorPrivacyManager; import android.net.ConnectivityManager; @@ -29,6 +34,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.text.format.DateFormat; import android.util.PluralsMessageFormatter; +import android.view.View; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; @@ -36,6 +42,7 @@ import com.android.systemui.dreams.dagger.DreamOverlayComponent; import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; import com.android.systemui.statusbar.policy.NextAlarmController; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.touch.TouchInsetManager; import com.android.systemui.util.ViewController; import com.android.systemui.util.time.DateFormatUtil; @@ -119,7 +126,8 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve DateFormatUtil dateFormatUtil, IndividualSensorPrivacyController sensorPrivacyController, DreamOverlayNotificationCountProvider dreamOverlayNotificationCountProvider, - ZenModeController zenModeController) { + ZenModeController zenModeController, + StatusBarWindowStateController statusBarWindowStateController) { super(view); mResources = resources; mMainExecutor = mainExecutor; @@ -131,6 +139,10 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve mSensorPrivacyController = sensorPrivacyController; mDreamOverlayNotificationCountProvider = dreamOverlayNotificationCountProvider; mZenModeController = zenModeController; + + // Register to receive show/hide updates for the system status bar. Our custom status bar + // needs to hide when the system status bar is showing to ovoid overlapping status bars. + statusBarWindowStateController.addListener(this::onSystemStatusBarStateChanged); } @Override @@ -229,4 +241,22 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve } }); } + + private void onSystemStatusBarStateChanged(@StatusBarManager.WindowVisibleState int state) { + mMainExecutor.execute(() -> { + if (!mIsAttached) { + 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; + } + }); + } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java index c147fde65cf7..54b9430eb445 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java @@ -42,6 +42,7 @@ import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.wm.shell.animation.FlingAnimationUtils; +import java.util.Optional; import javax.inject.Inject; import javax.inject.Named; @@ -77,7 +78,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private float mCurrentExpansion; - private final CentralSurfaces mCentralSurfaces; + private final Optional<CentralSurfaces> mCentralSurfaces; private VelocityTracker mVelocityTracker; @@ -107,7 +108,9 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { // If the user scrolling favors a vertical direction, begin capturing // scrolls. mCapture = Math.abs(distanceY) > Math.abs(distanceX); - mBouncerInitiallyShowing = mCentralSurfaces.isBouncerShowing(); + mBouncerInitiallyShowing = mCentralSurfaces + .map(CentralSurfaces::isBouncerShowing) + .orElse(false); if (mCapture) { // Since the user is dragging the bouncer up, set scrimmed to false. @@ -129,13 +132,17 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { return true; } + if (!mCentralSurfaces.isPresent()) { + return true; + } + // For consistency, we adopt the expansion definition found in the // PanelViewController. In this case, expansion refers to the view above the // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer // is fully hidden at full expansion (1) and fully visible when fully collapsed // (0). final float screenTravelPercentage = Math.abs(e1.getY() - e2.getY()) - / mCentralSurfaces.getDisplayHeight(); + / mCentralSurfaces.get().getDisplayHeight(); setPanelExpansion(mBouncerInitiallyShowing ? screenTravelPercentage : 1 - screenTravelPercentage); return true; @@ -171,7 +178,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { public BouncerSwipeTouchHandler( DisplayMetrics displayMetrics, StatusBarKeyguardViewManager statusBarKeyguardViewManager, - CentralSurfaces centralSurfaces, + Optional<CentralSurfaces> centralSurfaces, NotificationShadeWindowController notificationShadeWindowController, ValueAnimatorCreator valueAnimatorCreator, VelocityTrackerFactory velocityTrackerFactory, @@ -195,7 +202,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { @Override public void getTouchInitiationRegion(Region region) { - if (mCentralSurfaces.isBouncerShowing()) { + if (mCentralSurfaces.map(CentralSurfaces::isBouncerShowing).orElse(false)) { region.op(new Rect(0, 0, mDisplayMetrics.widthPixels, Math.round( mDisplayMetrics.heightPixels * mBouncerZoneScreenPercentage)), @@ -306,8 +313,12 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { } protected void flingToExpansion(float velocity, float expansion) { + if (!mCentralSurfaces.isPresent()) { + return; + } + // The animation utils deal in pixel units, rather than expansion height. - final float viewHeight = mCentralSurfaces.getDisplayHeight(); + final float viewHeight = mCentralSurfaces.get().getDisplayHeight(); final float currentHeight = viewHeight * mCurrentExpansion; final float targetHeight = viewHeight * expansion; 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 71dfa7433c32..165af135a792 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -25,6 +25,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardDisplayManager; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardViewController; +import com.android.keyguard.ViewMediatorCallback; import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent; import com.android.keyguard.dagger.KeyguardStatusBarViewComponent; import com.android.keyguard.dagger.KeyguardStatusViewComponent; @@ -127,4 +128,10 @@ public class KeyguardModule { notificationShadeWindowController, activityLaunchAnimator); } + + /** */ + @Provides + public ViewMediatorCallback providesViewMediatorCallback(KeyguardViewMediator viewMediator) { + return viewMediator.getViewMediatorCallback(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt index 8f0305f6dc6d..2ae1806f3b3d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt @@ -85,12 +85,13 @@ typealias ColorTransitionFactory = (Int, (ColorScheme) -> Int, (Int) -> Unit) -> */ class ColorSchemeTransition internal constructor( private val context: Context, - bgColor: Int, mediaViewHolder: MediaViewHolder, colorTransitionFactory: ColorTransitionFactory ) { - constructor(context: Context, bgColor: Int, mediaViewHolder: MediaViewHolder) : - this(context, bgColor, mediaViewHolder, ::ColorTransition) + constructor(context: Context, mediaViewHolder: MediaViewHolder) : + this(context, mediaViewHolder, ::ColorTransition) + + val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95) val surfaceColor = colorTransitionFactory( bgColor, diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index 8e81831a8dc3..ca25a18503ec 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -127,7 +127,6 @@ class MediaCarouselController @Inject constructor( private val visualStabilityCallback: OnReorderingAllowedListener private var needsReordering: Boolean = false private var keysNeedRemoval = mutableSetOf<String>() - private var bgColor = getBackgroundColor() protected var shouldScrollToActivePlayer: Boolean = false private var isRtl: Boolean = false set(value) { @@ -488,7 +487,7 @@ class MediaCarouselController @Inject constructor( val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp) - newRecs.bindRecommendation(data.copy(backgroundColor = bgColor)) + newRecs.bindRecommendation(data) val curVisibleMediaKey = MediaPlayerData.playerKeys() .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex) MediaPlayerData.addMediaRecommendation(key, data, newRecs, shouldPrioritize, systemClock) @@ -534,7 +533,6 @@ class MediaCarouselController @Inject constructor( } private fun recreatePlayers() { - bgColor = getBackgroundColor() pageIndicator.tintList = ColorStateList.valueOf(R.color.material_dynamic_neutral_variant80) MediaPlayerData.mediaData().forEach { (key, data, isSsMediaRec) -> @@ -554,10 +552,6 @@ class MediaCarouselController @Inject constructor( } } - private fun getBackgroundColor(): Int { - return context.getColor(R.color.material_dynamic_secondary95) - } - private fun updatePageIndicator() { val numPages = mediaContent.getChildCount() pageIndicator.setNumPages(numPages) @@ -904,7 +898,6 @@ internal object MediaPlayerData { private val EMPTY = MediaData( userId = -1, initialized = false, - backgroundColor = 0, app = null, appIcon = null, artist = null, diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index d3e2fc8f2424..915c3f294a59 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -60,6 +60,7 @@ import androidx.constraintlayout.widget.ConstraintSet; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.InstanceId; +import com.android.settingslib.Utils; import com.android.settingslib.widget.AdaptiveIcon; import com.android.systemui.ActivityIntentHelper; import com.android.systemui.R; @@ -150,7 +151,6 @@ public class MediaControlPanel { private MediaSession.Token mToken; private MediaController mController; private Lazy<MediaDataManager> mMediaDataManagerLazy; - private int mBackgroundColor; // Uid for the media app. protected int mUid = Process.INVALID_UID; private int mSmartspaceMediaItemsCount; @@ -343,8 +343,7 @@ public class MediaControlPanel { AnimatorSet exit = loadAnimator(R.anim.media_metadata_exit, Interpolators.EMPHASIZED_ACCELERATE, titleText, artistText); - mColorSchemeTransition = new ColorSchemeTransition( - mContext, mBackgroundColor, mMediaViewHolder); + mColorSchemeTransition = new ColorSchemeTransition(mContext, mMediaViewHolder); mMetadataAnimationHandler = new MetadataAnimationHandler(exit, enter); } @@ -409,7 +408,6 @@ public class MediaControlPanel { } mInstanceId = data.getInstanceId(); - mBackgroundColor = data.getBackgroundColor(); if (mToken == null || !mToken.equals(token)) { mToken = token; } @@ -485,7 +483,7 @@ public class MediaControlPanel { Drawable icon = device.getIcon(); if (icon instanceof AdaptiveIcon) { AdaptiveIcon aIcon = (AdaptiveIcon) icon; - aIcon.setBackgroundColor(mBackgroundColor); + aIcon.setBackgroundColor(mColorSchemeTransition.getBgColor()); iconView.setImageDrawable(aIcon); } else { iconView.setImageDrawable(icon); @@ -956,11 +954,11 @@ public class MediaControlPanel { } mSmartspaceId = SmallHash.hash(data.getTargetId()); - mBackgroundColor = data.getBackgroundColor(); mPackageName = data.getPackageName(); mInstanceId = data.getInstanceId(); TransitionLayout recommendationCard = mRecommendationViewHolder.getRecommendations(); - recommendationCard.setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor)); + recommendationCard.setBackgroundTintList( + Utils.getColorAttr(mContext, com.android.internal.R.attr.colorSurface)); List<SmartspaceAction> mediaRecommendationList = data.getRecommendations(); if (mediaRecommendationList == null || mediaRecommendationList.isEmpty()) { @@ -1054,6 +1052,28 @@ public class MediaControlPanel { recommendation.getTitle(), artistName, appName)); } + + // Set up title + CharSequence title = recommendation.getTitle(); + TextView titleView = + mRecommendationViewHolder.getMediaTitles().get(uiComponentIndex); + titleView.setText(title); + titleView.setTextColor(Utils.getColorAttrDefaultColor( + mContext, com.android.internal.R.attr.textColorPrimary)); + // TODO(b/223603970): If none of them have titles, should we then hide the views? + + // Set up subtitle + CharSequence subtitle = recommendation.getSubtitle(); + TextView subtitleView = + mRecommendationViewHolder.getMediaSubtitles().get(uiComponentIndex); + // It would look awkward to show a subtitle if we don't have a title. + boolean shouldShowSubtitleText = !TextUtils.isEmpty(title); + CharSequence subtitleText = shouldShowSubtitleText ? subtitle : ""; + subtitleView.setText(subtitleText); + subtitleView.setTextColor(Utils.getColorAttrDefaultColor( + mContext, com.android.internal.R.attr.textColorSecondary)); + // TODO(b/223603970): If none of them have subtitles, should we then hide the views? + uiComponentIndex++; } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt index f6d531b5b9d6..d04ec40c9f28 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt @@ -27,7 +27,6 @@ import com.android.systemui.R data class MediaData( val userId: Int, val initialized: Boolean = false, - val backgroundColor: Int, /** * App name that will be displayed on the player. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 0d65514bddc2..b2751cec5d9e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -85,7 +85,6 @@ private const val EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY = "dismiss_intent" private val LOADING = MediaData( userId = -1, initialized = false, - backgroundColor = 0, app = null, appIcon = null, artist = null, @@ -111,7 +110,6 @@ internal val EMPTY_SMARTSPACE_MEDIA_DATA = SmartspaceMediaData( cardAction = null, recommendations = emptyList(), dismissIntent = null, - backgroundColor = 0, headphoneConnectionTimeMillis = 0, instanceId = InstanceId.fakeInstanceId(-1)) @@ -177,7 +175,6 @@ class MediaDataManager( private val themeText = com.android.settingslib.Utils.getColorAttr(context, com.android.internal.R.attr.textColorPrimary).defaultColor - private val bgColor = context.getColor(R.color.material_dynamic_secondary95) // Internal listeners are part of the internal pipeline. External listeners (those registered // with [MediaDeviceManager.addListener]) receive events after they have propagated through @@ -591,7 +588,7 @@ class MediaDataManager( val mediaAction = getResumeMediaAction(resumeAction) val lastActive = systemClock.elapsedRealtime() foregroundExecutor.execute { - onMediaDataLoaded(packageName, null, MediaData(userId, true, bgColor, appName, + onMediaDataLoaded(packageName, null, MediaData(userId, true, appName, null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0), MediaButton(playOrPause = mediaAction), packageName, token, appIntent, device = null, active = false, @@ -601,14 +598,17 @@ class MediaDataManager( } } - private fun loadMediaDataInBg( + fun loadMediaDataInBg( key: String, sbn: StatusBarNotification, oldKey: String?, logEvent: Boolean = false ) { - val token = sbn.notification.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) - as MediaSession.Token? + val token = sbn.notification.extras.getParcelable( + Notification.EXTRA_MEDIA_SESSION, MediaSession.Token::class.java) + if (token == null) { + return + } val mediaController = mediaControllerFactory.create(token) val metadata = mediaController.metadata @@ -655,8 +655,8 @@ class MediaDataManager( val extras = sbn.notification.extras val deviceName = extras.getCharSequence(Notification.EXTRA_MEDIA_REMOTE_DEVICE, null) val deviceIcon = extras.getInt(Notification.EXTRA_MEDIA_REMOTE_ICON, -1) - val deviceIntent = extras.getParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT) - as PendingIntent? + val deviceIntent = extras.getParcelable( + Notification.EXTRA_MEDIA_REMOTE_INTENT, PendingIntent::class.java) Log.d(TAG, "$key is RCN for $deviceName") if (deviceName != null && deviceIcon > -1) { @@ -710,7 +710,7 @@ class MediaDataManager( val resumeAction: Runnable? = mediaEntries[key]?.resumeAction val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true val active = mediaEntries[key]?.active ?: true - onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app, + onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, app, smallIcon, artist, song, artWorkIcon, actionIcons, actionsToShowCollapsed, semanticActions, sbn.packageName, token, notif.contentIntent, device, active, resumeAction = resumeAction, playbackLocation = playbackLocation, @@ -1227,7 +1227,6 @@ class MediaDataManager( cardAction = target.baseAction, recommendations = target.iconGrid, dismissIntent = dismissIntent, - backgroundColor = 0, headphoneConnectionTimeMillis = target.creationTimeMillis, instanceId = logger.getNewInstanceId()) } diff --git a/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt index b2acc92fb30d..a83984036f60 100644 --- a/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt @@ -34,11 +34,23 @@ class RecommendationViewHolder private constructor(itemView: View) { val mediaCoverItems = listOf<ImageView>( itemView.requireViewById(R.id.media_cover1), itemView.requireViewById(R.id.media_cover2), - itemView.requireViewById(R.id.media_cover3)) + itemView.requireViewById(R.id.media_cover3) + ) val mediaCoverContainers = listOf<ViewGroup>( itemView.requireViewById(R.id.media_cover1_container), itemView.requireViewById(R.id.media_cover2_container), - itemView.requireViewById(R.id.media_cover3_container)) + itemView.requireViewById(R.id.media_cover3_container) + ) + val mediaTitles: List<TextView> = listOf( + itemView.requireViewById(R.id.media_title1), + itemView.requireViewById(R.id.media_title2), + itemView.requireViewById(R.id.media_title3) + ) + val mediaSubtitles: List<TextView> = listOf( + itemView.requireViewById(R.id.media_subtitle1), + itemView.requireViewById(R.id.media_subtitle2), + itemView.requireViewById(R.id.media_subtitle3) + ) // Settings/Guts screen val longPressText = itemView.requireViewById<TextView>(R.id.remove_text) diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt index c9d300bad5e0..60ef85d0b502 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt @@ -89,6 +89,7 @@ open class SeekBarObserver( holder.seekBar.thumb.alpha = if (data.seekAvailable) 255 else 0 holder.seekBar.isEnabled = data.seekAvailable progressDrawable?.animate = data.playing && !data.scrubbing + progressDrawable?.transitionEnabled = !data.seekAvailable if (holder.seekBar.maxHeight != seekBarEnabledMaxHeight) { holder.seekBar.maxHeight = seekBarEnabledMaxHeight diff --git a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt index e161ea7a9c68..930c5a8de125 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt @@ -51,10 +51,6 @@ data class SmartspaceMediaData( */ val dismissIntent: Intent?, /** - * View's background color. - */ - val backgroundColor: Int, - /** * The timestamp in milliseconds that headphone is connected. */ val headphoneConnectionTimeMillis: Long, diff --git a/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt index fbd17d7a212e..88f6f3dd9d0e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt @@ -5,13 +5,15 @@ import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.content.res.ColorStateList import android.graphics.Canvas -import android.graphics.Color import android.graphics.ColorFilter import android.graphics.Paint import android.graphics.Path import android.graphics.PixelFormat import android.graphics.drawable.Drawable import android.os.SystemClock +import android.util.MathUtils.lerp +import android.util.MathUtils.lerpInv +import android.util.MathUtils.lerpInvSat import androidx.annotation.VisibleForTesting import com.android.internal.graphics.ColorUtils import com.android.systemui.animation.Interpolators @@ -34,6 +36,13 @@ class SquigglyProgress : Drawable() { private var phaseOffset = 0f private var lastFrameTime = -1L + /* distance over which amplitude drops to zero, measured in wavelengths */ + private val transitionPeriods = 1.5f + /* wave endpoint as percentage of bar when play position is zero */ + private val minWaveEndpoint = 0.2f + /* wave endpoint as percentage of bar when play position matches wave endpoint */ + private val matchedWaveEndpoint = 0.6f + // Horizontal length of the sine wave var waveLength = 0f // Height of each peak of the sine wave @@ -51,6 +60,12 @@ class SquigglyProgress : Drawable() { linePaint.strokeWidth = value } + var transitionEnabled = true + set(value) { + field = value + invalidateSelf() + } + init { wavePaint.strokeCap = Paint.Cap.ROUND linePaint.strokeCap = Paint.Cap.ROUND @@ -95,57 +110,90 @@ class SquigglyProgress : Drawable() { if (animate) { invalidateSelf() val now = SystemClock.uptimeMillis() - phaseOffset -= (now - lastFrameTime) / 1000f * phaseSpeed + phaseOffset += (now - lastFrameTime) / 1000f * phaseSpeed phaseOffset %= waveLength lastFrameTime = now } - val totalProgressPx = (bounds.width() * (level / 10_000f)) + val progress = level / 10_000f + val totalProgressPx = bounds.width() * progress + val waveProgressPx = bounds.width() * ( + if (!transitionEnabled || progress > matchedWaveEndpoint) progress else + lerp(minWaveEndpoint, matchedWaveEndpoint, lerpInv(0f, matchedWaveEndpoint, progress))) + + // Build Wiggly Path + val waveStart = -phaseOffset + val waveEnd = waveProgressPx + val transitionLength = if (transitionEnabled) transitionPeriods * waveLength else 0.01f + + // helper function, computes amplitude for wave segment + val computeAmplitude: (Float, Float) -> Float = { x, sign -> + sign * heightFraction * lineAmplitude * + lerpInvSat(waveEnd, waveEnd - transitionLength, x) + } + + var currentX = waveEnd + var waveSign = if (phaseOffset < waveLength / 2) 1f else -1f + path.rewind() + + // Draw flat line from end to wave endpoint + path.moveTo(bounds.width().toFloat(), 0f) + path.lineTo(waveEnd, 0f) + + // First wave has shortened wavelength + // approx quarter wave gets us to first wave peak + // shouldn't be big enough to notice it's not a sin wave + currentX -= phaseOffset % (waveLength / 2) + val controlRatio = 0.25f + var currentAmp = computeAmplitude(currentX, waveSign) + path.cubicTo( + waveEnd, currentAmp * controlRatio, + lerp(currentX, waveEnd, controlRatio), currentAmp, + currentX, currentAmp) + + // Other waves have full wavelength + val dist = -1 * waveLength / 2f + while (currentX > waveStart) { + waveSign = -waveSign + val nextX = currentX + dist + val midX = currentX + dist / 2 + val nextAmp = computeAmplitude(nextX, waveSign) + path.cubicTo( + midX, currentAmp, + midX, nextAmp, + nextX, nextAmp) + currentAmp = nextAmp + currentX = nextX + } + + // Draw path; clip to progress position canvas.save() canvas.translate(bounds.left.toFloat(), bounds.centerY().toFloat()) - // Clip drawing, so we stop at the thumb canvas.clipRect( 0f, -lineAmplitude - strokeWidth, totalProgressPx, lineAmplitude + strokeWidth) - - // The squiggly line - val start = phaseOffset - var currentX = start - var waveSign = 1f - path.rewind() - path.moveTo(start, lineAmplitude * heightFraction) - while (currentX < totalProgressPx) { - val nextX = currentX + waveLength / 2f - val nextWaveSign = waveSign * -1 - path.cubicTo( - currentX + waveLength / 4f, lineAmplitude * waveSign * heightFraction, - nextX - waveLength / 4f, lineAmplitude * nextWaveSign * heightFraction, - nextX, lineAmplitude * nextWaveSign * heightFraction) - currentX = nextX - waveSign = nextWaveSign - } - wavePaint.style = Paint.Style.STROKE canvas.drawPath(path, wavePaint) canvas.restore() + // Draw path; clip between progression position & far edge + canvas.save() + canvas.translate(bounds.left.toFloat(), bounds.centerY().toFloat()) + canvas.clipRect( + totalProgressPx, + -lineAmplitude - strokeWidth, + bounds.width().toFloat(), + lineAmplitude + strokeWidth) + canvas.drawPath(path, linePaint) + canvas.restore() + // Draw round line cap at the beginning of the wave - val startAmp = cos(abs(phaseOffset) / waveLength * TWO_PI) - val p = Paint() - p.color = Color.WHITE + val startAmp = cos(abs(waveEnd - phaseOffset) / waveLength * TWO_PI) canvas.drawPoint( bounds.left.toFloat(), bounds.centerY() + startAmp * lineAmplitude * heightFraction, wavePaint) - - // Draw continuous line, to the right of the thumb - canvas.drawLine( - bounds.left.toFloat() + totalProgressPx, - bounds.centerY().toFloat(), - bounds.width().toFloat(), - bounds.centerY().toFloat(), - linePaint) } override fun getOpacity(): Int { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index 0edadcc82a24..73ab66e8471b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -117,6 +117,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { } mCheckBox.setVisibility(View.GONE); mStatusIcon.setVisibility(View.GONE); + mEndTouchArea.setVisibility(View.GONE); mContainerLayout.setOnClickListener(null); mTitleText.setTextColor(mController.getColorItemContent()); mSubTitleText.setTextColor(mController.getColorItemContent()); @@ -168,12 +169,16 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { setSingleLineLayout(getItemTitle(device), true /* bFocused */, true /* showSeekBar */, false /* showProgressBar */, false /* showStatus */); + mCheckBox.setOnCheckedChangeListener(null); mCheckBox.setVisibility(View.VISIBLE); mCheckBox.setChecked(true); - mSeekBar.setOnClickListener(null); - mSeekBar.setOnClickListener(v -> onGroupActionTriggered(false, device)); + mCheckBox.setOnCheckedChangeListener( + (buttonView, isChecked) -> onGroupActionTriggered(false, device)); setCheckBoxColor(mCheckBox, mController.getColorItemContent()); initSeekbar(device); + mEndTouchArea.setVisibility(View.VISIBLE); + mEndTouchArea.setOnClickListener(null); + mEndTouchArea.setOnClickListener((v) -> mCheckBox.performClick()); } else if (!mController.hasAdjustVolumeUserRestriction() && currentlyConnected) { mStatusIcon.setImageDrawable( mContext.getDrawable(R.drawable.media_output_status_check)); @@ -185,8 +190,12 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { initSeekbar(device); mCurrentActivePosition = position; } else if (isDeviceIncluded(mController.getSelectableMediaDevice(), device)) { + mCheckBox.setOnCheckedChangeListener(null); mCheckBox.setVisibility(View.VISIBLE); mCheckBox.setChecked(false); + mCheckBox.setOnCheckedChangeListener( + (buttonView, isChecked) -> onGroupActionTriggered(true, device)); + mEndTouchArea.setVisibility(View.VISIBLE); mContainerLayout.setOnClickListener(v -> onGroupActionTriggered(true, device)); setCheckBoxColor(mCheckBox, mController.getColorItemContent()); setSingleLineLayout(getItemTitle(device), false /* bFocused */, diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java index 5c536d469212..9dc29bd6e090 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -144,6 +144,7 @@ public abstract class MediaOutputBaseAdapter extends final LinearLayout mTwoLineLayout; final ImageView mStatusIcon; final CheckBox mCheckBox; + final LinearLayout mEndTouchArea; private String mDeviceId; MediaDeviceBaseViewHolder(View view) { @@ -159,6 +160,7 @@ public abstract class MediaOutputBaseAdapter extends mSeekBar = view.requireViewById(R.id.volume_seekbar); mStatusIcon = view.requireViewById(R.id.media_output_item_status); mCheckBox = view.requireViewById(R.id.check_box); + mEndTouchArea = view.requireViewById(R.id.end_action_area); } void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java deleted file mode 100644 index 72f308e4f6b1..000000000000 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.media.dialog; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.widget.SeekBar; - -/** - * Customized seekbar used by MediaOutputDialog, which only changes progress when dragging, - * otherwise performs click. - */ -public class MediaOutputSeekbar extends SeekBar { - private int mLastDownPosition = -1; - - public MediaOutputSeekbar(Context context) { - super(context); - } - - public MediaOutputSeekbar(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_DOWN) { - mLastDownPosition = Math.round(event.getX()); - } else if (event.getAction() == MotionEvent.ACTION_UP) { - if (mLastDownPosition == event.getX()) { - performClick(); - return true; - } - mLastDownPosition = -1; - } - return super.onTouchEvent(event); - } - - @Override - public boolean performClick() { - return super.performClick(); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt index cc37ef40321c..13340b7245fa 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt @@ -234,6 +234,7 @@ class FgsManagerController @Inject constructor( val dialog = SystemUIDialog(context) dialog.setTitle(R.string.fgs_manager_dialog_title) + dialog.setMessage(R.string.fgs_manager_dialog_message) val dialogContext = dialog.context @@ -241,7 +242,9 @@ class FgsManagerController @Inject constructor( recyclerView.layoutManager = LinearLayoutManager(dialogContext) recyclerView.adapter = appListAdapter - dialog.setView(recyclerView) + val topSpacing = dialogContext.resources + .getDimensionPixelSize(R.dimen.fgs_manager_list_top_spacing) + dialog.setView(recyclerView, 0, topSpacing, 0, 0) this.dialog = dialog diff --git a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java index 42d603ec5051..39d081da43cb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java @@ -26,6 +26,7 @@ import android.provider.Settings; import androidx.annotation.NonNull; +import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.CallbackController; @@ -38,6 +39,7 @@ import javax.inject.Inject; /** * @hide */ +@SysUISingleton public class ReduceBrightColorsController implements CallbackController<ReduceBrightColorsController.Listener> { private final ColorDisplayManager mManager; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index 8921e95fcee9..8ca095d9a609 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -58,6 +58,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.settingslib.Utils; +import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan; @@ -136,6 +137,7 @@ public class InternetDialog extends SystemUIDialog implements private Drawable mBackgroundOff = null; private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private boolean mCanConfigMobileData; + private boolean mCanChangeWifiState; // Wi-Fi entries private int mWifiNetworkHeight; @@ -180,6 +182,7 @@ public class InternetDialog extends SystemUIDialog implements mWifiManager = mInternetDialogController.getWifiManager(); mCanConfigMobileData = canConfigMobileData; mCanConfigWifi = canConfigWifi; + mCanChangeWifiState = WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(context); mKeyguard = keyguardStateController; mUiEventLogger = uiEventLogger; @@ -449,6 +452,14 @@ public class InternetDialog extends SystemUIDialog implements } mTurnWifiOnLayout.setBackground( (isDeviceLocked && mConnectedWifiEntry != null) ? mBackgroundOn : null); + + if (!mCanChangeWifiState && mWiFiToggle.isEnabled()) { + mWiFiToggle.setEnabled(false); + mWifiToggleTitleText.setEnabled(false); + final TextView summaryText = mDialogView.requireViewById(R.id.wifi_toggle_summary); + summaryText.setEnabled(false); + summaryText.setVisibility(View.VISIBLE); + } } @MainThread diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 83138f0666c1..551e8b51ae11 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -502,8 +502,8 @@ public class KeyguardIndicationController { private void updateLockScreenTrustMsg(int userId, CharSequence trustGrantedIndication, CharSequence trustManagedIndication) { - if (!TextUtils.isEmpty(trustGrantedIndication) - && mKeyguardUpdateMonitor.getUserHasTrust(userId)) { + final boolean userHasTrust = mKeyguardUpdateMonitor.getUserHasTrust(userId); + if (!TextUtils.isEmpty(trustGrantedIndication) && userHasTrust) { mRotateTextViewController.updateIndication( INDICATION_TYPE_TRUST, new KeyguardIndication.Builder() @@ -513,7 +513,7 @@ public class KeyguardIndicationController { false); } else if (!TextUtils.isEmpty(trustManagedIndication) && mKeyguardUpdateMonitor.getUserTrustIsManaged(userId) - && !mKeyguardUpdateMonitor.getUserHasTrust(userId)) { + && !userHasTrust) { mRotateTextViewController.updateIndication( INDICATION_TYPE_TRUST, new KeyguardIndication.Builder() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index f15df8ec2816..c1ea6bf7cec8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -72,6 +72,9 @@ class LockscreenShadeTransitionController @Inject constructor( dumpManager: DumpManager ) : Dumpable { private var pulseHeight: Float = 0f + @get:VisibleForTesting + var fractionToShade: Float = 0f + private set private var useSplitShade: Boolean = false private lateinit var nsslController: NotificationStackScrollLayoutController lateinit var notificationPanelController: NotificationPanelViewController @@ -405,9 +408,9 @@ class LockscreenShadeTransitionController @Inject constructor( if (field != value || forceApplyAmount) { field = value if (!nsslController.isInLockedDownShade() || field == 0f || forceApplyAmount) { - val notificationShelfProgress = + fractionToShade = MathUtils.saturate(dragDownAmount / notificationShelfTransitionDistance) - nsslController.setTransitionToFullShadeAmount(notificationShelfProgress) + nsslController.setTransitionToFullShadeAmount(fractionToShade) qSDragProgress = MathUtils.saturate(dragDownAmount / qsTransitionDistance) qS.setTransitionToFullShadeAmount(field, qSDragProgress) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 241a745b92e2..76f9db44af66 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -454,7 +454,7 @@ public class NotificationMediaManager implements Dumpable { * update this manager's internal state. * @return whether the current MediaMetadata changed (and needs to be announced to listeners). */ - private boolean findPlayingMediaNotification( + boolean findPlayingMediaNotification( @NonNull Collection<NotificationEntry> allNotifications) { boolean metaDataChanged = false; // Promote the media notification with a controller in 'playing' state, if any. @@ -465,7 +465,7 @@ public class NotificationMediaManager implements Dumpable { if (notif.isMediaNotification()) { final MediaSession.Token token = entry.getSbn().getNotification().extras.getParcelable( - Notification.EXTRA_MEDIA_SESSION); + Notification.EXTRA_MEDIA_SESSION, MediaSession.Token.class); if (token != null) { MediaController aController = new MediaController(mContext, token); if (PlaybackState.STATE_PLAYING diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 633786ffa787..afce945a5bc5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -85,9 +85,6 @@ public class NotificationShelf extends ActivatableNotificationView implements private NotificationShelfController mController; private float mActualWidth = -1; - /** Fraction of lockscreen to shade animation (on lockscreen swipe down). */ - private float mFractionToShade; - public NotificationShelf(Context context, AttributeSet attrs) { super(context, attrs); } @@ -234,13 +231,6 @@ public class NotificationShelf extends ActivatableNotificationView implements } /** - * @param fractionToShade Fraction of lockscreen to shade transition - */ - public void setFractionToShade(float fractionToShade) { - mFractionToShade = fractionToShade; - } - - /** * @return Actual width of shelf, accounting for possible ongoing width animation */ public int getActualWidth() { @@ -411,7 +401,8 @@ public class NotificationShelf extends ActivatableNotificationView implements || !mShowNotificationShelf || numViewsInShelf < 1f; - final float fractionToShade = Interpolators.STANDARD.getInterpolation(mFractionToShade); + final float fractionToShade = Interpolators.STANDARD.getInterpolation( + mAmbientState.getFractionToShade()); final float shortestWidth = mShelfIcons.calculateWidthFor(numViewsInShelf); updateActualWidth(fractionToShade, shortestWidth); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index 0fb6ea669458..039a3625c70c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -573,11 +573,10 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi } catch (RuntimeException e) { Log.e(TAG, "Unable to recover builder", e); // Trying to get the app name from the app info instead. - Parcelable appInfo = n.extras.getParcelable( - Notification.EXTRA_BUILDER_APPLICATION_INFO); - if (appInfo instanceof ApplicationInfo) { - appName = String.valueOf(((ApplicationInfo) appInfo).loadLabel( - c.getPackageManager())); + ApplicationInfo appInfo = n.extras.getParcelable( + Notification.EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class); + if (appInfo != null) { + appName = String.valueOf(appInfo.loadLabel(c.getPackageManager())); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 0fd9272c7da7..4fc347a09292 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -542,7 +542,8 @@ public final class NotificationEntry extends ListEntry { if (senderPerson == null) { return true; } - Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON); + Person user = extras.getParcelable( + Notification.EXTRA_MESSAGING_PERSON, Person.class); return Objects.equals(user, senderPerson); } } 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 efb46b9689d3..7245cb246932 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 @@ -2658,9 +2658,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return; } - // bail out if no public version - if (mPublicLayout.getChildCount() == 0) return; - if (!animated) { mPublicLayout.animate().cancel(); mPrivateLayout.animate().cancel(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java index 79f99b85c410..8732696dc7a1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java @@ -44,9 +44,8 @@ public class NotificationBigPictureTemplateViewWrapper extends NotificationTempl private void updateImageTag(StatusBarNotification sbn) { final Bundle extras = sbn.getNotification().extras; - boolean bigLargeIconSet = extras.containsKey(Notification.EXTRA_LARGE_ICON_BIG); - if (bigLargeIconSet) { - Icon bigLargeIcon = extras.getParcelable(Notification.EXTRA_LARGE_ICON_BIG); + Icon bigLargeIcon = extras.getParcelable(Notification.EXTRA_LARGE_ICON_BIG, Icon.class); + if (bigLargeIcon != null) { mRightIcon.setTag(ImageTransformState.ICON_TAG, bigLargeIcon); mLeftIcon.setTag(ImageTransformState.ICON_TAG, bigLargeIcon); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 25999a7f8dba..9acd60eb862a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -82,6 +82,23 @@ public class AmbientState { private boolean mAppearing; private float mPulseHeight = MAX_PULSE_HEIGHT; + /** Fraction of lockscreen to shade animation (on lockscreen swipe down). */ + private float mFractionToShade; + + /** + * @param fractionToShade Fraction of lockscreen to shade transition + */ + public void setFractionToShade(float fractionToShade) { + mFractionToShade = fractionToShade; + } + + /** + * @return fractionToShade Fraction of lockscreen to shade transition + */ + public float getFractionToShade() { + return mFractionToShade; + } + /** How we much we are sleeping. 1f fully dozing (AOD), 0f fully awake (for all other states) */ private float mDozeAmount = 0.0f; 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 b52fd6107701..851794c0440e 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 @@ -2295,7 +2295,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable int visibleIndex ) { return mStackScrollAlgorithm.getGapHeightForChild(mSectionsManager, visibleIndex, current, - previous); + previous, mAmbientState.getFractionToShade(), mAmbientState.isOnKeyguard()); } @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) @@ -5501,7 +5501,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * where it remains until the next lockscreen-to-shade transition. */ public void setFractionToShade(float fraction) { - mShelf.setFractionToShade(fraction); + mAmbientState.setFractionToShade(fraction); requestChildrenUpdate(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt index 9417aac49989..2b11f693da1d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt @@ -19,9 +19,11 @@ package com.android.systemui.statusbar.notification.stack import android.content.res.Resources import android.util.Log import android.view.View.GONE +import androidx.annotation.VisibleForTesting import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.StatusBarState.KEYGUARD import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow @@ -41,6 +43,7 @@ class NotificationStackSizeCalculator @Inject constructor( private val statusBarStateController: SysuiStatusBarStateController, + private val lockscreenShadeTransitionController: LockscreenShadeTransitionController, @Main private val resources: Resources ) { @@ -129,7 +132,7 @@ constructor( yield(dividerHeight + shelfIntrinsicHeight) // Only shelf. children.forEachIndexed { i, currentNotification -> - height += currentNotification.spaceNeeded(i, previous, stack, onLockscreen) + height += spaceNeeded(currentNotification, i, previous, stack, onLockscreen) previous = currentNotification val shelfHeight = @@ -156,22 +159,28 @@ constructor( private val NotificationStackScrollLayout.childrenSequence: Sequence<ExpandableView> get() = children.map { it as ExpandableView } - private fun onLockscreen() = statusBarStateController.state == KEYGUARD + @VisibleForTesting + fun onLockscreen() : Boolean { + return statusBarStateController.state == KEYGUARD + && lockscreenShadeTransitionController.fractionToShade == 0f + } - private fun ExpandableView.spaceNeeded( + @VisibleForTesting + fun spaceNeeded( + view: ExpandableView, visibleIndex: Int, previousView: ExpandableView?, stack: NotificationStackScrollLayout, onLockscreen: Boolean ): Float { - assert(isShowable(onLockscreen)) + assert(view.isShowable(onLockscreen)) var size = if (onLockscreen) { - getMinHeight(/* ignoreTemporaryStates= */ true).toFloat() + view.getMinHeight(/* ignoreTemporaryStates= */ true).toFloat() } else { - intrinsicHeight.toFloat() + view.intrinsicHeight.toFloat() } - size += calculateGapAndDividerHeight(stack, previousView, current = this, visibleIndex) + size += calculateGapAndDividerHeight(stack, previousView, current = view, visibleIndex) return size } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 9204c45ea1d5..22242b8fb4b4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -54,6 +54,7 @@ public class StackScrollAlgorithm { private int mPaddingBetweenElements; private int mGapHeight; + private int mGapHeightOnLockscreen; private int mCollapsedSize; private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState(); @@ -87,6 +88,8 @@ public class StackScrollAlgorithm { mPinnedZTranslationExtra = res.getDimensionPixelSize( R.dimen.heads_up_pinned_elevation); mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height); + mGapHeightOnLockscreen = res.getDimensionPixelSize( + R.dimen.notification_section_divider_height_lockscreen); mNotificationScrimPadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings); mMarginBottom = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom); } @@ -305,7 +308,8 @@ public class StackScrollAlgorithm { ambientState.getSectionProvider(), i, view, getPreviousView(i, state)); if (applyGapHeight) { - currentY += mGapHeight; + currentY += getGapForLocation( + ambientState.getFractionToShade(), ambientState.isOnKeyguard()); } if (ambientState.getShelf() != null) { @@ -454,8 +458,10 @@ public class StackScrollAlgorithm { ambientState.getSectionProvider(), i, view, getPreviousView(i, algorithmState)); if (applyGapHeight) { - algorithmState.mCurrentYPosition += expansionFraction * mGapHeight; - algorithmState.mCurrentExpandedYPosition += mGapHeight; + final float gap = getGapForLocation( + ambientState.getFractionToShade(), ambientState.isOnKeyguard()); + algorithmState.mCurrentYPosition += expansionFraction * gap; + algorithmState.mCurrentExpandedYPosition += gap; } viewState.yTranslation = algorithmState.mCurrentYPosition; @@ -539,16 +545,29 @@ public class StackScrollAlgorithm { SectionProvider sectionProvider, int visibleIndex, View child, - View previousChild) { + View previousChild, + float fractionToShade, + boolean onKeyguard) { if (childNeedsGapHeight(sectionProvider, visibleIndex, child, previousChild)) { - return mGapHeight; + return getGapForLocation(fractionToShade, onKeyguard); } else { return 0; } } + @VisibleForTesting + float getGapForLocation(float fractionToShade, boolean onKeyguard) { + if (fractionToShade > 0f) { + return MathUtils.lerp(mGapHeightOnLockscreen, mGapHeight, fractionToShade); + } + if (onKeyguard) { + return mGapHeightOnLockscreen; + } + return mGapHeight; + } + /** * Does a given child need a gap, i.e spacing before a view? * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java index 111cbbe81755..3ccef9d6eb14 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java @@ -23,12 +23,14 @@ import android.util.Log; import android.view.IWindowManager; import android.view.MotionEvent; +import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.AutoHideUiElement; import javax.inject.Inject; /** A controller to control all auto-hide things. Also see {@link AutoHideUiElement}. */ +@SysUISingleton public class AutoHideController { private static final String TAG = "AutoHideController"; private static final long AUTO_HIDE_TIMEOUT_MS = 2250; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt index 96fa8a5cfd71..34cd1ceeffc7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt @@ -20,11 +20,14 @@ import android.content.res.Configuration import android.graphics.Rect import android.os.LocaleList import android.view.View.LAYOUT_DIRECTION_RTL +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.policy.ConfigurationController import java.util.ArrayList +import javax.inject.Inject -class ConfigurationControllerImpl(context: Context) : ConfigurationController { +@SysUISingleton +class ConfigurationControllerImpl @Inject constructor(context: Context) : ConfigurationController { private val listeners: MutableList<ConfigurationController.ConfigurationListener> = ArrayList() private val lastConfig = Configuration() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt index 7db677ab6305..3e32b64ebe3a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt @@ -71,7 +71,13 @@ class KeyguardLiftController @Inject constructor( isListening = false updateListeningState() keyguardUpdateMonitor.requestFaceAuth(true) - keyguardUpdateMonitor.requestActiveUnlock() + if (keyguardUpdateMonitor.mRequestActiveUnlockOnWakeup) { + keyguardUpdateMonitor.requestActiveUnlock("wake-unlock," + + " extra=KeyguardLiftController") + } else if (keyguardUpdateMonitor.mInitiateActiveUnlockOnWakeup) { + keyguardUpdateMonitor.initiateActiveUnlock("wake-initiate," + + " extra=KeyguardLiftController") + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index c1142edec77c..af62f1830a39 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -3210,7 +3210,7 @@ public class NotificationPanelViewController extends PanelViewController { @Override protected void startUnlockHintAnimation() { - if (mPowerManager.isPowerSaveMode()) { + if (mPowerManager.isPowerSaveMode() || mAmbientState.getDozeAmount() > 0f) { onUnlockHintStarted(); onUnlockHintFinished(); return; @@ -3336,6 +3336,12 @@ public class NotificationPanelViewController extends PanelViewController { .log(LockscreenUiEvent.LOCKSCREEN_LOCK_SHOW_HINT); startUnlockHintAnimation(); } + if (mUpdateMonitor.isFaceEnrolled() + && mUpdateMonitor.mRequestActiveUnlockOnUnlockIntent + && mKeyguardBypassController.canBypass()) { + mUpdateMonitor.requestActiveUnlock("unlock-intent," + + " extra=lockScreenEmptySpaceTap", true); + } } return true; case StatusBarState.SHADE_LOCKED: diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java index 56c74bf98e6b..24660b261c51 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java @@ -288,7 +288,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW final boolean keyguardOrAod = state.mKeyguardShowing || (state.mDozing && mDozeParameters.getAlwaysOn()); if ((keyguardOrAod && !state.mBackdropShowing && !state.mLightRevealScrimOpaque) - || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe()) { + || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind()) { // Show the wallpaper if we're on keyguard/AOD and the wallpaper is not occluded by a // solid backdrop. Also, show it if we are currently animating between the // keyguard and the surface behind the keyguard - we want to use the wallpaper as a 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 49cd56f0176d..6758b761b4a1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -589,10 +589,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } private void updateAlternateAuthShowing(boolean updateScrim) { + final boolean isShowingAltAuth = isShowingAlternateAuth(); if (mKeyguardMessageAreaController != null) { - mKeyguardMessageAreaController.setAltBouncerShowing(isShowingAlternateAuth()); + mKeyguardMessageAreaController.setAltBouncerShowing(isShowingAltAuth); } - mBypassController.setAltBouncerShowing(isShowingAlternateAuth()); + mBypassController.setAltBouncerShowing(isShowingAltAuth); + mKeyguardUpdateManager.setUdfpsBouncerShowing(isShowingAltAuth); if (updateScrim) { mCentralSurfaces.updateScrimController(); @@ -1268,6 +1270,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb pw.println(" mAfterKeyguardGoneAction: " + mAfterKeyguardGoneAction); pw.println(" mAfterKeyguardGoneRunnables: " + mAfterKeyguardGoneRunnables); pw.println(" mPendingWakeupAction: " + mPendingWakeupAction); + pw.println(" isBouncerShowing(): " + isBouncerShowing()); + pw.println(" bouncerIsOrWillBeShowing(): " + bouncerIsOrWillBeShowing()); if (mBouncer != null) { mBouncer.dump(pw); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java index c3268354a539..1b7353923ada 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java @@ -29,10 +29,13 @@ import com.android.systemui.statusbar.connectivity.AccessPointController; import com.android.systemui.statusbar.connectivity.AccessPointControllerImpl; import com.android.systemui.statusbar.connectivity.NetworkController; import com.android.systemui.statusbar.connectivity.NetworkControllerImpl; +import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; import com.android.systemui.statusbar.policy.BluetoothController; import com.android.systemui.statusbar.policy.BluetoothControllerImpl; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.CastControllerImpl; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.DeviceControlsController; import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl; import com.android.systemui.statusbar.policy.DevicePostureController; @@ -85,6 +88,10 @@ public interface StatusBarPolicyModule { /** */ @Binds + ConfigurationController bindConfigurationController(ConfigurationControllerImpl impl); + + /** */ + @Binds ExtensionController provideExtensionController(ExtensionControllerImpl controllerImpl); /** */ @@ -182,4 +189,11 @@ public interface StatusBarPolicyModule { return resources.getStringArray( R.array.config_perDeviceStateRotationLockDefaults); } + + /** */ + @Provides + @SysUISingleton + static DataSaverController provideDataSaverController(NetworkController networkController) { + return networkController.getDataSaverController(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeModule.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeModule.java new file mode 100644 index 000000000000..7fa90df0e792 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeModule.java @@ -0,0 +1,49 @@ +/* + * 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.theme; + +import android.content.res.Resources; + +import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.util.concurrency.SysUIConcurrencyModule; + +import javax.inject.Named; + +import dagger.Module; +import dagger.Provides; + +/** */ +@Module(includes = {SysUIConcurrencyModule.class}) +public class ThemeModule { + static final String LAUNCHER_PACKAGE = "theme_launcher_package"; + static final String THEME_PICKER_PACKAGE = "theme_picker_package"; + + /** */ + @Provides + @Named(LAUNCHER_PACKAGE) + static String provideLauncherPackage(@Main Resources resources) { + return resources.getString(R.string.launcher_overlayable_package); + } + + /** */ + @Provides + @Named(THEME_PICKER_PACKAGE) + static String provideThemePickerPackage(@Main Resources resources) { + return resources.getString(R.string.themepicker_overlayable_package); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java index d795d81e44bf..ba39367e290e 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java @@ -31,6 +31,7 @@ import androidx.annotation.VisibleForTesting; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dump.DumpManager; import com.google.android.collect.Lists; @@ -45,6 +46,9 @@ import java.util.Set; import java.util.concurrent.Executor; import java.util.stream.Collectors; +import javax.inject.Inject; +import javax.inject.Named; + /** * Responsible for orchestrating overlays, based on user preferences and other inputs from * {@link ThemeOverlayController}. @@ -134,17 +138,17 @@ public class ThemeOverlayApplier implements Dumpable { private final Map<String, String> mCategoryToTargetPackage = new ArrayMap<>(); private final OverlayManager mOverlayManager; private final Executor mBgExecutor; - private final Executor mMainExecutor; private final String mLauncherPackage; private final String mThemePickerPackage; + @Inject public ThemeOverlayApplier(OverlayManager overlayManager, - Executor bgExecutor, - Executor mainExecutor, - String launcherPackage, String themePickerPackage, DumpManager dumpManager) { + @Background Executor bgExecutor, + @Named(ThemeModule.LAUNCHER_PACKAGE) String launcherPackage, + @Named(ThemeModule.THEME_PICKER_PACKAGE) String themePickerPackage, + DumpManager dumpManager) { mOverlayManager = overlayManager; mBgExecutor = bgExecutor; - mMainExecutor = mainExecutor; mLauncherPackage = launcherPackage; mThemePickerPackage = themePickerPackage; mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet( diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java index 6fdce1ae4ec2..640adcc9dd94 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java @@ -22,6 +22,12 @@ import com.android.systemui.dagger.SysUIComponent; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.SystemUIBinder; import com.android.systemui.dagger.SystemUIModule; +import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule; +import com.android.systemui.statusbar.notification.dagger.NotificationsModule; +import com.android.systemui.statusbar.notification.row.NotificationRowModule; + +import com.android.systemui.keyguard.dagger.KeyguardModule; +import com.android.systemui.recents.RecentsModule; import dagger.Subcomponent; @@ -30,13 +36,17 @@ import dagger.Subcomponent; */ @SysUISingleton @Subcomponent(modules = { + CentralSurfacesDependenciesModule.class, DefaultComponentBinder.class, DependencyProvider.class, - SystemUIBinder.class, + KeyguardModule.class, + NotificationRowModule.class, + NotificationsModule.class, + RecentsModule.class, SystemUIModule.class, + TvSystemUIBinder.class, TVSystemUICoreStartableModule.class, - TvSystemUIModule.class, - TvSystemUIBinder.class}) + TvSystemUIModule.class}) public interface TvSysUIComponent extends SysUIComponent { /** diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbAudioWarningDialogMessage.java b/packages/SystemUI/src/com/android/systemui/usb/UsbAudioWarningDialogMessage.java new file mode 100644 index 000000000000..e06353b3aad3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbAudioWarningDialogMessage.java @@ -0,0 +1,124 @@ +/* + * 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.usb; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; +import android.content.res.Resources; +import android.util.Log; + +import com.android.systemui.R; + +import java.lang.annotation.Retention; + +import javax.inject.Inject; + +/** + * USB Audio devices warning dialog messages help class. + */ +public class UsbAudioWarningDialogMessage { + private static final String TAG = "UsbAudioWarningDialogMessage"; + + @Retention(SOURCE) + @IntDef({TYPE_PERMISSION, TYPE_CONFIRM}) + public @interface DialogType {} + public static final int TYPE_PERMISSION = 0; + public static final int TYPE_CONFIRM = 1; + + private int mDialogType; + private UsbDialogHelper mDialogHelper; + + @Inject + public UsbAudioWarningDialogMessage() { + } + + /** + * Initialize USB audio warning dialog message type and helper class. + * @param type Dialog type for Activity. + * @param usbDialogHelper Helper class for getting USB permission and confirm dialogs + */ + public void init(@DialogType int type, UsbDialogHelper usbDialogHelper) { + mDialogType = type; + mDialogHelper = usbDialogHelper; + } + + boolean hasRecordPermission() { + return mDialogHelper.packageHasAudioRecordingPermission(); + } + + boolean isUsbAudioDevice() { + return mDialogHelper.isUsbDevice() && (mDialogHelper.deviceHasAudioCapture() + || (mDialogHelper.deviceHasAudioPlayback())); + } + + boolean hasAudioPlayback() { + return mDialogHelper.deviceHasAudioPlayback(); + } + + boolean hasAudioCapture() { + return mDialogHelper.deviceHasAudioCapture(); + } + + /** + * According to USB audio warning dialog matrix table to return warning message id. + * @return string resId for USB audio warning dialog message, otherwise {ID_NULL}. + * See usb_audio.md for USB audio Permission and Confirmation warning dialog resource + * string id matrix table. + */ + public int getMessageId() { + if (!mDialogHelper.isUsbDevice()) { + return getUsbAccessoryPromptId(); + } + + if (hasRecordPermission() && isUsbAudioDevice()) { + // case# 1, 2, 3 + return R.string.usb_audio_device_prompt; + } else if (!hasRecordPermission() && isUsbAudioDevice() && hasAudioPlayback() + && !hasAudioCapture()) { + // case# 5 + return R.string.usb_audio_device_prompt; + } + + if (!hasRecordPermission() && isUsbAudioDevice() && hasAudioCapture()) { + // case# 6,7 + return R.string.usb_audio_device_prompt_warn; + } + + Log.w(TAG, "Only shows title with empty content description!"); + return Resources.ID_NULL; + } + + /** + * Gets prompt dialog title. + * @return string id for USB prompt dialog title. + */ + public int getPromptTitleId() { + return (mDialogType == TYPE_PERMISSION) + ? R.string.usb_audio_device_permission_prompt_title + : R.string.usb_audio_device_confirm_prompt_title; + } + + /** + * Gets USB Accessory prompt message id. + * @return string id for USB Accessory prompt message. + */ + public int getUsbAccessoryPromptId() { + return (mDialogType == TYPE_PERMISSION) + ? R.string.usb_accessory_permission_prompt : R.string.usb_accessory_confirm_prompt; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java index f4558feb69aa..6e523d88a326 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java @@ -16,7 +16,10 @@ package com.android.systemui.usb; -import com.android.systemui.R; +import android.content.res.Resources; +import android.os.Bundle; + +import javax.inject.Inject; /** * Dialog shown to confirm the package to start when a USB device or accessory is attached and there @@ -24,23 +27,35 @@ import com.android.systemui.R; */ public class UsbConfirmActivity extends UsbDialogActivity { + private UsbAudioWarningDialogMessage mUsbConfirmMessageHandler; + + @Inject + public UsbConfirmActivity(UsbAudioWarningDialogMessage usbAudioWarningDialogMessage) { + mUsbConfirmMessageHandler = usbAudioWarningDialogMessage; + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + mUsbConfirmMessageHandler.init(UsbAudioWarningDialogMessage.TYPE_CONFIRM, mDialogHelper); + } + @Override protected void onResume() { super.onResume(); - final int strId; - boolean useRecordWarning = false; - if (mDialogHelper.isUsbDevice()) { - useRecordWarning = mDialogHelper.deviceHasAudioCapture() - && !mDialogHelper.packageHasAudioRecordingPermission(); - strId = useRecordWarning - ? R.string.usb_device_confirm_prompt_warn - : R.string.usb_device_confirm_prompt; - } else { - // UsbAccessory case - strId = R.string.usb_accessory_confirm_prompt; - } - setAlertParams(strId); // Only show the "always use" checkbox if there is no USB/Record warning + final boolean useRecordWarning = mDialogHelper.isUsbDevice() + && (mDialogHelper.deviceHasAudioCapture() + && !mDialogHelper.packageHasAudioRecordingPermission()); + + final int titleId = mUsbConfirmMessageHandler.getPromptTitleId(); + final String title = getString(titleId, mDialogHelper.getAppName(), + mDialogHelper.getDeviceDescription()); + final int messageId = mUsbConfirmMessageHandler.getMessageId(); + String message = (messageId != Resources.ID_NULL) + ? getString(messageId, mDialogHelper.getAppName(), + mDialogHelper.getDeviceDescription()) : null; + setAlertParams(title, message); if (!useRecordWarning) { addAlwaysUseCheckbox(); } diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDialogActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDialogActivity.java index 930bc338a918..55dec5fbe344 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbDialogActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDialogActivity.java @@ -41,7 +41,7 @@ abstract class UsbDialogActivity extends AlertActivity private TextView mClearDefaultHint; @Override - protected final void onCreate(Bundle savedInstanceState) { + protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().addSystemFlags( WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); @@ -87,11 +87,10 @@ abstract class UsbDialogActivity extends AlertActivity } } - void setAlertParams(int strId) { + void setAlertParams(String title, String message) { final AlertController.AlertParams ap = mAlertParams; - ap.mTitle = mDialogHelper.getAppName(); - ap.mMessage = getString(strId, mDialogHelper.getAppName(), - mDialogHelper.getDeviceDescription()); + ap.mTitle = title; + ap.mMessage = message; ap.mPositiveButtonText = getString(android.R.string.ok); ap.mNegativeButtonText = getString(android.R.string.cancel); ap.mPositiveButtonListener = this; diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDialogHelper.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDialogHelper.java index d63fb86ea6d4..3f061d3113be 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbDialogHelper.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDialogHelper.java @@ -134,6 +134,13 @@ public class UsbDialogHelper { } /** + * @return True if the intent contains a UsbDevice which can play audio. + */ + public boolean deviceHasAudioPlayback() { + return mDevice != null && mDevice.getHasAudioPlayback(); + } + + /** * @return True if the package has RECORD_AUDIO permission specified in its manifest. */ public boolean packageHasAudioRecordingPermission() { @@ -272,15 +279,15 @@ public class UsbDialogHelper { return desc; } - /** - * Whether the calling package can set as default handler of the USB device or accessory. - * In case of a UsbAccessory this is the case if the calling package has an intent filter for - * {@link UsbManager#ACTION_USB_ACCESSORY_ATTACHED} with a usb-accessory filter matching the - * attached accessory. In case of a UsbDevice this is the case if the calling package has an - * intent filter for {@link UsbManager#ACTION_USB_DEVICE_ATTACHED} with a usb-device filter - * matching the attached device. - * - * @return True if the package can be default for the USB device. + /** + * Whether the calling package can set as default handler of the USB device or accessory. + * In case of a UsbAccessory this is the case if the calling package has an intent filter for + * {@link UsbManager#ACTION_USB_ACCESSORY_ATTACHED} with a usb-accessory filter matching the + * attached accessory. In case of a UsbDevice this is the case if the calling package has an + * intent filter for {@link UsbManager#ACTION_USB_DEVICE_ATTACHED} with a usb-device filter + * matching the attached device. + * + * @return True if the package can be default for the USB device. */ public boolean canBeDefault() { return mCanBeDefault; diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java index 38d634777177..9484d3a63801 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java @@ -16,7 +16,10 @@ package com.android.systemui.usb; -import com.android.systemui.R; +import android.content.res.Resources; +import android.os.Bundle; + +import javax.inject.Inject; /** * Dialog shown when a package requests access to a USB device or accessory. @@ -24,23 +27,36 @@ import com.android.systemui.R; public class UsbPermissionActivity extends UsbDialogActivity { private boolean mPermissionGranted = false; + private UsbAudioWarningDialogMessage mUsbPermissionMessageHandler; + + @Inject + public UsbPermissionActivity(UsbAudioWarningDialogMessage usbAudioWarningDialogMessage) { + mUsbPermissionMessageHandler = usbAudioWarningDialogMessage; + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + mUsbPermissionMessageHandler.init(UsbAudioWarningDialogMessage.TYPE_PERMISSION, + mDialogHelper); + } @Override protected void onResume() { super.onResume(); - final int strId; - boolean useRecordWarning = false; - if (mDialogHelper.isUsbDevice()) { - useRecordWarning = mDialogHelper.deviceHasAudioCapture() - && !mDialogHelper.packageHasAudioRecordingPermission(); - strId = useRecordWarning - ? R.string.usb_device_permission_prompt_warn - : R.string.usb_device_permission_prompt; - } else { - // UsbAccessory case - strId = R.string.usb_accessory_permission_prompt; - } - setAlertParams(strId); + final boolean useRecordWarning = mDialogHelper.isUsbDevice() + && (mDialogHelper.deviceHasAudioCapture() + && !mDialogHelper.packageHasAudioRecordingPermission()); + + final int titleId = mUsbPermissionMessageHandler.getPromptTitleId(); + final String title = getString(titleId, mDialogHelper.getAppName(), + mDialogHelper.getDeviceDescription()); + final int messageId = mUsbPermissionMessageHandler.getMessageId(); + String message = (messageId != Resources.ID_NULL) + ? getString(messageId, mDialogHelper.getAppName(), + mDialogHelper.getDeviceDescription()) : null; + setAlertParams(title, message); + // Only show the "always use" checkbox if there is no USB/Record warning if (!useRecordWarning && mDialogHelper.canBeDefault()) { addAlwaysUseCheckbox(); diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java index 107fe870a07a..323db5cfd249 100644 --- a/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java @@ -64,6 +64,15 @@ public abstract class GlobalConcurrencyModule { } /** + * @deprecated Use @Main Handler. + */ + @Deprecated + @Provides + public static Handler provideHandler() { + return new Handler(); + } + + /** * Provide a Main-Thread Executor. */ @Provides diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java index 8f61abcca4dc..8c736dc9fe70 100644 --- a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java @@ -16,6 +16,8 @@ package com.android.systemui.util.concurrency; +import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; + import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -28,6 +30,8 @@ import com.android.systemui.dagger.qualifiers.Main; import java.util.concurrent.Executor; +import javax.inject.Named; + import dagger.Module; import dagger.Provides; @@ -162,4 +166,14 @@ public abstract class SysUIConcurrencyModule { @Background DelayableExecutor executor) { return new MessageRouterImpl(executor); } + + /** */ + @Provides + @SysUISingleton + @Named(TIME_TICK_HANDLER_NAME) + public static Handler provideTimeTickHandler() { + HandlerThread thread = new HandlerThread("TimeTick"); + thread.start(); + return new Handler(thread.getLooper()); + } } diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/LeakDetector.java b/packages/SystemUI/src/com/android/systemui/util/leak/LeakDetector.java index 95e8273beded..f0e389078c2f 100644 --- a/packages/SystemUI/src/com/android/systemui/util/leak/LeakDetector.java +++ b/packages/SystemUI/src/com/android/systemui/util/leak/LeakDetector.java @@ -17,9 +17,9 @@ package com.android.systemui.util.leak; import android.os.Build; +import android.util.IndentingPrintWriter; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.IndentingPrintWriter; import com.android.systemui.Dumpable; import com.android.systemui.dump.DumpManager; @@ -133,17 +133,4 @@ public class LeakDetector implements Dumpable { pw.decreaseIndent(); pw.println(); } - - public static LeakDetector create(DumpManager dumpManager) { - if (ENABLED) { - TrackedCollections collections = new TrackedCollections(); - return new LeakDetector( - collections, - new TrackedGarbage(collections), - new TrackedObjects(collections), - dumpManager); - } else { - return new LeakDetector(null, null, null, dumpManager); - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/LeakModule.java b/packages/SystemUI/src/com/android/systemui/util/leak/LeakModule.java new file mode 100644 index 000000000000..8f5b4d6bdce9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/leak/LeakModule.java @@ -0,0 +1,42 @@ +/* + * 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.util.leak; + +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.util.Compile; + +import dagger.Module; +import dagger.Provides; + +/** */ +@Module +public class LeakModule { + @Provides + @SysUISingleton + LeakDetector providesLeakDetector(DumpManager dumpManager, TrackedCollections collections) { + if (Compile.IS_DEBUG) { + return new LeakDetector( + collections, + new TrackedGarbage(collections), + new TrackedObjects(collections), + dumpManager); + } else { + return new LeakDetector(null, null, null, dumpManager); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/TrackedCollections.java b/packages/SystemUI/src/com/android/systemui/util/leak/TrackedCollections.java index 5577daf7e4bf..a3e13bd8f72b 100644 --- a/packages/SystemUI/src/com/android/systemui/util/leak/TrackedCollections.java +++ b/packages/SystemUI/src/com/android/systemui/util/leak/TrackedCollections.java @@ -24,6 +24,8 @@ import java.util.Collection; import java.util.Map; import java.util.function.Predicate; +import javax.inject.Inject; + /** * Tracks the size of collections. */ @@ -34,6 +36,10 @@ public class TrackedCollections { private final WeakIdentityHashMap<Collection<?>, CollectionState> mCollections = new WeakIdentityHashMap<>(); + @Inject + TrackedCollections() { + } + /** * @see LeakDetector#trackCollection(Collection, String) */ diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java index 9b2702ff7bf2..1243c4718229 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java @@ -215,6 +215,7 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard logoView.setImageDrawable(mContext.getDrawable(R.drawable.ic_qs_plus)); mEmptyStateView.<TextView>requireViewById(R.id.empty_state_title).setText(label); mEmptyStateView.setOnClickListener(clickListener); + mAppButton.setOnClickListener(clickListener); } void showErrorMessage(@Nullable CharSequence message) { @@ -256,6 +257,11 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard } @VisibleForTesting + Button getAppButton() { + return mAppButton; + } + + @VisibleForTesting TextView getErrorView() { return mErrorView; } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt index cc606de1b0b9..b1e2012ecd40 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt @@ -97,5 +97,6 @@ private fun faceModel(user: Int) = KeyguardFaceListenModel( primaryUser = false, scanningAllowedByStrongAuth = false, secureCameraLaunched = false, - switchingUser = false + switchingUser = false, + udfpsBouncerShowing = false ) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index 14c903c86b62..ba058c71a7f4 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -291,7 +291,9 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { @Test public void testTwoOrMoreUsersDoesAllowDropDown() { // GIVEN one user has been setup - when(mUserSwitcherController.getUsers()).thenReturn(buildUserRecords(2)); + ArrayList<UserRecord> records = buildUserRecords(2); + when(mUserSwitcherController.getCurrentUserRecord()).thenReturn(records.get(0)); + when(mUserSwitcherController.getUsers()).thenReturn(records); // WHEN UserSwitcherViewMode is initialized setupUserSwitcher(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 05cf6a50eda0..5d624cdc47a1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.inOrder; @@ -183,6 +184,8 @@ public class UdfpsControllerTest extends SysuiTestCase { private SystemUIDialogManager mSystemUIDialogManager; @Mock private ActivityLaunchAnimator mActivityLaunchAnimator; + @Mock + private AlternateUdfpsTouchProvider mTouchProvider; // Capture listeners so that they can be used to send events @Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor; @@ -256,7 +259,8 @@ public class UdfpsControllerTest extends SysuiTestCase { mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker, - mActivityLaunchAnimator); + mActivityLaunchAnimator, + Optional.of(mTouchProvider)); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture()); @@ -418,9 +422,10 @@ public class UdfpsControllerTest extends SysuiTestCase { mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); moveEvent.recycle(); // THEN FingerprintManager is notified about onPointerDown - verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), - eq(mUdfpsController.mSensorProps.sensorId), + verify(mTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0), eq(0f), eq(0f)); + verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(), + anyFloat(), anyFloat()); verify(mLatencyTracker).onActionStart(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); // AND illumination begins verify(mUdfpsView).startIllumination(mOnIlluminatedRunnableCaptor.capture()); @@ -447,9 +452,10 @@ public class UdfpsControllerTest extends SysuiTestCase { // AND onIlluminatedRunnable that notifies FingerprintManager is set verify(mUdfpsView).startIllumination(mOnIlluminatedRunnableCaptor.capture()); mOnIlluminatedRunnableCaptor.getValue().run(); - verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), - eq(mUdfpsController.mSensorProps.sensorId), + verify(mTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0), eq(3f) /* minor */, eq(2f) /* major */); + verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(), + anyFloat(), anyFloat()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java index 186f2bb1d127..1296417afd2b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java @@ -205,6 +205,7 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { captureAltAuthInterceptor(); when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true); + when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true); mAltAuthInterceptor.onBouncerVisibilityChanged(); assertTrue(mController.shouldPauseAuth()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt index 141b3b446c01..53dcc8d269c9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt @@ -37,7 +37,8 @@ class FakeBroadcastDispatcher( dumpManager: DumpManager, logger: BroadcastDispatcherLogger, userTracker: UserTracker -) : BroadcastDispatcher(context, looper, executor, dumpManager, logger, userTracker) { +) : BroadcastDispatcher( + context, looper, executor, dumpManager, logger, userTracker, PendingRemovalStore(logger)) { private val registeredReceivers = ArraySet<BroadcastReceiver>() 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 4915dedb376e..3c28d48c2eb5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java @@ -16,10 +16,14 @@ package com.android.systemui.dreams; +import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; +import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; + 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.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -32,6 +36,7 @@ import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.provider.Settings; import android.testing.AndroidTestingRunner; +import android.view.View; import androidx.test.filters.SmallTest; @@ -40,6 +45,8 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; import com.android.systemui.statusbar.policy.NextAlarmController; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.statusbar.window.StatusBarWindowStateController; +import com.android.systemui.statusbar.window.StatusBarWindowStateListener; import com.android.systemui.touch.TouchInsetManager; import com.android.systemui.util.time.DateFormatUtil; @@ -84,6 +91,8 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { ZenModeController mZenModeController; @Mock DreamOverlayNotificationCountProvider mDreamOverlayNotificationCountProvider; + @Mock + StatusBarWindowStateController mStatusBarWindowStateController; private final Executor mMainExecutor = Runnable::run; @@ -107,7 +116,8 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { mDateFormatUtil, mSensorPrivacyController, mDreamOverlayNotificationCountProvider, - mZenModeController); + mZenModeController, + mStatusBarWindowStateController); } @Test @@ -401,4 +411,41 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { verify(mView).showIcon( DreamOverlayStatusBarView.STATUS_ICON_PRIORITY_MODE_ON, false, null); } + + @Test + public void testStatusBarHiddenWhenSystemStatusBarShown() { + mController.onViewAttached(); + + final ArgumentCaptor<StatusBarWindowStateListener> + callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class); + verify(mStatusBarWindowStateController).addListener(callbackCapture.capture()); + callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_SHOWING); + + verify(mView).setVisibility(View.INVISIBLE); + } + + @Test + public void testStatusBarShownWhenSystemStatusBarHidden() { + mController.onViewAttached(); + + final ArgumentCaptor<StatusBarWindowStateListener> + callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class); + verify(mStatusBarWindowStateController).addListener(callbackCapture.capture()); + callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_HIDDEN); + + verify(mView).setVisibility(View.VISIBLE); + } + + @Test + public void testUnattachedStatusBarVisibilityUnchangedWhenSystemStatusBarHidden() { + mController.onViewAttached(); + mController.onViewDetached(); + + final ArgumentCaptor<StatusBarWindowStateListener> + callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class); + verify(mStatusBarWindowStateController).addListener(callbackCapture.capture()); + callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_SHOWING); + + verify(mView, never()).setVisibility(anyInt()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java index e175af7a037d..d0b3d6d2d2c7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java @@ -57,6 +57,8 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.Optional; + @SmallTest @RunWith(AndroidTestingRunner.class) public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { @@ -110,7 +112,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { mTouchHandler = new BouncerSwipeTouchHandler( mDisplayMetrics, mStatusBarKeyguardViewManager, - mCentralSurfaces, + Optional.of(mCentralSurfaces), mNotificationShadeWindowController, mValueAnimatorCreator, mVelocityTrackerFactory, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt index 86527d9558fd..8f967ab5294f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt @@ -38,7 +38,6 @@ import org.mockito.junit.MockitoJUnit private const val DEFAULT_COLOR = Color.RED private const val TARGET_COLOR = Color.BLUE -private const val BG_COLOR = Color.GREEN @SmallTest @RunWith(AndroidTestingRunner::class) @@ -66,8 +65,7 @@ class ColorSchemeTransitionTest : SysuiTestCase() { transitionFactory = { default, extractColor, applyColor -> mockTransition } whenever(extractColor.invoke(colorScheme)).thenReturn(TARGET_COLOR) - colorSchemeTransition = ColorSchemeTransition(context, - BG_COLOR, mediaViewHolder, transitionFactory) + colorSchemeTransition = ColorSchemeTransition(context, mediaViewHolder, transitionFactory) colorTransition = object : ColorTransition(DEFAULT_COLOR, extractColor, applyColor) { override fun buildAnimator(): ValueAnimator { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index 607a873a73d1..33db99342586 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -90,7 +90,6 @@ import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit private const val KEY = "TEST_KEY" -private const val BG_COLOR = Color.RED private const val PACKAGE = "PKG" private const val ARTIST = "ARTIST" private const val TITLE = "TITLE" @@ -171,8 +170,18 @@ public class MediaControlPanelTest : SysuiTestCase() { @Mock private lateinit var recommendationViewHolder: RecommendationViewHolder @Mock private lateinit var smartspaceAction: SmartspaceAction private lateinit var smartspaceData: SmartspaceMediaData - @Mock private lateinit var coverContainer: ViewGroup - private lateinit var coverItem: ImageView + @Mock private lateinit var coverContainer1: ViewGroup + @Mock private lateinit var coverContainer2: ViewGroup + @Mock private lateinit var coverContainer3: ViewGroup + private lateinit var coverItem1: ImageView + private lateinit var coverItem2: ImageView + private lateinit var coverItem3: ImageView + private lateinit var recTitle1: TextView + private lateinit var recTitle2: TextView + private lateinit var recTitle3: TextView + private lateinit var recSubtitle1: TextView + private lateinit var recSubtitle2: TextView + private lateinit var recSubtitle3: TextView @JvmField @Rule val mockito = MockitoJUnit.rule() @@ -237,7 +246,6 @@ public class MediaControlPanelTest : SysuiTestCase() { session.setActive(true) mediaData = MediaTestUtils.emptyMediaData.copy( - backgroundColor = BG_COLOR, artist = ARTIST, song = TITLE, packageName = PACKAGE, @@ -371,14 +379,30 @@ public class MediaControlPanelTest : SysuiTestCase() { * Initialize elements for the recommendation view holder */ private fun initRecommendationViewHolderMocks() { + recTitle1 = TextView(context) + recTitle2 = TextView(context) + recTitle3 = TextView(context) + recSubtitle1 = TextView(context) + recSubtitle2 = TextView(context) + recSubtitle3 = TextView(context) + whenever(recommendationViewHolder.recommendations).thenReturn(view) whenever(recommendationViewHolder.cardIcon).thenReturn(appIcon) // Add a recommendation item - coverItem = ImageView(context).also { it.setId(R.id.media_cover1) } - whenever(coverContainer.id).thenReturn(R.id.media_cover1_container) - whenever(recommendationViewHolder.mediaCoverItems).thenReturn(listOf(coverItem)) - whenever(recommendationViewHolder.mediaCoverContainers).thenReturn(listOf(coverContainer)) + coverItem1 = ImageView(context).also { it.setId(R.id.media_cover1) } + coverItem2 = ImageView(context).also { it.setId(R.id.media_cover2) } + coverItem3 = ImageView(context).also { it.setId(R.id.media_cover3) } + + whenever(recommendationViewHolder.mediaCoverItems) + .thenReturn(listOf(coverItem1, coverItem2, coverItem3)) + whenever(recommendationViewHolder.mediaCoverContainers) + .thenReturn(listOf(coverContainer1, coverContainer2, coverContainer3)) + whenever(recommendationViewHolder.mediaTitles) + .thenReturn(listOf(recTitle1, recTitle2, recTitle3)) + whenever(recommendationViewHolder.mediaSubtitles).thenReturn( + listOf(recSubtitle1, recSubtitle2, recSubtitle3) + ) // Long press menu whenever(recommendationViewHolder.settings).thenReturn(settings) @@ -391,7 +415,10 @@ public class MediaControlPanelTest : SysuiTestCase() { // Needed for card and item action click val mockContext = mock(Context::class.java) whenever(view.context).thenReturn(mockContext) - whenever(coverContainer.context).thenReturn(mockContext) + whenever(coverContainer1.context).thenReturn(mockContext) + whenever(coverContainer2.context).thenReturn(mockContext) + whenever(coverContainer3.context).thenReturn(mockContext) + } @After @@ -673,9 +700,11 @@ public class MediaControlPanelTest : SysuiTestCase() { MediaAction(icon, null, "custom 0", bg), MediaAction(icon, Runnable {}, "custom 1", bg) ) - val state = mediaData.copy(actions = actions, + val state = mediaData.copy( + actions = actions, actionsToShowInCompact = listOf(1, 2), - semanticActions = null) + semanticActions = null + ) player.attachPlayer(viewHolder) player.bindPlayer(state, PACKAGE) @@ -724,11 +753,14 @@ public class MediaControlPanelTest : SysuiTestCase() { val icon = context.getDrawable(R.drawable.ic_media_play) val bg = context.getDrawable(R.drawable.ic_media_play_container) val semanticActions0 = MediaButton( - playOrPause = MediaAction(mockAvd0, Runnable {}, "play", null)) + playOrPause = MediaAction(mockAvd0, Runnable {}, "play", null) + ) val semanticActions1 = MediaButton( - playOrPause = MediaAction(mockAvd1, Runnable {}, "pause", null)) + playOrPause = MediaAction(mockAvd1, Runnable {}, "pause", null) + ) val semanticActions2 = MediaButton( - playOrPause = MediaAction(mockAvd2, Runnable {}, "loading", null)) + playOrPause = MediaAction(mockAvd2, Runnable {}, "loading", null) + ) val state0 = mediaData.copy(semanticActions = semanticActions0) val state1 = mediaData.copy(semanticActions = semanticActions1) val state2 = mediaData.copy(semanticActions = semanticActions2) @@ -768,8 +800,8 @@ public class MediaControlPanelTest : SysuiTestCase() { assertThat(actionPlayPause.getBackground()).isNull() verify(mockAvd0, times(1)) .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java)) - verify(mockAvd1, times(1) - ).registerAnimationCallback(any(Animatable2.AnimationCallback::class.java)) + verify(mockAvd1, times(1)) + .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java)) verify(mockAvd2, times(1)) .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java)) verify(mockAvd0, times(1)) @@ -1219,12 +1251,70 @@ public class MediaControlPanelTest : SysuiTestCase() { player.attachRecommendation(recommendationViewHolder) player.bindRecommendation(smartspaceData) - verify(coverContainer).setOnClickListener(captor.capture()) + verify(coverContainer1).setOnClickListener(captor.capture()) captor.value.onClick(recommendationViewHolder.recommendations) verify(logger).logRecommendationItemTap(eq(PACKAGE), eq(instanceId), eq(0)) } + @Test + fun bindRecommendation_hasTitlesAndSubtitles() { + player.attachRecommendation(recommendationViewHolder) + + val title1 = "Title1" + val title2 = "Title2" + val title3 = "Title3" + val subtitle1 = "Subtitle1" + val subtitle2 = "Subtitle2" + val subtitle3 = "Subtitle3" + + val data = smartspaceData.copy( + recommendations = listOf( + SmartspaceAction.Builder("id1", title1) + .setSubtitle(subtitle1) + .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id2", title2) + .setSubtitle(subtitle2) + .setIcon(Icon.createWithResource(context, R.drawable.ic_alarm)) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id3", title3) + .setSubtitle(subtitle3) + .setIcon(Icon.createWithResource(context, R.drawable.ic_3g_mobiledata)) + .setExtras(Bundle.EMPTY) + .build() + ) + ) + player.bindRecommendation(data) + + assertThat(recTitle1.text).isEqualTo(title1) + assertThat(recTitle2.text).isEqualTo(title2) + assertThat(recTitle3.text).isEqualTo(title3) + assertThat(recSubtitle1.text).isEqualTo(subtitle1) + assertThat(recSubtitle2.text).isEqualTo(subtitle2) + assertThat(recSubtitle3.text).isEqualTo(subtitle3) + } + + @Test + fun bindRecommendation_noTitle_subtitleNotShown() { + player.attachRecommendation(recommendationViewHolder) + + val data = smartspaceData.copy( + recommendations = listOf( + SmartspaceAction.Builder("id1", "") + .setSubtitle("fake subtitle") + .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)) + .setExtras(Bundle.EMPTY) + .build() + ) + ) + player.bindRecommendation(data) + + assertThat(recSubtitle1.text).isEqualTo("") + } + private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener = withArgCaptor { verify(seekBarViewModel).setScrubbingChangeListener(capture()) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java index eacec20cac12..3e335c5163a9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java @@ -57,7 +57,6 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { private static final String OLD_KEY = "TEST_KEY_OLD"; private static final String APP = "APP"; private static final String PACKAGE = "PKG"; - private static final int BG_COLOR = Color.RED; private static final String ARTIST = "ARTIST"; private static final String TITLE = "TITLE"; private static final String DEVICE_NAME = "DEVICE_NAME"; @@ -76,7 +75,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { mManager.addListener(mListener); mMediaData = new MediaData( - USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, + USER_ID, true, APP, null, ARTIST, TITLE, null, new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null, MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L, InstanceId.fakeInstanceId(-1), -1); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt index 7ec31a7ae829..0cbceb6700b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -277,6 +277,49 @@ class MediaDataManagerTest : SysuiTestCase() { } @Test + fun testLoadMediaDataInBg_invalidTokenNoCrash() { + val bundle = Bundle() + // wrong data type + bundle.putParcelable(Notification.EXTRA_MEDIA_SESSION, Bundle()) + val rcn = SbnBuilder().run { + setPkg(SYSTEM_PACKAGE_NAME) + modifyNotification(context).also { + it.setSmallIcon(android.R.drawable.ic_media_pause) + it.addExtras(bundle) + it.setStyle(MediaStyle().apply { + setRemotePlaybackInfo("Remote device", 0, null) + }) + } + build() + } + + mediaDataManager.loadMediaDataInBg(KEY, rcn, null) + // no crash even though the data structure is incorrect + } + + @Test + fun testLoadMediaDataInBg_invalidMediaRemoteIntentNoCrash() { + val bundle = Bundle() + // wrong data type + bundle.putParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT, Bundle()) + val rcn = SbnBuilder().run { + setPkg(SYSTEM_PACKAGE_NAME) + modifyNotification(context).also { + it.setSmallIcon(android.R.drawable.ic_media_pause) + it.addExtras(bundle) + it.setStyle(MediaStyle().apply { + setMediaSession(session.sessionToken) + setRemotePlaybackInfo("Remote device", 0, null) + }) + } + build() + } + + mediaDataManager.loadMediaDataInBg(KEY, rcn, null) + // no crash even though the data structure is incorrect + } + + @Test fun testOnNotificationRemoved_callsListener() { addNotificationAndLoad() val data = mediaDataCaptor.value @@ -467,7 +510,6 @@ class MediaDataManagerTest : SysuiTestCase() { cardAction = mediaSmartspaceBaseAction, recommendations = listOf(mediaRecommendationItem), dismissIntent = DISMISS_INTENT, - backgroundColor = 0, headphoneConnectionTimeMillis = 1234L, instanceId = InstanceId.fakeInstanceId(instanceId))), eq(false)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt index c7ef94eb6a64..ae58fe67da23 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt @@ -7,7 +7,6 @@ class MediaTestUtils { val emptyMediaData = MediaData( userId = 0, initialized = true, - backgroundColor = 0, app = null, appIcon = null, artist = null, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SquigglyProgressTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SquigglyProgressTest.kt index e3cd90b13422..d087b0fe4413 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/SquigglyProgressTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/SquigglyProgressTest.kt @@ -19,7 +19,7 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock -import org.mockito.Mockito.anyFloat +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit @@ -35,8 +35,7 @@ class SquigglyProgressTest : SysuiTestCase() { lateinit var squigglyProgress: SquigglyProgress @Mock lateinit var canvas: Canvas - @Captor lateinit var wavePaintCaptor: ArgumentCaptor<Paint> - @Captor lateinit var linePaintCaptor: ArgumentCaptor<Paint> + @Captor lateinit var paintCaptor: ArgumentCaptor<Paint> @JvmField @Rule val mockitoRule = MockitoJUnit.rule() @Before @@ -53,9 +52,7 @@ class SquigglyProgressTest : SysuiTestCase() { fun testDrawPathAndLine() { squigglyProgress.draw(canvas) - verify(canvas).drawPath(any(), wavePaintCaptor.capture()) - verify(canvas).drawLine(anyFloat(), anyFloat(), anyFloat(), anyFloat(), - linePaintCaptor.capture()) + verify(canvas, times(2)).drawPath(any(), paintCaptor.capture()) } @Test @@ -69,12 +66,11 @@ class SquigglyProgressTest : SysuiTestCase() { fun testStrokeWidth() { squigglyProgress.draw(canvas) - verify(canvas).drawPath(any(), wavePaintCaptor.capture()) - verify(canvas).drawLine(anyFloat(), anyFloat(), anyFloat(), anyFloat(), - linePaintCaptor.capture()) + verify(canvas, times(2)).drawPath(any(), paintCaptor.capture()) + val (wavePaint, linePaint) = paintCaptor.getAllValues() - assertThat(wavePaintCaptor.value.strokeWidth).isEqualTo(strokeWidth) - assertThat(linePaintCaptor.value.strokeWidth).isEqualTo(strokeWidth) + assertThat(wavePaint.strokeWidth).isEqualTo(strokeWidth) + assertThat(linePaint.strokeWidth).isEqualTo(strokeWidth) } @Test @@ -82,13 +78,12 @@ class SquigglyProgressTest : SysuiTestCase() { squigglyProgress.alpha = alpha squigglyProgress.draw(canvas) - verify(canvas).drawPath(any(), wavePaintCaptor.capture()) - verify(canvas).drawLine(anyFloat(), anyFloat(), anyFloat(), anyFloat(), - linePaintCaptor.capture()) + verify(canvas, times(2)).drawPath(any(), paintCaptor.capture()) + val (wavePaint, linePaint) = paintCaptor.getAllValues() assertThat(squigglyProgress.alpha).isEqualTo(alpha) - assertThat(wavePaintCaptor.value.alpha).isEqualTo(alpha) - assertThat(linePaintCaptor.value.alpha).isEqualTo((alpha / 255f * DISABLED_ALPHA).toInt()) + assertThat(wavePaint.alpha).isEqualTo(alpha) + assertThat(linePaint.alpha).isEqualTo((alpha / 255f * DISABLED_ALPHA).toInt()) } @Test @@ -96,12 +91,11 @@ class SquigglyProgressTest : SysuiTestCase() { squigglyProgress.colorFilter = colorFilter squigglyProgress.draw(canvas) - verify(canvas).drawPath(any(), wavePaintCaptor.capture()) - verify(canvas).drawLine(anyFloat(), anyFloat(), anyFloat(), anyFloat(), - linePaintCaptor.capture()) + verify(canvas, times(2)).drawPath(any(), paintCaptor.capture()) + val (wavePaint, linePaint) = paintCaptor.getAllValues() - assertThat(wavePaintCaptor.value.colorFilter).isEqualTo(colorFilter) - assertThat(linePaintCaptor.value.colorFilter).isEqualTo(colorFilter) + assertThat(wavePaint.colorFilter).isEqualTo(colorFilter) + assertThat(linePaint.colorFilter).isEqualTo(colorFilter) } @Test @@ -109,12 +103,11 @@ class SquigglyProgressTest : SysuiTestCase() { squigglyProgress.setTint(tint) squigglyProgress.draw(canvas) - verify(canvas).drawPath(any(), wavePaintCaptor.capture()) - verify(canvas).drawLine(anyFloat(), anyFloat(), anyFloat(), anyFloat(), - linePaintCaptor.capture()) + verify(canvas, times(2)).drawPath(any(), paintCaptor.capture()) + val (wavePaint, linePaint) = paintCaptor.getAllValues() - assertThat(wavePaintCaptor.value.color).isEqualTo(tint) - assertThat(linePaintCaptor.value.color).isEqualTo( + assertThat(wavePaint.color).isEqualTo(tint) + assertThat(linePaint.color).isEqualTo( ColorUtils.setAlphaComponent(tint, DISABLED_ALPHA)) } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java index cf97bdae9af2..616f89455c74 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java @@ -21,12 +21,15 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; import android.widget.LinearLayout; +import android.widget.Switch; import android.widget.TextView; import androidx.recyclerview.widget.RecyclerView; import androidx.test.filters.SmallTest; +import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.logging.UiEventLogger; +import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -42,6 +45,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.mockito.MockitoSession; import java.util.List; @@ -79,12 +83,16 @@ public class InternetDialogTest extends SysuiTestCase { private LinearLayout mEthernet; private LinearLayout mMobileDataToggle; private LinearLayout mWifiToggle; + private Switch mWifiToggleSwitch; + private TextView mWifiToggleSummary; private LinearLayout mConnectedWifi; private RecyclerView mWifiList; private LinearLayout mSeeAll; private LinearLayout mWifiScanNotify; private TextView mAirplaneModeSummaryText; + private MockitoSession mMockitoSession; + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -101,6 +109,15 @@ public class InternetDialogTest extends SysuiTestCase { .thenReturn(MOBILE_NETWORK_SUMMARY); when(mInternetDialogController.getWifiManager()).thenReturn(mWifiManager); + mMockitoSession = ExtendedMockito.mockitoSession() + .spyStatic(WifiEnterpriseRestrictionUtils.class) + .startMocking(); + when(WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(mContext)).thenReturn(true); + + createInternetDialog(); + } + + private void createInternetDialog() { mInternetDialog = new InternetDialog(mContext, mock(InternetDialogFactory.class), mInternetDialogController, true, true, true, mock(UiEventLogger.class), mHandler, mBgExecutor, mKeyguard); @@ -114,6 +131,8 @@ public class InternetDialogTest extends SysuiTestCase { mEthernet = mDialogView.requireViewById(R.id.ethernet_layout); mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_network_layout); mWifiToggle = mDialogView.requireViewById(R.id.turn_on_wifi_layout); + mWifiToggleSwitch = mDialogView.requireViewById(R.id.wifi_toggle); + mWifiToggleSummary = mDialogView.requireViewById(R.id.wifi_toggle_summary); mConnectedWifi = mDialogView.requireViewById(R.id.wifi_connected_layout); mWifiList = mDialogView.requireViewById(R.id.wifi_list_layout); mSeeAll = mDialogView.requireViewById(R.id.see_all_layout); @@ -124,6 +143,7 @@ public class InternetDialogTest extends SysuiTestCase { @After public void tearDown() { mInternetDialog.dismissDialog(); + mMockitoSession.finishMocking(); } @Test @@ -411,6 +431,33 @@ public class InternetDialogTest extends SysuiTestCase { } @Test + public void updateDialog_disallowChangeWifiState_disableWifiSwitch() { + mInternetDialog.dismissDialog(); + when(WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(mContext)).thenReturn(false); + createInternetDialog(); + + mInternetDialog.updateDialog(false); + + // Disable Wi-Fi switch and show restriction message in summary. + assertThat(mWifiToggleSwitch.isEnabled()).isFalse(); + assertThat(mWifiToggleSummary.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mWifiToggleSummary.getText().length()).isNotEqualTo(0); + } + + @Test + public void updateDialog_allowChangeWifiState_enableWifiSwitch() { + mInternetDialog.dismissDialog(); + when(WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(mContext)).thenReturn(true); + createInternetDialog(); + + mInternetDialog.updateDialog(false); + + // Enable Wi-Fi switch and hide restriction message in summary. + assertThat(mWifiToggleSwitch.isEnabled()).isTrue(); + assertThat(mWifiToggleSummary.getVisibility()).isEqualTo(View.GONE); + } + + @Test public void updateDialog_wifiOn_hideWifiScanNotify() { // The preconditions WiFi ON and WiFi entries are already in setUp() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java index 03c22b32b8f6..1b1f4e41f200 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java @@ -32,6 +32,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import android.app.Notification; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; @@ -41,6 +42,7 @@ import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Icon; +import android.os.Bundle; import android.os.UserHandle; import android.service.notification.StatusBarNotification; @@ -170,4 +172,16 @@ public class StatusBarIconViewTest extends SysuiTestCase { mIconView.getIcon(largeIcon); // No crash? good } + + @Test + public void testContentDescForNotification_invalidAi_noCrash() { + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(0) + .build(); + // should be ApplicationInfo + n.extras.putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, new Bundle()); + StatusBarIconView.contentDescForNotification(mContext, n); + + // no crash, good + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java index 241451edf698..5804ad425267 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java @@ -229,6 +229,41 @@ public class NotificationEntryTest extends SysuiTestCase { assertTrue(entry.isLastMessageFromReply()); } + @Test + public void notificationDataEntry_testIsLastMessageFromReply_invalidPerson_noCrash() { + Person.Builder person = new Person.Builder() + .setName("name") + .setKey("abc") + .setUri("uri") + .setBot(true); + + Bundle bundle = new Bundle(); + // should be Person.class + bundle.putParcelable(Notification.EXTRA_MESSAGING_PERSON, new Bundle()); + Bundle[] messagesBundle = new Bundle[]{new Notification.MessagingStyle.Message( + "text", 0, person.build()).toBundle()}; + bundle.putParcelableArray(Notification.EXTRA_MESSAGES, messagesBundle); + + Notification notification = new Notification.Builder(mContext, "test") + .addExtras(bundle) + .build(); + + NotificationEntry entry = new NotificationEntryBuilder() + .setPkg("pkg") + .setOpPkg("pkg") + .setTag("tag") + .setNotification(notification) + .setUser(mContext.getUser()) + .setOverrideGroupKey("") + .build(); + entry.setHasSentReply(); + + entry.isLastMessageFromReply(); + + // no crash, good + } + + private Notification.Action createContextualAction(String title) { return new Notification.Action.Builder( Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java new file mode 100644 index 000000000000..509ba4194e5e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java @@ -0,0 +1,76 @@ +/* + * 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.row.wrapper; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.app.Notification; +import android.content.Context; +import android.graphics.drawable.Icon; +import android.os.Bundle; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.testing.TestableLooper.RunWithLooper; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidTestingRunner.class) +@SmallTest +@RunWithLooper +public class NotificationBigPictureTemplateViewWrapperTest extends SysuiTestCase { + + private View mView; + private ExpandableNotificationRow mRow; + + + @Before + public void setup() throws Exception { + allowTestableLooperAsMainThread(); + NotificationTestHelper helper = new NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)); + mView = LayoutInflater.from(mContext).inflate( + com.android.internal.R.layout.notification_template_material_big_picture, null); + mRow = helper.createRow(); + } + + @Test + public void invalidLargeIconBig_noCrash() { + NotificationViewWrapper wrapper = new NotificationBigPictureTemplateViewWrapper( + mContext, mView, mRow); + // should be Icon.class + mRow.getEntry().getSbn().getNotification().setSmallIcon( + Icon.createWithResource(mContext, 0)); + mRow.getEntry().getSbn().getNotification().extras.putParcelable( + Notification.EXTRA_LARGE_ICON_BIG, new Bundle()); + wrapper.onContentUpdated(mRow); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt index 5d160366b27d..4270d72770dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt @@ -144,7 +144,7 @@ class NotificationShelfTest : SysuiTestCase() { } private fun setFractionToShade(fraction: Float) { - shelf.setFractionToShade(fraction) + whenever(ambientState.fractionToShade).thenReturn(fraction) } private fun setOnLockscreen(isOnLockscreen: Boolean) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt index 968e16aab14e..c3658ba11b2d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt @@ -23,6 +23,8 @@ import android.view.View.VISIBLE import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.LockscreenShadeTransitionController +import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow @@ -44,7 +46,7 @@ import org.mockito.MockitoAnnotations class NotificationStackSizeCalculatorTest : SysuiTestCase() { @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController - + @Mock private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController @Mock private lateinit var stackLayout: NotificationStackScrollLayout private val testableResources = mContext.getOrCreateTestableResources() @@ -63,6 +65,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { sizeCalculator = NotificationStackSizeCalculator( statusBarStateController = sysuiStatusBarStateController, + lockscreenShadeTransitionController = lockscreenShadeTransitionController, testableResources.resources) } @@ -155,6 +158,55 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { assertThat(height).isAtMost(availableSpace) } + @Test + fun onLockscreen_onKeyguard_AndNotGoingToShade_returnsTrue() { + whenever(sysuiStatusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) + whenever(lockscreenShadeTransitionController.fractionToShade).thenReturn(0f) + assertThat(sizeCalculator.onLockscreen()).isTrue() + } + + @Test + fun onLockscreen_goingToShade_returnsFalse() { + whenever(sysuiStatusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) + whenever(lockscreenShadeTransitionController.fractionToShade).thenReturn(0.5f) + assertThat(sizeCalculator.onLockscreen()).isFalse() + } + + @Test + fun onLockscreen_notOnLockscreen_returnsFalse() { + whenever(sysuiStatusBarStateController.state).thenReturn(StatusBarState.SHADE) + whenever(lockscreenShadeTransitionController.fractionToShade).thenReturn(1f) + assertThat(sizeCalculator.onLockscreen()).isFalse() + } + + @Test + fun spaceNeeded_onLockscreen_usesMinHeight() { + setGapHeight(0f) + // No divider height since we're testing one element where index = 0 + + val expandableView = createMockRow(rowHeight) + whenever(expandableView.getMinHeight(any())).thenReturn(5) + whenever(expandableView.intrinsicHeight).thenReturn(10) + + val space = sizeCalculator.spaceNeeded(expandableView, visibleIndex = 0, + previousView = null, stack = stackLayout, onLockscreen = true) + assertThat(space).isEqualTo(5) + } + + @Test + fun spaceNeeded_notOnLockscreen_usesIntrinsicHeight() { + setGapHeight(0f) + // No divider height since we're testing one element where index = 0 + + val expandableView = createMockRow(rowHeight) + whenever(expandableView.getMinHeight(any())).thenReturn(5) + whenever(expandableView.intrinsicHeight).thenReturn(10) + + val space = sizeCalculator.spaceNeeded(expandableView, visibleIndex = 0, + previousView = null, stack = stackLayout, onLockscreen = false) + assertThat(space).isEqualTo(10) + } + private fun computeMaxKeyguardNotifications( rows: List<ExpandableView>, availableSpace: Float, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt index 6b4dc5883f08..1f90d0cf9494 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt @@ -1,5 +1,6 @@ package com.android.systemui.statusbar.notification.stack +import android.annotation.DimenRes import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.systemui.R @@ -31,6 +32,14 @@ class StackScrollAlgorithmTest : SysuiTestCase() { mStatusBarKeyguardViewManager ) + private val testableResources = mContext.orCreateTestableResources + + private fun px(@DimenRes id: Int): Float = + testableResources.resources.getDimensionPixelSize(id).toFloat() + + private val bigGap = px(R.dimen.notification_section_divider_height) + private val smallGap = px(R.dimen.notification_section_divider_height_lockscreen) + @Before fun setUp() { whenever(notificationRow.viewState).thenReturn(expandableViewState) @@ -80,4 +89,25 @@ class StackScrollAlgorithmTest : SysuiTestCase() { val centeredY = ambientState.stackY + fullHeight / 2f - emptyShadeView.height / 2f assertThat(emptyShadeView.viewState?.yTranslation).isEqualTo(centeredY) } + + @Test + fun getGapForLocation_onLockscreen_returnsSmallGap() { + val gap = stackScrollAlgorithm.getGapForLocation( + /* fractionToShade= */ 0f, /* onKeyguard= */ true) + assertThat(gap).isEqualTo(smallGap) + } + + @Test + fun getGapForLocation_goingToShade_interpolatesGap() { + val gap = stackScrollAlgorithm.getGapForLocation( + /* fractionToShade= */ 0.5f, /* onKeyguard= */ true) + assertThat(gap).isEqualTo(smallGap * 0.5f + bigGap * 0.5f) + } + + @Test + fun getGapForLocation_notOnLockscreen_returnsBigGap() { + val gap = stackScrollAlgorithm.getGapForLocation( + /* fractionToShade= */ 0f, /* onKeyguard= */ false) + assertThat(gap).isEqualTo(bigGap) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java index 09773d31de41..05fb1f5959ea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java @@ -80,6 +80,7 @@ import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent; import com.android.keyguard.dagger.KeyguardStatusBarViewComponent; import com.android.keyguard.dagger.KeyguardStatusViewComponent; import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; +import com.android.systemui.DejankUtils; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; @@ -352,6 +353,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mKeyguardStatusView = new KeyguardStatusView(mContext); mKeyguardStatusView.setId(R.id.keyguard_status_view); + DejankUtils.setImmediate(true); when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false); when(mHeadsUpCallback.getContext()).thenReturn(mContext); @@ -942,6 +944,29 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test + public void testUnlockHintAnimation_runs_whenNotInPowerSaveMode_andDozeAmountIsZero() { + when(mPowerManager.isPowerSaveMode()).thenReturn(false); + when(mAmbientState.getDozeAmount()).thenReturn(0f); + mNotificationPanelViewController.startUnlockHintAnimation(); + assertThat(mNotificationPanelViewController.mHintAnimationRunning).isTrue(); + } + + @Test + public void testUnlockHintAnimation_doesNotRun_inPowerSaveMode() { + when(mPowerManager.isPowerSaveMode()).thenReturn(true); + mNotificationPanelViewController.startUnlockHintAnimation(); + assertThat(mNotificationPanelViewController.mHintAnimationRunning).isFalse(); + } + + @Test + public void testUnlockHintAnimation_doesNotRun_whenDozeAmountNotZero() { + when(mPowerManager.isPowerSaveMode()).thenReturn(false); + when(mAmbientState.getDozeAmount()).thenReturn(0.5f); + mNotificationPanelViewController.startUnlockHintAnimation(); + assertThat(mNotificationPanelViewController.mHintAnimationRunning).isFalse(); + } + + @Test public void setKeyguardStatusBarAlpha_setsAlphaOnKeyguardStatusBarController() { float statusBarAlpha = 0.5f; diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java index 2c461ae1b598..3032ff1fe61f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java @@ -104,7 +104,7 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { public void setup() throws Exception { MockitoAnnotations.initMocks(this); mManager = new ThemeOverlayApplier(mOverlayManager, - MoreExecutors.directExecutor(), MoreExecutors.directExecutor(), + MoreExecutors.directExecutor(), LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager) { @Override protected OverlayManagerTransaction.Builder getTransactionBuilder() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt index 7ac243452222..05a8f0a35e28 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt @@ -78,6 +78,9 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) + overrideResource( + com.android.internal.R.integer.config_unfoldTransitionHalfFoldedTimeout, + HALF_OPENED_TIMEOUT_MILLIS.toInt()) deviceStates = FoldableTestUtils.findDeviceStates(context) foldStateProvider = @@ -319,4 +322,8 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { private fun sendHingeAngleEvent(angle: Int) { hingeAngleCaptor.value.accept(angle.toFloat()) } + + companion object { + private const val HALF_OPENED_TIMEOUT_MILLIS = 300L + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt index 3d554880ed58..b30c20db642d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt @@ -21,11 +21,16 @@ import android.hardware.usb.IUsbSerialReader import android.hardware.usb.UsbAccessory import android.hardware.usb.UsbManager import android.testing.AndroidTestingRunner +import android.testing.TestableLooper import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule +import androidx.test.runner.intercepting.SingleActivityFactory import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat + +import javax.inject.Inject + import org.junit.After import org.junit.Before import org.junit.Rule @@ -37,14 +42,26 @@ import org.junit.runner.RunWith */ @RunWith(AndroidTestingRunner::class) @SmallTest +@TestableLooper.RunWithLooper class UsbPermissionActivityTest : SysuiTestCase() { - class UsbPermissionActivityTestable : UsbPermissionActivity() + private var mMessage: UsbAudioWarningDialogMessage = UsbAudioWarningDialogMessage() + + open class UsbPermissionActivityTestable @Inject constructor ( + val message: UsbAudioWarningDialogMessage + ) + : UsbPermissionActivity(UsbAudioWarningDialogMessage()) @Rule @JvmField var activityRule = ActivityTestRule<UsbPermissionActivityTestable>( - UsbPermissionActivityTestable::class.java, false, false) + object : SingleActivityFactory<UsbPermissionActivityTestable>( + UsbPermissionActivityTestable::class.java + ) { + override fun create(intent: Intent?): UsbPermissionActivityTestable { + return UsbPermissionActivityTestable(mMessage) + } + }, false, false) private val activityIntent = Intent(mContext, UsbPermissionActivityTestable::class.java) .apply { @@ -72,6 +89,7 @@ class UsbPermissionActivityTest : SysuiTestCase() { @Before fun setUp() { + UsbPermissionActivityTestable(mMessage) activityRule.launchActivity(activityIntent) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java index 3ee1a27dc69a..8f934335e93b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakDetectorTest.java @@ -52,6 +52,8 @@ public class LeakDetectorTest extends SysuiTestCase { private Object mObject; private Collection<?> mCollection; + + private CollectionWaiter trackObjectWith(Consumer<Object> tracker) { mObject = new Object(); CollectionWaiter collectionWaiter = ReferenceTestUtils.createCollectionWaiter(mObject); @@ -69,7 +71,9 @@ public class LeakDetectorTest extends SysuiTestCase { @Before public void setup() { - mLeakDetector = LeakDetector.create(Mockito.mock(DumpManager.class)); + TrackedCollections collections = new TrackedCollections(); + mLeakDetector = new LeakDetector(collections, new TrackedGarbage(collections), + new TrackedObjects(collections), Mockito.mock(DumpManager.class)); // Note: Do not try to factor out object / collection waiter creation. The optimizer will // try and cache accesses to fields and thus create a GC root for the duration of the test diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java index 01769e52c8d3..b1950eac9846 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java @@ -20,6 +20,7 @@ import static android.view.View.GONE; import static android.view.View.VISIBLE; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -323,6 +324,7 @@ public class WalletScreenControllerTest extends SysuiTestCase { assertEquals(GONE, mWalletView.getCardCarousel().getVisibility()); assertEquals(VISIBLE, mWalletView.getEmptyStateView().getVisibility()); assertEquals(GONE, mWalletView.getErrorView().getVisibility()); + assertTrue(mWalletView.getAppButton().hasOnClickListeners()); } @Test diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index ac0944be9739..b4c107c1a2c1 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -671,6 +671,9 @@ public class CompanionDeviceManagerService extends SystemService { association = AssociationInfo.builder(association) .setNotifyOnDeviceNearby(active) .build(); + // Do not need to call {@link BleCompanionDeviceScanner#restartScan()} since it will + // trigger {@link BleCompanionDeviceScanner#restartScan(int, AssociationInfo)} when + // an application sets/unsets the mNotifyOnDeviceNearby flag. mAssociationStore.updateAssociation(association); // TODO(b/218615198): correctly handle the case when the device is currently present. diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java index 33301b180619..ad09f7cd3dde 100644 --- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java +++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java @@ -194,6 +194,8 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { // Collect MAC addresses from all associations. final Set<String> macAddresses = new HashSet<>(); for (AssociationInfo association : mAssociationStore.getAssociations()) { + if (!association.isNotifyOnDeviceNearby()) continue; + // Beware that BT stack does not consider low-case MAC addresses valid, while // MacAddress.toString() return a low-case String. final String macAddress = association.getDeviceMacAddressAsString(); diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java index 1f8ef8226c32..877ee8218ea6 100644 --- a/services/core/java/com/android/server/BinaryTransparencyService.java +++ b/services/core/java/com/android/server/BinaryTransparencyService.java @@ -441,6 +441,7 @@ public class BinaryTransparencyService extends SystemService { final JobInfo jobInfo = new JobInfo.Builder(COMPUTE_APEX_MODULE_SHA256_JOB_ID, new ComponentName(context, UpdateMeasurementsJobService.class)) .setRequiresDeviceIdle(true) + .setRequiresCharging(true) .build(); if (jobScheduler.schedule(jobInfo) != JobScheduler.RESULT_SUCCESS) { Slog.e(TAG, "Failed to schedule job to update binary measurements."); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 35f7e064e358..7cee203d0bf0 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -15093,7 +15093,7 @@ public class ActivityManagerService extends IActivityManager.Stub @GuardedBy("this") final boolean canGcNowLocked() { for (BroadcastQueue q : mBroadcastQueues) { - if (!q.mParallelBroadcasts.isEmpty() || !q.mDispatcher.isEmpty()) { + if (!q.mParallelBroadcasts.isEmpty() || !q.mDispatcher.isIdle()) { return false; } } diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java index 90201a033668..8c42d4d1bd5a 100644 --- a/services/core/java/com/android/server/am/AppBatteryTracker.java +++ b/services/core/java/com/android/server/am/AppBatteryTracker.java @@ -266,22 +266,16 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> } FrameworkStatsLog.write(FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO, uid, - FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__RESTRICTION_LEVEL__LEVEL_UNKNOWN, - FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__THRESHOLD__THRESHOLD_UNKNOWN, - FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__TRACKER__UNKNOWN_TRACKER, + AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN, // RestrictionLevel + AppBackgroundRestrictionsInfo.THRESHOLD_UNKNOWN, + AppBackgroundRestrictionsInfo.UNKNOWN_TRACKER, null /*byte[] fgs_tracker_info*/, getBatteryTrackerInfoProtoLocked(uid) /*byte[] battery_tracker_info*/, null /*byte[] broadcast_events_tracker_info*/, null /*byte[] bind_service_events_tracker_info*/, - FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_UNKNOWN, - FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__OPT_LEVEL__UNKNOWN, - FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_UNKNOWN, + AppBackgroundRestrictionsInfo.REASON_UNKNOWN, // ExemptionReason + AppBackgroundRestrictionsInfo.UNKNOWN, // OptimizationLevel + AppBackgroundRestrictionsInfo.SDK_UNKNOWN, // TargetSdk isLowRamDeviceStatic()); } } diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java index 635d86c69985..d70404fb6a65 100644 --- a/services/core/java/com/android/server/am/AppRestrictionController.java +++ b/services/core/java/com/android/server/am/AppRestrictionController.java @@ -116,6 +116,7 @@ import android.content.pm.ServiceInfo.ForegroundServiceType; import android.database.ContentObserver; import android.graphics.drawable.Icon; import android.net.Uri; +import android.os.AppBackgroundRestrictionsInfo; import android.os.Build; import android.os.Environment; import android.os.Handler; @@ -123,7 +124,6 @@ import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; -import android.os.PowerExemptionManager.ExemptionReason; import android.os.PowerExemptionManager.ReasonCode; import android.os.RemoteException; import android.os.SystemClock; @@ -1095,6 +1095,14 @@ public final class AppRestrictionController { DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "restriction_exempted_packages"; /** + * Whether or not to show the notification for abusive apps, i.e. when the system + * detects it's draining significant amount of battery in the background. + * {@code true} - we'll show the prompt to user, {@code false} - we'll not show it. + */ + static final String KEY_BG_PROMPT_ABUSIVE_APPS_TO_BG_RESTRICTED = + DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "prompt_abusive_apps_to_bg_restricted"; + + /** * Default value to {@link #mBgAutoRestrictedBucket}. */ static final boolean DEFAULT_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION = false; @@ -1119,6 +1127,11 @@ public final class AppRestrictionController { */ final boolean mDefaultBgPromptFgsWithNotiToBgRestricted; + /** + * Default value to {@link #mBgPromptAbusiveAppsToBgRestricted}. + */ + final boolean mDefaultBgPromptAbusiveAppToBgRestricted; + volatile boolean mBgAutoRestrictedBucket; volatile boolean mRestrictedBucketEnabled; @@ -1144,10 +1157,17 @@ public final class AppRestrictionController { */ volatile boolean mBgPromptFgsWithNotiOnLongRunning; + /** + * @see #KEY_BG_PROMPT_ABUSIVE_APPS_TO_BG_RESTRICTED. + */ + volatile boolean mBgPromptAbusiveAppsToBgRestricted; + ConstantsObserver(Handler handler, Context context) { super(handler); mDefaultBgPromptFgsWithNotiToBgRestricted = context.getResources().getBoolean( com.android.internal.R.bool.config_bg_prompt_fgs_with_noti_to_bg_restricted); + mDefaultBgPromptAbusiveAppToBgRestricted = context.getResources().getBoolean( + com.android.internal.R.bool.config_bg_prompt_abusive_apps_to_bg_restricted); } @Override @@ -1172,6 +1192,9 @@ public final class AppRestrictionController { case KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_ON_LONG_RUNNING: updateBgPromptFgsWithNotiOnLongRunning(); break; + case KEY_BG_PROMPT_ABUSIVE_APPS_TO_BG_RESTRICTED: + updateBgPromptAbusiveAppToBgRestricted(); + break; case KEY_BG_RESTRICTION_EXEMPTED_PACKAGES: updateBgRestrictionExemptedPackages(); break; @@ -1209,6 +1232,7 @@ public final class AppRestrictionController { updateBgLongFgsNotificationMinimalInterval(); updateBgPromptFgsWithNotiToBgRestricted(); updateBgPromptFgsWithNotiOnLongRunning(); + updateBgPromptAbusiveAppToBgRestricted(); updateBgRestrictionExemptedPackages(); } @@ -1251,6 +1275,13 @@ public final class AppRestrictionController { DEFAULT_BG_PROMPT_FGS_WITH_NOTIFICATION_ON_LONG_RUNNING); } + private void updateBgPromptAbusiveAppToBgRestricted() { + mBgPromptAbusiveAppsToBgRestricted = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_BG_PROMPT_ABUSIVE_APPS_TO_BG_RESTRICTED, + mDefaultBgPromptAbusiveAppToBgRestricted); + } + private void updateBgRestrictionExemptedPackages() { final String settings = DeviceConfig.getString( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, @@ -1290,6 +1321,10 @@ public final class AppRestrictionController { pw.print('='); pw.println(mBgPromptFgsWithNotiToBgRestricted); pw.print(prefix); + pw.print(KEY_BG_PROMPT_ABUSIVE_APPS_TO_BG_RESTRICTED); + pw.print('='); + pw.println(mBgPromptAbusiveAppsToBgRestricted); + pw.print(prefix); pw.print(KEY_BG_RESTRICTION_EXEMPTED_PACKAGES); pw.print('='); pw.println(mBgRestrictionExemptedPackages.toString()); @@ -1881,76 +1916,59 @@ public final class AppRestrictionController { private int getRestrictionLevelStatsd(@RestrictionLevel int level) { switch (level) { case RESTRICTION_LEVEL_UNKNOWN: - return FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__RESTRICTION_LEVEL__LEVEL_UNKNOWN; + return AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN; case RESTRICTION_LEVEL_UNRESTRICTED: - return FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__RESTRICTION_LEVEL__LEVEL_UNRESTRICTED; + return AppBackgroundRestrictionsInfo.LEVEL_UNRESTRICTED; case RESTRICTION_LEVEL_EXEMPTED: - return FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__RESTRICTION_LEVEL__LEVEL_EXEMPTED; + return AppBackgroundRestrictionsInfo.LEVEL_EXEMPTED; case RESTRICTION_LEVEL_ADAPTIVE_BUCKET: - return FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__RESTRICTION_LEVEL__LEVEL_ADAPTIVE_BUCKET; + return AppBackgroundRestrictionsInfo.LEVEL_ADAPTIVE_BUCKET; case RESTRICTION_LEVEL_RESTRICTED_BUCKET: - return FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__RESTRICTION_LEVEL__LEVEL_RESTRICTED_BUCKET; + return AppBackgroundRestrictionsInfo.LEVEL_RESTRICTED_BUCKET; case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED: - return FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__RESTRICTION_LEVEL__LEVEL_BACKGROUND_RESTRICTED; + return AppBackgroundRestrictionsInfo.LEVEL_BACKGROUND_RESTRICTED; case RESTRICTION_LEVEL_HIBERNATION: - return FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__RESTRICTION_LEVEL__LEVEL_HIBERNATION; + return AppBackgroundRestrictionsInfo.LEVEL_HIBERNATION; default: - return FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__RESTRICTION_LEVEL__LEVEL_UNKNOWN; + return AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN; } } private int getThresholdStatsd(int reason) { switch (reason) { case REASON_MAIN_FORCED_BY_SYSTEM: - return FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__THRESHOLD__THRESHOLD_RESTRICTED; + return AppBackgroundRestrictionsInfo.THRESHOLD_RESTRICTED; case REASON_MAIN_FORCED_BY_USER: - return FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__THRESHOLD__THRESHOLD_USER; + return AppBackgroundRestrictionsInfo.THRESHOLD_USER; default: - return FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__THRESHOLD__THRESHOLD_UNKNOWN; + return AppBackgroundRestrictionsInfo.THRESHOLD_UNKNOWN; } } private int getTrackerTypeStatsd(@TrackerType int type) { switch (type) { case TRACKER_TYPE_BATTERY: - return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TRACKER__BATTERY_TRACKER; + return AppBackgroundRestrictionsInfo.BATTERY_TRACKER; case TRACKER_TYPE_BATTERY_EXEMPTION: - return FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__TRACKER__BATTERY_EXEMPTION_TRACKER; + return AppBackgroundRestrictionsInfo.BATTERY_EXEMPTION_TRACKER; case TRACKER_TYPE_FGS: - return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TRACKER__FGS_TRACKER; + return AppBackgroundRestrictionsInfo.FGS_TRACKER; case TRACKER_TYPE_MEDIA_SESSION: - return FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__TRACKER__MEDIA_SESSION_TRACKER; + return AppBackgroundRestrictionsInfo.MEDIA_SESSION_TRACKER; case TRACKER_TYPE_PERMISSION: - return FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__TRACKER__PERMISSION_TRACKER; + return AppBackgroundRestrictionsInfo.PERMISSION_TRACKER; case TRACKER_TYPE_BROADCAST_EVENTS: - return FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__TRACKER__BROADCAST_EVENTS_TRACKER; + return AppBackgroundRestrictionsInfo.BROADCAST_EVENTS_TRACKER; case TRACKER_TYPE_BIND_SERVICE_EVENTS: - return FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__TRACKER__BIND_SERVICE_EVENTS_TRACKER; + return AppBackgroundRestrictionsInfo.BIND_SERVICE_EVENTS_TRACKER; default: - return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TRACKER__UNKNOWN_TRACKER; + return AppBackgroundRestrictionsInfo.UNKNOWN_TRACKER; } } - private @ExemptionReason int getExemptionReasonStatsd(int uid, @RestrictionLevel int level) { + private int getExemptionReasonStatsd(int uid, @RestrictionLevel int level) { if (level != RESTRICTION_LEVEL_EXEMPTED) { - return FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_DENIED; + return AppBackgroundRestrictionsInfo.REASON_DENIED; } @ReasonCode final int reasonCode = getBackgroundRestrictionExemptionReason(uid); @@ -1960,16 +1978,15 @@ public final class AppRestrictionController { private int getOptimizationLevelStatsd(@RestrictionLevel int level) { switch (level) { case RESTRICTION_LEVEL_UNKNOWN: - return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__OPT_LEVEL__UNKNOWN; + return AppBackgroundRestrictionsInfo.UNKNOWN; case RESTRICTION_LEVEL_UNRESTRICTED: - return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__OPT_LEVEL__NOT_OPTIMIZED; + return AppBackgroundRestrictionsInfo.NOT_OPTIMIZED; case RESTRICTION_LEVEL_ADAPTIVE_BUCKET: - return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__OPT_LEVEL__OPTIMIZED; + return AppBackgroundRestrictionsInfo.OPTIMIZED; case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED: - return FrameworkStatsLog - .APP_BACKGROUND_RESTRICTIONS_INFO__OPT_LEVEL__BACKGROUND_RESTRICTED; + return AppBackgroundRestrictionsInfo.BACKGROUND_RESTRICTED; default: - return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__OPT_LEVEL__UNKNOWN; + return AppBackgroundRestrictionsInfo.UNKNOWN; } } @@ -1977,26 +1994,26 @@ public final class AppRestrictionController { private int getTargetSdkStatsd(String packageName) { final PackageManager pm = mInjector.getPackageManager(); if (pm == null) { - return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_UNKNOWN; + return AppBackgroundRestrictionsInfo.SDK_UNKNOWN; } try { final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */); if (pkg == null || pkg.applicationInfo == null) { - return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_UNKNOWN; + return AppBackgroundRestrictionsInfo.SDK_UNKNOWN; } final int targetSdk = pkg.applicationInfo.targetSdkVersion; if (targetSdk < Build.VERSION_CODES.S) { - return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_PRE_S; + return AppBackgroundRestrictionsInfo.SDK_PRE_S; } else if (targetSdk < Build.VERSION_CODES.TIRAMISU) { - return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_S; + return AppBackgroundRestrictionsInfo.SDK_S; } else if (targetSdk == Build.VERSION_CODES.TIRAMISU) { - return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_T; + return AppBackgroundRestrictionsInfo.SDK_T; } else { - return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_UNKNOWN; + return AppBackgroundRestrictionsInfo.SDK_UNKNOWN; } } catch (PackageManager.NameNotFoundException ignored) { } - return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_UNKNOWN; + return AppBackgroundRestrictionsInfo.SDK_UNKNOWN; } private void applyRestrictionLevel(String pkgName, int uid, @@ -2296,9 +2313,16 @@ public final class AppRestrictionController { } void postRequestBgRestrictedIfNecessary(String packageName, int uid) { + if (!mBgController.mConstantsObserver.mBgPromptAbusiveAppsToBgRestricted) { + if (DEBUG_BG_RESTRICTION_CONTROLLER) { + Slog.i(TAG, "Not requesting bg-restriction due to config"); + } + return; + } + final Intent intent = new Intent(Settings.ACTION_VIEW_ADVANCED_POWER_USAGE_DETAIL); intent.setData(Uri.fromParts(PACKAGE_SCHEME, packageName, null)); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE, null, @@ -2362,7 +2386,7 @@ public final class AppRestrictionController { } else { final Intent intent = new Intent(Settings.ACTION_VIEW_ADVANCED_POWER_USAGE_DETAIL); intent.setData(Uri.fromParts(PACKAGE_SCHEME, packageName, null)); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); pendingIntent = PendingIntent.getActivityAsUser(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE, null, UserHandle.of(UserHandle.getUserId(uid))); diff --git a/services/core/java/com/android/server/am/BroadcastDispatcher.java b/services/core/java/com/android/server/am/BroadcastDispatcher.java index 872531a47bc9..49477ad75302 100644 --- a/services/core/java/com/android/server/am/BroadcastDispatcher.java +++ b/services/core/java/com/android/server/am/BroadcastDispatcher.java @@ -512,12 +512,24 @@ public class BroadcastDispatcher { */ public boolean isEmpty() { synchronized (mLock) { + return isIdle() + && getBootCompletedBroadcastsUidsSize(Intent.ACTION_LOCKED_BOOT_COMPLETED) == 0 + && getBootCompletedBroadcastsUidsSize(Intent.ACTION_BOOT_COMPLETED) == 0; + } + } + + /** + * Have less check than {@link #isEmpty()}. + * The dispatcher is considered as idle even with deferred LOCKED_BOOT_COMPLETED/BOOT_COMPLETED + * broadcasts because those can be deferred until the first time the uid's process is started. + * @return + */ + public boolean isIdle() { + synchronized (mLock) { return mCurrentBroadcast == null && mOrderedBroadcasts.isEmpty() && isDeferralsListEmpty(mDeferredBroadcasts) - && isDeferralsListEmpty(mAlarmBroadcasts) - && getBootCompletedBroadcastsUidsSize(Intent.ACTION_LOCKED_BOOT_COMPLETED) == 0 - && getBootCompletedBroadcastsUidsSize(Intent.ACTION_BOOT_COMPLETED) == 0; + && isDeferralsListEmpty(mAlarmBroadcasts); } } diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 8a7fece5905c..dd7fb84b46bf 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -879,9 +879,9 @@ public final class BroadcastQueue { // Ensure that broadcasts are only sent to other apps if they are explicitly marked as // exported, or are System level broadcasts - if (!skip && !filter.exported && !Process.isCoreUid(r.callingUid) - && filter.receiverList.uid != r.callingUid) { - + if (!skip && !filter.exported && mService.checkComponentPermission(null, r.callingPid, + r.callingUid, filter.receiverList.uid, filter.exported) + != PackageManager.PERMISSION_GRANTED) { Slog.w(TAG, "Exported Denial: sending " + r.intent.toString() + ", action: " + r.intent.getAction() @@ -2218,7 +2218,7 @@ public final class BroadcastQueue { } boolean isIdle() { - return mParallelBroadcasts.isEmpty() && mDispatcher.isEmpty() + return mParallelBroadcasts.isEmpty() && mDispatcher.isIdle() && (mPendingBroadcast == null); } diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java index af87677ecb66..1fa56bc3e055 100644 --- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java +++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java @@ -47,6 +47,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.List; import java.util.Objects; +import java.util.concurrent.TimeUnit; /** * Entry point for most GNSS HAL commands and callbacks. @@ -1271,7 +1272,8 @@ public class GnssNative { @NativeEntryPoint boolean isInEmergencySession() { return Binder.withCleanCallingIdentity( - () -> mEmergencyHelper.isInEmergency(mConfiguration.getEsExtensionSec())); + () -> mEmergencyHelper.isInEmergency( + TimeUnit.SECONDS.toMillis(mConfiguration.getEsExtensionSec()))); } /** diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 8ed145c8d1b1..3eee572e82f3 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -5827,7 +5827,7 @@ public class NotificationManagerService extends SystemService { // Add summary final ApplicationInfo appInfo = adjustedSbn.getNotification().extras.getParcelable( - Notification.EXTRA_BUILDER_APPLICATION_INFO); + Notification.EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class); final Bundle extras = new Bundle(); extras.putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, appInfo); final String channelId = notificationRecord.getChannel().getId(); diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 849f53026c99..349174d95ec4 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -207,6 +207,9 @@ final class DefaultPermissionGrantPolicy { STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE); STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); STORAGE_PERMISSIONS.add(Manifest.permission.ACCESS_MEDIA_LOCATION); + STORAGE_PERMISSIONS.add(Manifest.permission.READ_MEDIA_AUDIO); + STORAGE_PERMISSIONS.add(Manifest.permission.READ_MEDIA_VIDEO); + STORAGE_PERMISSIONS.add(Manifest.permission.READ_MEDIA_IMAGES); } private static final Set<String> NEARBY_DEVICES_PERMISSIONS = new ArraySet<>(); diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java index 9aa53f18d0aa..7baec6217e2e 100644 --- a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java +++ b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java @@ -79,7 +79,8 @@ import java.util.Set; import java.util.function.Function; /** Resolves all Android component types [activities, services, providers and receivers]. */ -public class ComponentResolver extends ComponentResolverLocked implements Snappable { +public class ComponentResolver extends ComponentResolverLocked implements + Snappable<ComponentResolverApi> { private static final boolean DEBUG = false; private static final String TAG = "PackageManager"; private static final boolean DEBUG_FILTERS = false; @@ -166,11 +167,13 @@ public class ComponentResolver extends ComponentResolverLocked implements Snappa mProvidersByAuthority = new ArrayMap<>(); mDeferProtectedFilters = true; - mSnapshot = new SnapshotCache<ComponentResolverApi>(this, this) { + mSnapshot = new SnapshotCache<>(this, this) { @Override public ComponentResolverApi createSnapshot() { - return new ComponentResolverSnapshot(ComponentResolver.this, - userNeedsBadgingCache); + synchronized (mLock) { + return new ComponentResolverSnapshot(ComponentResolver.this, + userNeedsBadgingCache); + } }}; } diff --git a/services/core/java/com/android/server/wm/BLASTSync.md b/services/core/java/com/android/server/wm/BLASTSync.md index 2f39d6db385e..dbb28d4d5ae3 100644 --- a/services/core/java/com/android/server/wm/BLASTSync.md +++ b/services/core/java/com/android/server/wm/BLASTSync.md @@ -106,3 +106,88 @@ to the client via ClientTransaction), we haven't even incremented the seqId yet, at the same time as the state? We solve this by pushing all client communication through a handler thread that has to acquire the lock. This ensures we uphold requirement 2. += Transaction ordering = + +Applying transactions from different process, and in to different server side transaction queues +raises various questions about transaction ordering. There are two tricky questions in this +domain that we address here: + 1. The ordering of Transactions from a single BLASTBufferQueue wrt to eachother + 2. The ordering of non synced WM updates to syncable state, wrt a BLASTSyncEngine + transaction + +== Ordering of Transactions in a single BBQ == + +We can see if sync is never involved, there are never any real questions about ordering. +Even using one-way setTransactionState, the calls are from a single thread to a single +interface on another, and will be ordered. When we hand out transactions for sync +is where issues can start to arise. Obviously if we apply another transaction +immediately after handing out the sync transaction it could arrive first, and this would +cause an ordering issue. It's also possible that the sync transaction arrives before the +transaction applied before it (since setTransactionState is one way from BBQ). Even if +the transactions are applied in the right order, it's possible for them to be +commited out of sync, as the BBQ and SyncConsumer may be using different apply tokens +resulting in the transaction being placed in different server side queues. + +To solve these issues, we use a scheme involving "barrier transactions". We show how +this scheme handles every permutation of (Sync, NotSync). + +1. NotSync, NotSync: This is the trivial case. As long as both buffers are submitted from + the same thread, they will arrive in SF in order, and SF will place them in the same + queue (since there is a shared apply token), which it will process in order. +2. Sync, NotSync: For sync transactions we register a commit callback, and require it to + be fired before applying the next transaction. Since the commit callback is only + fired when the transaction has latched on the server side, any transaction applied + later, must occur later. +3. Sync, Sync: Ordering of consecutive sync transactions is delegated to the sync + consumer +4. NotSync, Sync: This is the trickiest case, as we don't want to set a commit callback + on not sync transactions (as this would incur an unacceptable performance overhead). + Further complicating matters, we want to apply them with one-way binder, since + this is the hot path for all graphical updates. To solve this we use a + "setBufferHasBarrier" system. Sync transactions (before they are handed out) + are tagged with a barrier, referring to the frame number of the last + non sync transaction. SurfaceFlinger will ensure the correct ordering + by stalling transactions in the queue until barriers are fulfilled. This barrier + primitive is dangerous, because it could result in deadlocking, but its ok in + this scenario since only BBQ uses its apply token. + +We can see from this that all frames are in order. + +== Ordering of WM updates to syncable state == + +A second interesting question, is about the ordering of WM updates to syncable-state. In +the WM we frequently write code like + getPendingTransaction/getSyncTransaction().show(mSurfaceControl). +In normal operation, getSyncTransaction and getPendingTransaction both refer to the same +transaction which is the displaycontent pendingtransaction, applied by the WindowManager. +During sync, each syncing container will instead use its mSyncTransaction, which will +eventually be merged to be applied by the WindowManager. We can see that we have a congruent +"problem" to BLASTBufferQueue transaction ordering. Namely, earlier state updates which were +given to SysUI could end up being applied later than state updates which were made later but +directly applied by the WM. We can break this down in to two cases: +=== getPendingTransaction() and getSyncTransaction() ordering === +It's possible for code like this to occur: +getSyncTransaction().OP1 +getPendingTransaction().OP2 +applyPendingTransaction +applySyncTransaction + +in this case the change in OP2 was made later, but occurs first. We define this case as +intended behavior, and say there is no ordering guarantee between the pending +and sync transactions. This means the pending transaction is only useful in +some marginal cases and should probably be considered a deprecated primitive. +=== getSyncTransaction() and getSyncTransaction() ordering === +However, we take great care to ensure usage of getSyncTransaction() reflects +the ordering the developer would expect. BLASTSyncEngine will register +a commit callback on all transactions it hands out. In the interval between +receiving this commit callback and sending out the transaction, we may have other +data enter getSyncTransaction. If we returned the pending transaction +(as we do in normal time), then we could create ordering issues, since the pending +transactions ordering is undefined. Instead we continue to return the sync transaction +during this interval. If no second sync has started by the time we receive +the commit callback, then we directly apply this left over data in the sync transaction +guaranteed it will be ordered correctly, and return to using the pending +transaction. If a second sync has started, then we just allow the data +to persist in the mSyncTransaction, potentially to be overwritten +by the new sync. It will eventually apply with SysUI's apply token and +ordering will be maintained. diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java index 6e205be5b574..3f300bce54c4 100644 --- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java +++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE; +import static com.android.server.wm.WindowManagerService.H.WINDOW_STATE_BLAST_SYNC_TIMEOUT; import android.annotation.NonNull; import android.os.Trace; @@ -147,6 +148,33 @@ class BLASTSyncEngine { for (WindowContainer wc : mRootMembers) { wc.finishSync(merged, false /* cancel */); } + + final ArraySet<WindowContainer> wcAwaitingCommit = new ArraySet<>(); + for (WindowContainer wc : mRootMembers) { + wc.waitForSyncTransactionCommit(wcAwaitingCommit); + } + final Runnable callback = new Runnable() { + // Can run a second time if the action completes after the timeout. + boolean ran = false; + public void run() { + synchronized (mWm.mGlobalLock) { + if (ran) { + return; + } + mWm.mH.removeCallbacks(this); + ran = true; + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + for (WindowContainer wc : wcAwaitingCommit) { + wc.onSyncTransactionCommitted(t); + } + t.apply(); + wcAwaitingCommit.clear(); + } + } + }; + merged.addTransactionCommittedListener((r) -> { r.run(); }, callback::run); + mWm.mH.postDelayed(callback, WINDOW_STATE_BLAST_SYNC_TIMEOUT); + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionReady"); mListener.onTransactionReady(mSyncId, merged); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index eaf82b625f71..2d7d7055a03b 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -138,8 +138,8 @@ import android.window.ClientWindowFrames; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.policy.ForceShowNavigationBarSettingsObserver; import com.android.internal.policy.GestureNavigationSettingsObserver; +import com.android.internal.policy.KidsModeSettingsObserver; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.policy.SystemBarUtils; import com.android.internal.protolog.common.ProtoLog; @@ -378,7 +378,7 @@ public class DisplayPolicy { private final WindowManagerInternal.AppTransitionListener mAppTransitionListener; - private final ForceShowNavigationBarSettingsObserver mForceShowNavigationBarSettingsObserver; + private final KidsModeSettingsObserver mKidsModeSettingsObserver; private boolean mForceShowNavigationBarEnabled; private class PolicyHandler extends Handler { @@ -653,17 +653,17 @@ public class DisplayPolicy { }); mHandler.post(mGestureNavigationSettingsObserver::register); - mForceShowNavigationBarSettingsObserver = new ForceShowNavigationBarSettingsObserver( + mKidsModeSettingsObserver = new KidsModeSettingsObserver( mHandler, mContext); - mForceShowNavigationBarSettingsObserver.setOnChangeRunnable(() -> { + mKidsModeSettingsObserver.setOnChangeRunnable(() -> { synchronized (mLock) { mForceShowNavigationBarEnabled = - mForceShowNavigationBarSettingsObserver.isEnabled(); + mKidsModeSettingsObserver.isEnabled(); updateSystemBarAttributes(); } }); - mForceShowNavigationBarEnabled = mForceShowNavigationBarSettingsObserver.isEnabled(); - mHandler.post(mForceShowNavigationBarSettingsObserver::register); + mForceShowNavigationBarEnabled = mKidsModeSettingsObserver.isEnabled(); + mHandler.post(mKidsModeSettingsObserver::register); } /** @@ -2861,7 +2861,7 @@ public class DisplayPolicy { void release() { mDisplayContent.mTransitionController.unregisterLegacyListener(mAppTransitionListener); mHandler.post(mGestureNavigationSettingsObserver::unregister); - mHandler.post(mForceShowNavigationBarSettingsObserver::unregister); + mHandler.post(mKidsModeSettingsObserver::unregister); mImmersiveModeConfirmation.release(); } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index fc407e616bd9..efe617d86d4e 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -445,7 +445,7 @@ public class RecentsAnimationController implements DeathRecipient { } final int taskCount = visibleTasks.size(); - for (int i = 0; i < taskCount; i++) { + for (int i = taskCount - 1; i >= 0; i--) { final Task task = visibleTasks.get(i); if (skipAnimation(task)) { continue; @@ -746,7 +746,7 @@ public class RecentsAnimationController implements DeathRecipient { ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "collectTaskRemoteAnimations, target: %s", target); } - }, true); + }, false /* traverseTopToBottom */); } void logRecentsAnimationStartTime(int durationMs) { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 6f69e0324b0b..22714c6f5593 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2007,8 +2007,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> r.getDisplayContent().prepareAppTransition(TRANSIT_NONE); final TaskFragment organizedTf = r.getOrganizedTaskFragment(); - // TODO: Does it make sense to only count non-finishing activities? - final boolean singleActivity = task.getActivityCount() == 1; + final boolean singleActivity = task.getNonFinishingActivityCount() == 1; final Task rootTask; if (singleActivity) { rootTask = task; diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index bd078d880980..9ea566ece6ec 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1380,14 +1380,6 @@ class Task extends TaskFragment { return getActivity(ActivityRecord::canBeTopRunning); } - int getActivityCount() { - final int[] activityCount = new int[1]; - forAllActivities(ar -> { - activityCount[0]++; - }); - return activityCount[0]; - } - /** * Return true if any activities in this task belongs to input uid. */ diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 4e0d84c67d03..e0346544d528 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -1034,19 +1034,19 @@ class TaskFragment extends WindowContainer<WindowContainer> { // If the top activity is the resumed one, nothing to do. if (mResumedActivity == next && next.isState(RESUMED) && taskDisplayArea.allResumedActivitiesComplete()) { + // Ensure the visibility gets updated before execute app transition. + taskDisplayArea.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */, + false /* preserveWindows */, true /* notifyClients */); // Make sure we have executed any pending transitions, since there // should be nothing left to do at this point. executeAppTransition(options); - // For devices that are not in fullscreen mode (e.g. freeform windows), it's possible - // we still want to check if the visibility of other windows have changed (e.g. bringing - // a fullscreen window forward to cover another freeform activity.) - if (taskDisplayArea.inMultiWindowMode()) { - if (taskDisplayArea.mDisplayContent != null - && taskDisplayArea.mDisplayContent.mFocusedApp != next) { - taskDisplayArea.mDisplayContent.setFocusedApp(next); - } - taskDisplayArea.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */, - false /* preserveWindows */, true /* notifyClients */); + + // In a multi-resumed environment, like in a freeform device, the top + // activity can be resumed, but it might not be the focused app. + // Set focused app when top activity is resumed + if (taskDisplayArea.inMultiWindowMode() && taskDisplayArea.mDisplayContent != null + && taskDisplayArea.mDisplayContent.mFocusedApp != next) { + taskDisplayArea.mDisplayContent.setFocusedApp(next); } ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Top activity " + "resumed %s", next); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 4dbcea1e4751..f5af2b4e29fa 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -236,6 +236,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< */ private boolean mCommittedReparentToAnimationLeash; + private int mSyncTransactionCommitCallbackDepth = 0; + /** Interface for {@link #isAnimating} to check which cases for the container is animating. */ public interface AnimationFlags { /** @@ -2688,6 +2690,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * {@link #getPendingTransaction()} */ public Transaction getSyncTransaction() { + if (mSyncTransactionCommitCallbackDepth > 0) { + return mSyncTransaction; + } if (mSyncState != SYNC_STATE_NONE) { return mSyncTransaction; } @@ -3909,4 +3914,29 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< p.updateOverlayInsetsState(originalChange); } } + + void waitForSyncTransactionCommit(ArraySet<WindowContainer> wcAwaitingCommit) { + if (wcAwaitingCommit.contains(this)) { + return; + } + mSyncTransactionCommitCallbackDepth++; + wcAwaitingCommit.add(this); + + for (int i = mChildren.size() - 1; i >= 0; --i) { + mChildren.get(i).waitForSyncTransactionCommit(wcAwaitingCommit); + } + } + + void onSyncTransactionCommitted(SurfaceControl.Transaction t) { + mSyncTransactionCommitCallbackDepth--; + if (mSyncTransactionCommitCallbackDepth > 0) { + return; + } + if (mSyncState != SYNC_STATE_NONE) { + return; + } + + t.merge(mSyncTransaction); + } + } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index c1c8b81f2c32..67f7ff701981 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -1522,7 +1522,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); return 0; } - if (taskFragment.isEmbeddedTaskFragmentInPip()) { + if (taskFragment.isEmbeddedTaskFragmentInPip() + // When the Task enters PiP before the organizer removes the empty TaskFragment, we + // should allow it to do the cleanup. + && taskFragment.getTopNonFinishingActivity() != null) { final Throwable exception = new IllegalArgumentException( "Not allowed to delete TaskFragment in PIP Task"); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java index c0b4f0fe5812..ac542935fa0a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java @@ -585,6 +585,7 @@ public final class BackgroundRestrictionTest { DeviceConfigSession<Float> bgCurrentDrainRestrictedBucketThreshold = null; DeviceConfigSession<Float> bgCurrentDrainBgRestrictedThreshold = null; DeviceConfigSession<Boolean> bgPromptFgsWithNotiToBgRestricted = null; + DeviceConfigSession<Boolean> bgPromptAbusiveAppToBgRestricted = null; DeviceConfigSession<Long> bgNotificationMinInterval = null; DeviceConfigSession<Integer> bgBatteryExemptionTypes = null; DeviceConfigSession<Boolean> bgCurrentDrainDecoupleThresholds = null; @@ -642,6 +643,14 @@ public final class BackgroundRestrictionTest { R.bool.config_bg_prompt_fgs_with_noti_to_bg_restricted)); bgPromptFgsWithNotiToBgRestricted.set(true); + bgPromptAbusiveAppToBgRestricted = new DeviceConfigSession<>( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + ConstantsObserver.KEY_BG_PROMPT_ABUSIVE_APPS_TO_BG_RESTRICTED, + DeviceConfig::getBoolean, + mContext.getResources().getBoolean( + R.bool.config_bg_prompt_abusive_apps_to_bg_restricted)); + bgPromptAbusiveAppToBgRestricted.set(true); + bgNotificationMinInterval = new DeviceConfigSession<>( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, ConstantsObserver.KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL, @@ -1055,6 +1064,7 @@ public final class BackgroundRestrictionTest { closeIfNotNull(bgCurrentDrainRestrictedBucketThreshold); closeIfNotNull(bgCurrentDrainBgRestrictedThreshold); closeIfNotNull(bgPromptFgsWithNotiToBgRestricted); + closeIfNotNull(bgPromptAbusiveAppToBgRestricted); closeIfNotNull(bgNotificationMinInterval); closeIfNotNull(bgBatteryExemptionTypes); closeIfNotNull(bgCurrentDrainDecoupleThresholds); @@ -1613,6 +1623,7 @@ public final class BackgroundRestrictionTest { DeviceConfigSession<String> bgPermissionsInMonitor = null; DeviceConfigSession<Boolean> bgCurrentDrainHighThresholdByBgLocation = null; DeviceConfigSession<Boolean> bgCurrentDrainDecoupleThresholds = null; + DeviceConfigSession<Boolean> bgPromptAbusiveAppToBgRestricted = null; mBgRestrictionController.addAppBackgroundRestrictionListener(listener); @@ -1751,6 +1762,14 @@ public final class BackgroundRestrictionTest { AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLD); bgCurrentDrainDecoupleThresholds.set(true); + bgPromptAbusiveAppToBgRestricted = new DeviceConfigSession<>( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + ConstantsObserver.KEY_BG_PROMPT_ABUSIVE_APPS_TO_BG_RESTRICTED, + DeviceConfig::getBoolean, + mContext.getResources().getBoolean( + R.bool.config_bg_prompt_abusive_apps_to_bg_restricted)); + bgPromptAbusiveAppToBgRestricted.set(true); + mCurrentTimeMillis = 10_000L; doReturn(mCurrentTimeMillis - windowMs).when(stats).getStatsStartTimestamp(); doReturn(mCurrentTimeMillis).when(stats).getStatsEndTimestamp(); @@ -2168,6 +2187,7 @@ public final class BackgroundRestrictionTest { closeIfNotNull(bgBatteryExemptionTypes); closeIfNotNull(bgPermissionMonitorEnabled); closeIfNotNull(bgPermissionsInMonitor); + closeIfNotNull(bgPromptAbusiveAppToBgRestricted); closeIfNotNull(bgCurrentDrainHighThresholdByBgLocation); closeIfNotNull(bgCurrentDrainDecoupleThresholds); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index d135de0fc921..a297608af480 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -527,6 +527,14 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { verify(mAtm.mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer), eq(errorToken), any(IllegalArgumentException.class)); assertNotNull(mAtm.mWindowOrganizerController.getTaskFragment(mFragmentToken)); + + // Allow organizer to delete empty TaskFragment for cleanup. + final Task task = mTaskFragment.getTask(); + mTaskFragment.removeChild(mTaskFragment.getTopMostActivity()); + mAtm.mWindowOrganizerController.applyTransaction(mTransaction); + + assertNull(mAtm.mWindowOrganizerController.getTaskFragment(mFragmentToken)); + assertNull(task.getTopChild()); } @Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt index bff099e2e7a1..6257484be9bd 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt @@ -91,6 +91,7 @@ class CloseImeEditorPopupDialogTest(private val testSpec: FlickerTestParameter) .then() .isVisible(FlickerComponentName.IME_SNAPSHOT) .then() + .isInvisible(FlickerComponentName.IME_SNAPSHOT) .isInvisible(FlickerComponentName.IME) } } |