diff options
212 files changed, 5104 insertions, 2182 deletions
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 e728a2c55765..d0a1b027ec48 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -183,6 +183,9 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.zone.ZoneOffsetTransition; +import java.time.zone.ZoneRules; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -194,6 +197,7 @@ import java.util.Set; import java.util.TimeZone; import java.util.TreeSet; import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; import java.util.function.Predicate; /** @@ -234,8 +238,12 @@ public class AlarmManagerService extends SystemService { private static final long TEMPORARY_QUOTA_DURATION = INTERVAL_DAY; - // System property read on some device configurations to initialize time properly. + // System properties read on some device configurations to initialize time properly and + // perform DST transitions at the bootloader level. private static final String TIMEOFFSET_PROPERTY = "persist.sys.time.offset"; + private static final String DST_TRANSITION_PROPERTY = "persist.sys.time.dst_transition"; + private static final String DST_OFFSET_PROPERTY = "persist.sys.time.dst_offset"; + private final Intent mBackgroundIntent = new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND); @@ -2200,6 +2208,19 @@ public class AlarmManagerService extends SystemService { final int gmtOffset = newZone.getOffset(mInjector.getCurrentTimeMillis()); SystemProperties.set(TIMEOFFSET_PROPERTY, String.valueOf(gmtOffset)); + + final ZoneRules rules = newZone.toZoneId().getRules(); + final ZoneOffsetTransition transition = rules.nextTransition(Instant.now()); + if (null != transition) { + // Get the offset between the time after the DST transition and before. + final long transitionOffset = TimeUnit.SECONDS.toMillis(( + transition.getOffsetAfter().getTotalSeconds() + - transition.getOffsetBefore().getTotalSeconds())); + // Time when the next DST transition is programmed. + final long nextTransition = TimeUnit.SECONDS.toMillis(transition.toEpochSecond()); + SystemProperties.set(DST_TRANSITION_PROPERTY, String.valueOf(nextTransition)); + SystemProperties.set(DST_OFFSET_PROPERTY, String.valueOf(transitionOffset)); + } } // Clear the default time zone in the system server process. This forces the next call diff --git a/core/api/test-current.txt b/core/api/test-current.txt index b8c32a4aa7ef..af40c3d658a7 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -317,9 +317,6 @@ package android.app { } public class ComponentOptions { - field public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED = 1; // 0x1 - field public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2; // 0x2 - field public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED = 0; // 0x0 } public class DownloadManager { @@ -2034,41 +2031,6 @@ package android.media { method public android.media.PlaybackParams setAudioStretchMode(int); } - @FlaggedApi("android.os.vibrator.haptics_customization_enabled") public final class RingtoneSelection { - method @NonNull public static android.media.RingtoneSelection fromUri(@Nullable android.net.Uri, int); - method public int getSoundSource(); - method @Nullable public android.net.Uri getSoundUri(); - method public int getVibrationSource(); - method @Nullable public android.net.Uri getVibrationUri(); - method public static boolean isRingtoneSelectionUri(@Nullable android.net.Uri); - method @NonNull public android.net.Uri toUri(); - field public static final String DEFAULT_SELECTION_URI_STRING = "content://media/ringtone"; - field public static final int FROM_URI_RINGTONE_SELECTION_ONLY = 3; // 0x3 - field public static final int FROM_URI_RINGTONE_SELECTION_OR_SOUND = 1; // 0x1 - field public static final int FROM_URI_RINGTONE_SELECTION_OR_VIBRATION = 2; // 0x2 - field public static final int SOUND_SOURCE_OFF = 1; // 0x1 - field public static final int SOUND_SOURCE_SYSTEM_DEFAULT = 3; // 0x3 - field public static final int SOUND_SOURCE_UNSPECIFIED = 0; // 0x0 - field public static final int SOUND_SOURCE_URI = 2; // 0x2 - field public static final int VIBRATION_SOURCE_APPLICATION_DEFAULT = 4; // 0x4 - field public static final int VIBRATION_SOURCE_AUDIO_CHANNEL = 10; // 0xa - field public static final int VIBRATION_SOURCE_HAPTIC_GENERATOR = 11; // 0xb - field public static final int VIBRATION_SOURCE_OFF = 1; // 0x1 - field public static final int VIBRATION_SOURCE_SYSTEM_DEFAULT = 3; // 0x3 - field public static final int VIBRATION_SOURCE_UNSPECIFIED = 0; // 0x0 - field public static final int VIBRATION_SOURCE_URI = 2; // 0x2 - } - - @FlaggedApi("android.os.vibrator.haptics_customization_enabled") public static final class RingtoneSelection.Builder { - ctor public RingtoneSelection.Builder(); - ctor public RingtoneSelection.Builder(@NonNull android.media.RingtoneSelection); - method @NonNull public android.media.RingtoneSelection build(); - method @NonNull public android.media.RingtoneSelection.Builder setSoundSource(int); - method @NonNull public android.media.RingtoneSelection.Builder setSoundSource(@NonNull android.net.Uri); - method @NonNull public android.media.RingtoneSelection.Builder setVibrationSource(int); - method @NonNull public android.media.RingtoneSelection.Builder setVibrationSource(@NonNull android.net.Uri); - } - public static final class VolumeShaper.Configuration.Builder { method @NonNull public android.media.VolumeShaper.Configuration.Builder setOptionFlags(int); } diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt index 658ddbf0abfd..685ea63cb432 100644 --- a/core/api/test-lint-baseline.txt +++ b/core/api/test-lint-baseline.txt @@ -2055,14 +2055,8 @@ UnflaggedApi: android.media.RingtoneSelection#SOUND_SOURCE_DEFAULT: New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_DEFAULT UnflaggedApi: android.media.RingtoneSelection#SOUND_SOURCE_OFF: New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_OFF -UnflaggedApi: android.media.RingtoneSelection#SOUND_SOURCE_SYSTEM_DEFAULT: - New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_SYSTEM_DEFAULT -UnflaggedApi: android.media.RingtoneSelection#SOUND_SOURCE_UNSPECIFIED: - New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_UNSPECIFIED UnflaggedApi: android.media.RingtoneSelection#SOUND_SOURCE_URI: New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_URI -UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_APPLICATION_DEFAULT: - New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_APPLICATION_DEFAULT UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_APPLICATION_PROVIDED: New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_APPLICATION_PROVIDED UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_AUDIO_CHANNEL: @@ -2073,10 +2067,6 @@ UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_HAPTIC_GENERATOR: New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_HAPTIC_GENERATOR UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_OFF: New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_OFF -UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_SYSTEM_DEFAULT: - New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_SYSTEM_DEFAULT -UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_UNSPECIFIED: - New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_UNSPECIFIED UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_URI: New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_URI UnflaggedApi: android.media.RingtoneSelection#fromUri(android.net.Uri, int): diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 57c67be7e625..63cafdc6c855 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -9601,9 +9601,9 @@ public class Activity extends ContextThemeWrapper * Specifies whether the activities below this one in the task can also start other activities * or finish the task. * <p> - * Starting from Target SDK Level {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, apps - * are blocked from starting new activities or finishing their task unless the top activity of - * such task belong to the same UID for security reasons. + * Starting from Target SDK Level {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, apps + * may be blocked from starting new activities or finishing their task unless the top activity + * of such task belong to the same UID for security reasons. * <p> * Setting this flag to {@code true} will allow the launching app to ignore the restriction if * this activity is on top. Apps matching the UID of this activity are always exempt. diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 2a2c5f05f122..e094ac61b500 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -93,15 +93,30 @@ public class ActivityOptions extends ComponentOptions { */ public static final String EXTRA_USAGE_TIME_REPORT_PACKAGES = "android.usage_time_packages"; - /** No explicit value chosen. The system will decide whether to grant privileges. */ - public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED = - ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; - /** Allow the {@link PendingIntent} to use the background activity start privileges. */ - public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED = - ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; - /** Deny the {@link PendingIntent} to use the background activity start privileges. */ - public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = - ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; + /** Enumeration of background activity start modes. + * + * These define if an app wants to grant it's background activity start privileges to a + * {@link PendingIntent}. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"MODE_BACKGROUND_ACTIVITY_START_"}, value = { + MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED, + MODE_BACKGROUND_ACTIVITY_START_ALLOWED, + MODE_BACKGROUND_ACTIVITY_START_DENIED}) + public @interface BackgroundActivityStartMode {} + /** + * No explicit value chosen. The system will decide whether to grant privileges. + */ + public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED = 0; + /** + * Allow the {@link PendingIntent} to use the background activity start privileges. + */ + public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED = 1; + /** + * Deny the {@link PendingIntent} to use the background activity start privileges. + */ + public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2; /** * The package name that created the options. diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index 1b5b0fc5d917..60d622dd78c6 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -16,6 +16,8 @@ package android.app; +import static android.app.ActivityOptions.BackgroundActivityStartMode; + import android.annotation.CurrentTimeMillisLong; import android.annotation.FlaggedApi; import android.annotation.IntDef; @@ -1132,7 +1134,8 @@ public class BroadcastOptions extends ComponentOptions { @SystemApi @NonNull @Override // to narrow down the return type - public BroadcastOptions setPendingIntentBackgroundActivityStartMode(int state) { + public BroadcastOptions setPendingIntentBackgroundActivityStartMode( + @BackgroundActivityStartMode int state) { super.setPendingIntentBackgroundActivityStartMode(state); return this; } diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java index ce16ddf5c05f..397477d72a9a 100644 --- a/core/java/android/app/ComponentOptions.java +++ b/core/java/android/app/ComponentOptions.java @@ -16,16 +16,17 @@ package android.app; -import android.annotation.IntDef; +import static android.app.ActivityOptions.BackgroundActivityStartMode; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.TestApi; import android.os.Bundle; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - /** * Base class for {@link ActivityOptions} and {@link BroadcastOptions}. * @hide @@ -56,32 +57,6 @@ public class ComponentOptions { private @Nullable Boolean mPendingIntentBalAllowed = null; private boolean mPendingIntentBalAllowedByPermission = false; - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = {"MODE_BACKGROUND_ACTIVITY_START_"}, value = { - MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED, - MODE_BACKGROUND_ACTIVITY_START_ALLOWED, - MODE_BACKGROUND_ACTIVITY_START_DENIED}) - public @interface BackgroundActivityStartMode {} - /** - * No explicit value chosen. The system will decide whether to grant privileges. - * @hide - */ - @TestApi - public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED = 0; - /** - * Allow the {@link PendingIntent} to use the background activity start privileges. - * @hide - */ - @TestApi - public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED = 1; - /** - * Deny the {@link PendingIntent} to use the background activity start privileges. - * @hide - */ - @TestApi - public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2; - ComponentOptions() { } diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 04e3dab55939..7119730c6984 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -772,6 +772,16 @@ public final class CameraManager { "CameraDeviceSetup is not supported for Camera ID: " + cameraId); } + return getCameraDeviceSetupUnsafe(cameraId); + + } + + /** + * Creates and returns a {@link CameraDeviceSetup} instance without any error checking. To + * be used (carefully) by callers who are sure that CameraDeviceSetup instance can be legally + * created and don't want to pay the latency cost of calling {@link #getCameraDeviceSetup}. + */ + private CameraDevice.CameraDeviceSetup getCameraDeviceSetupUnsafe(@NonNull String cameraId) { return new CameraDeviceSetupImpl(cameraId, /*cameraManager=*/ this, mContext); } @@ -857,8 +867,9 @@ public final class CameraManager { ICameraDeviceUser cameraUser = null; CameraDevice.CameraDeviceSetup cameraDeviceSetup = null; - if (Flags.cameraDeviceSetup() && isCameraDeviceSetupSupported(cameraId)) { - cameraDeviceSetup = getCameraDeviceSetup(cameraId); + if (Flags.cameraDeviceSetup() + && CameraDeviceSetupImpl.isCameraDeviceSetupSupported(characteristics)) { + cameraDeviceSetup = getCameraDeviceSetupUnsafe(cameraId); } android.hardware.camera2.impl.CameraDeviceImpl deviceImpl = diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index c13dd363d79e..c03dc718d1a1 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -406,6 +406,7 @@ public class CallLog { * Builder for the add-call parameters. */ public static final class AddCallParametersBuilder { + public static final int MAX_NUMBER_OF_CHARACTERS = 256; private CallerInfo mCallerInfo; private String mNumber; private String mPostDialDigits; @@ -431,7 +432,7 @@ public class CallLog { private Uri mPictureUri; private int mIsPhoneAccountMigrationPending; private boolean mIsBusinessCall; - private String mBusinessName; + private String mAssertedDisplayName; /** * @param callerInfo the CallerInfo object to get the target contact from. @@ -659,11 +660,18 @@ public class CallLog { } /** - * @param businessName should be set if the caller is a business call + * @param assertedDisplayName the asserted display name associated with the business + * call + * @throws IllegalArgumentException if the assertedDisplayName is over 256 characters */ @FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER) - public @NonNull AddCallParametersBuilder setBusinessName(String businessName) { - mBusinessName = businessName; + public @NonNull AddCallParametersBuilder setAssertedDisplayName( + String assertedDisplayName) { + if (assertedDisplayName.length() > MAX_NUMBER_OF_CHARACTERS) { + throw new IllegalArgumentException("assertedDisplayName exceeds the character" + + " limit of " + MAX_NUMBER_OF_CHARACTERS + "."); + } + mAssertedDisplayName = assertedDisplayName; return this; } @@ -678,7 +686,7 @@ public class CallLog { mCallBlockReason, mCallScreeningAppName, mCallScreeningComponentName, mMissedReason, mPriority, mSubject, mLatitude, mLongitude, mPictureUri, - mIsPhoneAccountMigrationPending, mIsBusinessCall, mBusinessName); + mIsPhoneAccountMigrationPending, mIsBusinessCall, mAssertedDisplayName); } else { return new AddCallParams(mCallerInfo, mNumber, mPostDialDigits, mViaNumber, mPresentation, mCallType, mFeatures, mAccountHandle, mStart, mDuration, @@ -716,7 +724,7 @@ public class CallLog { private Uri mPictureUri; private int mIsPhoneAccountMigrationPending; private boolean mIsBusinessCall; - private String mBusinessName; + private String mAssertedDisplayName; private AddCallParams(CallerInfo callerInfo, String number, String postDialDigits, String viaNumber, int presentation, int callType, int features, @@ -761,7 +769,8 @@ public class CallLog { CharSequence callScreeningAppName, String callScreeningComponentName, long missedReason, int priority, String subject, double latitude, double longitude, Uri pictureUri, - int isPhoneAccountMigrationPending, boolean isBusinessCall, String businessName) { + int isPhoneAccountMigrationPending, boolean isBusinessCall, + String assertedDisplayName) { mCallerInfo = callerInfo; mNumber = number; mPostDialDigits = postDialDigits; @@ -787,7 +796,7 @@ public class CallLog { mPictureUri = pictureUri; mIsPhoneAccountMigrationPending = isPhoneAccountMigrationPending; mIsBusinessCall = isBusinessCall; - mBusinessName = businessName; + mAssertedDisplayName = assertedDisplayName; } } @@ -1833,7 +1842,7 @@ public class CallLog { values.put(IS_PHONE_ACCOUNT_MIGRATION_PENDING, params.mIsPhoneAccountMigrationPending); if (Flags.businessCallComposer()) { values.put(IS_BUSINESS_CALL, Integer.valueOf(params.mIsBusinessCall ? 1 : 0)); - values.put(ASSERTED_DISPLAY_NAME, params.mBusinessName); + values.put(ASSERTED_DISPLAY_NAME, params.mAssertedDisplayName); } if ((params.mCallerInfo != null) && (params.mCallerInfo.getContactId() > 0)) { // Update usage information for the number associated with the contact ID. diff --git a/core/java/android/widget/flags/notification_widget_flags.aconfig b/core/java/android/widget/flags/notification_widget_flags.aconfig index bfe3d052479b..e60fa157e8e4 100644 --- a/core/java/android/widget/flags/notification_widget_flags.aconfig +++ b/core/java/android/widget/flags/notification_widget_flags.aconfig @@ -15,4 +15,14 @@ flag { metadata { purpose: PURPOSE_BUGFIX } +} + +flag { + name: "conversation_style_set_avatar_async" + namespace: "systemui" + description: "Offloads conversation avatar drawable loading to the background thread" + bug: "305540309" + metadata { + purpose: PURPOSE_BUGFIX + } }
\ No newline at end of file diff --git a/core/java/com/android/internal/widget/ConversationAvatarData.java b/core/java/com/android/internal/widget/ConversationAvatarData.java new file mode 100644 index 000000000000..e04772f72516 --- /dev/null +++ b/core/java/com/android/internal/widget/ConversationAvatarData.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.widget; + +import android.graphics.drawable.Drawable; + +/** + * @hide + */ +interface ConversationAvatarData { + final class OneToOneConversationAvatarData implements ConversationAvatarData { + final Drawable mDrawable; + + OneToOneConversationAvatarData(Drawable drawable) { + mDrawable = drawable; + } + } + + final class GroupConversationAvatarData implements ConversationAvatarData { + final Drawable mLastIcon; + final Drawable mSecondLastIcon; + + GroupConversationAvatarData(Drawable lastIcon, Drawable secondLastIcon) { + mLastIcon = lastIcon; + mSecondLastIcon = secondLastIcon; + } + } +} diff --git a/core/java/com/android/internal/widget/ConversationHeaderData.java b/core/java/com/android/internal/widget/ConversationHeaderData.java new file mode 100644 index 000000000000..0953b3912a91 --- /dev/null +++ b/core/java/com/android/internal/widget/ConversationHeaderData.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.widget; + +import android.annotation.Nullable; + +/** + * @hide + */ +final class ConversationHeaderData { + private final CharSequence mConversationText; + + private final ConversationAvatarData mConversationAvatarData; + + ConversationHeaderData(CharSequence conversationText, + ConversationAvatarData conversationAvatarData) { + mConversationText = conversationText; + mConversationAvatarData = conversationAvatarData; + } + + @Nullable + CharSequence getConversationText() { + return mConversationText; + } + + @Nullable + ConversationAvatarData getConversationAvatar() { + return mConversationAvatarData; + } +} diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index 889434f40472..06835f00755f 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -34,8 +34,10 @@ import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Rect; import android.graphics.Typeface; +import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.Icon; +import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import android.text.Spannable; @@ -59,8 +61,11 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RemoteViews; import android.widget.TextView; +import android.widget.flags.Flags; import com.android.internal.R; +import com.android.internal.widget.ConversationAvatarData.GroupConversationAvatarData; +import com.android.internal.widget.ConversationAvatarData.OneToOneConversationAvatarData; import java.util.ArrayList; import java.util.List; @@ -403,11 +408,14 @@ public class ConversationLayout extends FrameLayout */ @RemotableViewMethod(asyncImpl = "setDataAsync") public void setData(Bundle extras) { - bind(parseMessagingData(extras, /* usePrecomputedText= */ false)); + bind(parseMessagingData(extras, + /* usePrecomputedText= */ false, + /*includeConversationIcon= */false)); } @NonNull - private MessagingData parseMessagingData(Bundle extras, boolean usePrecomputedText) { + private MessagingData parseMessagingData(Bundle extras, boolean usePrecomputedText, + boolean includeConversationIcon) { Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES); List<Notification.MessagingStyle.Message> newMessages = Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages); @@ -438,8 +446,20 @@ public class ConversationLayout extends FrameLayout // Lets first find the groups (populate `groups` and `senders`) findGroups(newHistoricMessagingMessages, newMessagingMessages, user, groups, senders); + // load conversation header data, avatar and title. + final ConversationHeaderData conversationHeaderData; + if (includeConversationIcon && Flags.conversationStyleSetAvatarAsync()) { + conversationHeaderData = loadConversationHeaderData(mIsOneToOne, + mConversationTitle, + mShortcutIcon, + mLargeIcon, newMessagingMessages, user, groups, mLayoutColor); + } else { + conversationHeaderData = null; + } + return new MessagingData(user, showSpinner, unreadCount, - newHistoricMessagingMessages, newMessagingMessages, groups, senders); + newHistoricMessagingMessages, newMessagingMessages, groups, senders, + conversationHeaderData); } /** @@ -457,7 +477,9 @@ public class ConversationLayout extends FrameLayout } final MessagingData messagingData = - parseMessagingData(extras, /* usePrecomputedText= */ true); + parseMessagingData(extras, + /* usePrecomputedText= */ true, + /*includeConversationIcon=*/true); return () -> { finalizeInflate(messagingData.getHistoricMessagingMessages()); @@ -536,11 +558,10 @@ public class ConversationLayout extends FrameLayout mMessages = messagingData.getNewMessagingMessages(); mHistoricMessages = messagingData.getHistoricMessagingMessages(); - updateHistoricMessageVisibility(); updateTitleAndNamesDisplay(); - updateConversationLayout(); + updateConversationLayout(messagingData); // Recycle everything at the end of the update, now that we know it's no longer needed. for (MessagingLinearLayout.MessagingChild child : mToRecycle) { @@ -552,7 +573,31 @@ public class ConversationLayout extends FrameLayout /** * Update the layout according to the data provided (i.e mIsOneToOne, expanded etc); */ - private void updateConversationLayout() { + private void updateConversationLayout(MessagingData messagingData) { + if (!Flags.conversationStyleSetAvatarAsync()) { + computeAndSetConversationAvatarAndName(); + } else { + ConversationHeaderData conversationHeaderData = + messagingData.getConversationHeaderData(); + if (conversationHeaderData == null) { + conversationHeaderData = loadConversationHeaderData(mIsOneToOne, + mConversationTitle, mShortcutIcon, mLargeIcon, mMessages, mUser, + messagingData.getGroups(), + mLayoutColor); + } + setConversationAvatarAndNameFromData(conversationHeaderData); + } + + updateAppName(); + updateIconPositionAndSize(); + updateImageMessages(); + updatePaddingsBasedOnContentAvailability(); + updateActionListPadding(); + updateAppNameDividerVisibility(); + } + + @Deprecated + private void computeAndSetConversationAvatarAndName() { // Set avatar and name CharSequence conversationText = mConversationTitle; mConversationIcon = mShortcutIcon; @@ -603,12 +648,43 @@ public class ConversationLayout extends FrameLayout // Update if the groups can hide the sender if they are first (applies to 1:1 conversations) // This needs to happen after all of the above o update all of the groups mPeopleHelper.maybeHideFirstSenderName(mGroups, mIsOneToOne, conversationText); - updateAppName(); - updateIconPositionAndSize(); - updateImageMessages(); - updatePaddingsBasedOnContentAvailability(); - updateActionListPadding(); - updateAppNameDividerVisibility(); + } + + private void setConversationAvatarAndNameFromData( + ConversationHeaderData conversationHeaderData) { + final OneToOneConversationAvatarData oneToOneConversationDrawable; + final GroupConversationAvatarData groupConversationAvatarData; + final ConversationAvatarData conversationAvatar = + conversationHeaderData.getConversationAvatar(); + if (conversationAvatar instanceof OneToOneConversationAvatarData) { + oneToOneConversationDrawable = + ((OneToOneConversationAvatarData) conversationAvatar); + groupConversationAvatarData = null; + } else { + oneToOneConversationDrawable = null; + groupConversationAvatarData = ((GroupConversationAvatarData) conversationAvatar); + } + + if (oneToOneConversationDrawable != null) { + mConversationIconView.setVisibility(VISIBLE); + mConversationFacePile.setVisibility(GONE); + mConversationIconView.setImageDrawable(oneToOneConversationDrawable.mDrawable); + } else { + mConversationIconView.setVisibility(GONE); + // This will also inflate it! + mConversationFacePile.setVisibility(VISIBLE); + // rebind the value to the inflated view instead of the stub + mConversationFacePile = findViewById(R.id.conversation_face_pile); + bindFacePile(groupConversationAvatarData); + } + CharSequence conversationText = conversationHeaderData.getConversationText(); + if (TextUtils.isEmpty(conversationText)) { + conversationText = mIsOneToOne ? mFallbackChatName : mFallbackGroupChatName; + } + mConversationText.setText(conversationText); + // Update if the groups can hide the sender if they are first (applies to 1:1 conversations) + // This needs to happen after all of the above o update all of the groups + mPeopleHelper.maybeHideFirstSenderName(mGroups, mIsOneToOne, conversationText); } private void updateActionListPadding() { @@ -675,7 +751,12 @@ public class ConversationLayout extends FrameLayout topView.setImageIcon(secondLastIcon); } + @Deprecated private void bindFacePile() { + bindFacePile(null); + } + + private void bindFacePile(@Nullable GroupConversationAvatarData groupConversationAvatarData) { ImageView bottomBackground = mConversationFacePile.findViewById( R.id.conversation_face_pile_bottom_background); ImageView bottomView = mConversationFacePile.findViewById( @@ -683,7 +764,13 @@ public class ConversationLayout extends FrameLayout ImageView topView = mConversationFacePile.findViewById( R.id.conversation_face_pile_top); - bindFacePile(bottomBackground, bottomView, topView); + if (groupConversationAvatarData == null) { + bindFacePile(bottomBackground, bottomView, topView); + } else { + bindFacePileWithDrawable(bottomBackground, bottomView, topView, + groupConversationAvatarData); + + } int conversationAvatarSize; int facepileAvatarSize; @@ -718,6 +805,13 @@ public class ConversationLayout extends FrameLayout bottomBackground.setLayoutParams(layoutParams); } + private void bindFacePileWithDrawable(ImageView bottomBackground, ImageView bottomView, + ImageView topView, GroupConversationAvatarData groupConversationAvatarData) { + applyNotificationBackgroundColor(bottomBackground); + bottomView.setImageDrawable(groupConversationAvatarData.mLastIcon); + topView.setImageDrawable(groupConversationAvatarData.mSecondLastIcon); + } + private void updateAppName() { mAppName.setVisibility(mIsCollapsed ? GONE : VISIBLE); } @@ -789,22 +883,62 @@ public class ConversationLayout extends FrameLayout mMessagingLinearLayout.getPaddingBottom()); } + /** + * async version of {@link ConversationLayout#setLargeIcon} + */ @RemotableViewMethod + public Runnable setLargeIconAsync(Icon largeIcon) { + if (!Flags.conversationStyleSetAvatarAsync()) { + return () -> setLargeIcon(largeIcon); + } + + mLargeIcon = largeIcon; + return NotificationRunnables.NOOP; + } + + @RemotableViewMethod(asyncImpl = "setLargeIconAsync") public void setLargeIcon(Icon largeIcon) { mLargeIcon = largeIcon; } + /** + * async version of {@link ConversationLayout#setShortcutIcon} + */ @RemotableViewMethod + public Runnable setShortcutIconAsync(Icon shortcutIcon) { + if (!Flags.conversationStyleSetAvatarAsync()) { + return () -> setShortcutIcon(shortcutIcon); + } + + mShortcutIcon = shortcutIcon; + return NotificationRunnables.NOOP; + } + + @RemotableViewMethod(asyncImpl = "setShortcutIconAsync") public void setShortcutIcon(Icon shortcutIcon) { mShortcutIcon = shortcutIcon; } /** + * async version of {@link ConversationLayout#setConversationTitle} + */ + @RemotableViewMethod + public Runnable setConversationTitleAsync(CharSequence conversationTitle) { + if (!Flags.conversationStyleSetAvatarAsync()) { + return () -> setConversationTitle(conversationTitle); + } + + // Remove formatting from the title. + mConversationTitle = conversationTitle != null ? conversationTitle.toString() : null; + return NotificationRunnables.NOOP; + } + + /** * Sets the conversation title of this conversation. * * @param conversationTitle the conversation title */ - @RemotableViewMethod + @RemotableViewMethod(asyncImpl = "setConversationTitleAsync") public void setConversationTitle(CharSequence conversationTitle) { // Remove formatting from the title. mConversationTitle = conversationTitle != null ? conversationTitle.toString() : null; @@ -888,12 +1022,37 @@ public class ConversationLayout extends FrameLayout } } + /** + * async version of {@link ConversationLayout#setLayoutColor} + */ @RemotableViewMethod + public Runnable setLayoutColorAsync(int color) { + if (!Flags.conversationStyleSetAvatarAsync()) { + return () -> setLayoutColor(color); + } + + mLayoutColor = color; + return NotificationRunnables.NOOP; + } + + @RemotableViewMethod(asyncImpl = "setLayoutColorAsync") public void setLayoutColor(int color) { mLayoutColor = color; } + /** + * async version of {@link ConversationLayout#setIsOneToOne} + */ @RemotableViewMethod + public Runnable setIsOneToOneAsync(boolean oneToOne) { + if (!Flags.conversationStyleSetAvatarAsync()) { + return () -> setIsOneToOne(oneToOne); + } + mIsOneToOne = oneToOne; + return NotificationRunnables.NOOP; + } + + @RemotableViewMethod(asyncImpl = "setIsOneToOneAsync") public void setIsOneToOne(boolean oneToOne) { mIsOneToOne = oneToOne; } @@ -1022,6 +1181,125 @@ public class ConversationLayout extends FrameLayout return person == null ? null : person.getKey() == null ? person.getName() : person.getKey(); } + private ConversationHeaderData loadConversationHeaderData(boolean isOneToOne, + CharSequence conversationTitle, Icon shortcutIcon, Icon largeIcon, + List<MessagingMessage> messages, + Person user, + List<List<MessagingMessage>> groups, int layoutColor) { + Icon conversationIcon = shortcutIcon; + CharSequence conversationText = conversationTitle; + final CharSequence userKey = getKey(user); + if (isOneToOne) { + for (int i = messages.size() - 1; i >= 0; i--) { + final Notification.MessagingStyle.Message message = messages.get(i).getMessage(); + final Person sender = message.getSenderPerson(); + final CharSequence senderKey = getKey(sender); + if ((sender != null && senderKey != userKey) || i == 0) { + if (conversationText == null || conversationText.length() == 0) { + conversationText = sender != null ? sender.getName() : ""; + } + if (conversationIcon == null) { + conversationIcon = sender != null ? sender.getIcon() + : mPeopleHelper.createAvatarSymbol(conversationText, "", + layoutColor); + } + break; + } + } + } + + if (conversationIcon == null) { + conversationIcon = largeIcon; + } + + if (isOneToOne || conversationIcon != null) { + return new ConversationHeaderData( + conversationText, + new OneToOneConversationAvatarData( + resolveAvatarImage(conversationIcon))); + } + + final List<List<Notification.MessagingStyle.Message>> groupMessages = new ArrayList<>(); + for (int i = 0; i < groups.size(); i++) { + final List<Notification.MessagingStyle.Message> groupMessage = new ArrayList<>(); + for (int j = 0; j < groups.get(i).size(); j++) { + groupMessage.add(groups.get(i).get(j).getMessage()); + } + groupMessages.add(groupMessage); + } + + final PeopleHelper.NameToPrefixMap nameToPrefixMap = + mPeopleHelper.mapUniqueNamesToPrefixWithGroupList(groupMessages); + + Icon lastIcon = null; + Icon secondLastIcon = null; + + CharSequence lastKey = null; + + for (int i = groups.size() - 1; i >= 0; i--) { + final Notification.MessagingStyle.Message message = groups.get(i).get(0).getMessage(); + final Person sender = + message.getSenderPerson() != null ? message.getSenderPerson() : user; + final CharSequence senderKey = getKey(sender); + final boolean notUser = senderKey != userKey; + final boolean notIncluded = senderKey != lastKey; + + if ((notUser && notIncluded) || (i == 0 && lastKey == null)) { + if (lastIcon == null) { + if (sender.getIcon() != null) { + lastIcon = sender.getIcon(); + } else { + final CharSequence senderName = + sender.getName() != null ? sender.getName() : ""; + lastIcon = mPeopleHelper.createAvatarSymbol( + senderName, nameToPrefixMap.getPrefix(senderName), + layoutColor); + } + lastKey = senderKey; + } else { + if (sender.getIcon() != null) { + secondLastIcon = sender.getIcon(); + } else { + final CharSequence senderName = + sender.getName() != null ? sender.getName() : ""; + secondLastIcon = mPeopleHelper.createAvatarSymbol( + senderName, nameToPrefixMap.getPrefix(senderName), + layoutColor); + } + break; + } + } + } + + if (lastIcon == null) { + lastIcon = mPeopleHelper.createAvatarSymbol( + "", "", layoutColor); + } + + if (secondLastIcon == null) { + secondLastIcon = mPeopleHelper.createAvatarSymbol( + "", "", layoutColor); + } + + return new ConversationHeaderData( + conversationText, + new GroupConversationAvatarData(resolveAvatarImage(lastIcon), + resolveAvatarImage(secondLastIcon))); + } + + /** + * {@link ImageResolver#loadImage(Uri)} accepts Uri to load images. However Conversation Avatars + * are received as Icon. So, we can't make use of ImageResolver. + */ + @Nullable + private Drawable resolveAvatarImage(Icon conversationIcon) { + try { + return LocalImageResolver.resolveImage(conversationIcon, getContext()); + } catch (Exception ex) { + return null; + } + } + /** * Creates new messages, reusing existing ones if they are available. * diff --git a/core/java/com/android/internal/widget/MessagingData.java b/core/java/com/android/internal/widget/MessagingData.java index 42de60e08e18..fb1f28fb8ef3 100644 --- a/core/java/com/android/internal/widget/MessagingData.java +++ b/core/java/com/android/internal/widget/MessagingData.java @@ -16,6 +16,7 @@ package com.android.internal.widget; +import android.annotation.Nullable; import android.app.Person; import java.util.List; @@ -32,6 +33,8 @@ final class MessagingData { private final List<Person> mSenders; private final int mUnreadCount; + private ConversationHeaderData mConversationHeaderData; + MessagingData(Person user, boolean showSpinner, List<MessagingMessage> historicMessagingMessages, List<MessagingMessage> newMessagingMessages, List<List<MessagingMessage>> groups, @@ -39,7 +42,7 @@ final class MessagingData { this(user, showSpinner, /* unreadCount= */0, historicMessagingMessages, newMessagingMessages, groups, - senders); + senders, null); } MessagingData(Person user, boolean showSpinner, @@ -47,7 +50,8 @@ final class MessagingData { List<MessagingMessage> historicMessagingMessages, List<MessagingMessage> newMessagingMessages, List<List<MessagingMessage>> groups, - List<Person> senders) { + List<Person> senders, + @Nullable ConversationHeaderData conversationHeaderData) { mUser = user; mShowSpinner = showSpinner; mUnreadCount = unreadCount; @@ -55,6 +59,7 @@ final class MessagingData { mNewMessagingMessages = newMessagingMessages; mGroups = groups; mSenders = senders; + mConversationHeaderData = conversationHeaderData; } public Person getUser() { @@ -84,4 +89,9 @@ final class MessagingData { public List<List<MessagingMessage>> getGroups() { return mGroups; } + + @Nullable + public ConversationHeaderData getConversationHeaderData() { + return mConversationHeaderData; + } } diff --git a/core/java/com/android/internal/widget/NotificationRunnables.java b/core/java/com/android/internal/widget/NotificationRunnables.java new file mode 100644 index 000000000000..cb7ae617fbea --- /dev/null +++ b/core/java/com/android/internal/widget/NotificationRunnables.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.widget; + +public final class NotificationRunnables { + public static final Runnable NOOP = () -> { + }; +} diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 070d07c22fbc..52237989f059 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -18,6 +18,8 @@ //#define LOG_NDEBUG 0 #define LOG_TAG "AudioSystem-JNI" +#include <android/binder_ibinder_jni.h> +#include <android/binder_libbinder.h> #include <android/media/AudioVibratorInfo.h> #include <android/media/INativeSpatializerCallback.h> #include <android/media/ISpatializer.h> @@ -25,6 +27,7 @@ #include <android_media_audiopolicy.h> #include <android_os_Parcel.h> #include <audiomanager/AudioManager.h> +#include <binder/IBinder.h> #include <jni.h> #include <media/AidlConversion.h> #include <media/AudioContainers.h> @@ -150,13 +153,14 @@ static struct { static jclass gAudioMixClass; static jmethodID gAudioMixCstor; static struct { - jfieldID mRule; - jfieldID mFormat; - jfieldID mRouteFlags; - jfieldID mDeviceType; - jfieldID mDeviceAddress; - jfieldID mMixType; - jfieldID mCallbackFlags; + jfieldID mRule; + jfieldID mFormat; + jfieldID mRouteFlags; + jfieldID mDeviceType; + jfieldID mDeviceAddress; + jfieldID mMixType; + jfieldID mCallbackFlags; + jfieldID mToken; } gAudioMixFields; static jclass gAudioFormatClass; @@ -2300,11 +2304,15 @@ static jint convertAudioMixFromNative(JNIEnv *env, jobject *jAudioMix, const Aud if (status != AUDIO_JAVA_SUCCESS) { return status; } + std::unique_ptr<AIBinder, decltype(&AIBinder_decStrong)> aiBinder(AIBinder_fromPlatformBinder( + nAudioMix.mToken), + &AIBinder_decStrong); + jobject jBinderToken = AIBinder_toJavaBinder(env, aiBinder.get()); jstring deviceAddress = env->NewStringUTF(nAudioMix.mDeviceAddress.c_str()); *jAudioMix = env->NewObject(gAudioMixClass, gAudioMixCstor, jAudioMixingRule, jAudioFormat, nAudioMix.mRouteFlags, nAudioMix.mCbFlags, nAudioMix.mDeviceType, - deviceAddress); + deviceAddress, jBinderToken); return AUDIO_JAVA_SUCCESS; } @@ -2333,6 +2341,12 @@ static jint convertAudioMixToNative(JNIEnv *env, AudioMix *nAudioMix, const jobj nAudioMix->mVoiceCommunicationCaptureAllowed = env->GetBooleanField(jRule, gAudioMixingRuleFields.mVoiceCommunicationCaptureAllowed); + jobject jToken = env->GetObjectField(jAudioMix, gAudioMixFields.mToken); + + std::unique_ptr<AIBinder, decltype(&AIBinder_decStrong)> + aiBinder(AIBinder_fromJavaBinder(env, jToken), &AIBinder_decStrong); + nAudioMix->mToken = AIBinder_toPlatformBinder(aiBinder.get()); + jint status = convertAudioMixingRuleToNative(env, jRule, &(nAudioMix->mCriteria)); env->DeleteLocalRef(jRule); @@ -3659,9 +3673,10 @@ int register_android_media_AudioSystem(JNIEnv *env) jclass audioMixClass = FindClassOrDie(env, "android/media/audiopolicy/AudioMix"); gAudioMixClass = MakeGlobalRefOrDie(env, audioMixClass); if (audio_flags::audio_mix_test_api()) { - gAudioMixCstor = GetMethodIDOrDie(env, audioMixClass, "<init>", - "(Landroid/media/audiopolicy/AudioMixingRule;Landroid/" - "media/AudioFormat;IIILjava/lang/String;)V"); + gAudioMixCstor = + GetMethodIDOrDie(env, audioMixClass, "<init>", + "(Landroid/media/audiopolicy/AudioMixingRule;Landroid/" + "media/AudioFormat;IIILjava/lang/String;Landroid/os/IBinder;)V"); } gAudioMixFields.mRule = GetFieldIDOrDie(env, audioMixClass, "mRule", "Landroid/media/audiopolicy/AudioMixingRule;"); @@ -3673,6 +3688,7 @@ int register_android_media_AudioSystem(JNIEnv *env) "Ljava/lang/String;"); gAudioMixFields.mMixType = GetFieldIDOrDie(env, audioMixClass, "mMixType", "I"); gAudioMixFields.mCallbackFlags = GetFieldIDOrDie(env, audioMixClass, "mCallbackFlags", "I"); + gAudioMixFields.mToken = GetFieldIDOrDie(env, audioMixClass, "mToken", "Landroid/os/IBinder;"); jclass audioFormatClass = FindClassOrDie(env, "android/media/AudioFormat"); gAudioFormatClass = MakeGlobalRefOrDie(env, audioFormatClass); diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml index d1b1af3e77ab..490f0883fbfb 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml @@ -16,6 +16,7 @@ --> <com.android.wm.shell.windowdecor.WindowDecorLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/desktop_mode_caption" android:layout_width="match_parent" @@ -27,6 +28,8 @@ android:id="@+id/open_menu_button" android:layout_width="wrap_content" android:layout_height="match_parent" + android:tint="?androidprv:attr/materialColorOnSurface" + android:background="?android:selectableItemBackground" android:orientation="horizontal" android:clickable="true" android:focusable="true" @@ -78,7 +81,9 @@ android:id="@+id/maximize_button_view" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="end"/> + android:layout_gravity="end" + android:clickable="true" + android:focusable="true" /> <ImageButton android:id="@+id/close_window" @@ -86,9 +91,10 @@ android:layout_height="40dp" android:padding="4dp" android:layout_marginEnd="8dp" + android:tint="?androidprv:attr/materialColorOnSurface" + android:background="?android:selectableItemBackgroundBorderless" android:contentDescription="@string/close_button_text" android:src="@drawable/decor_close_button_dark" android:scaleType="fitCenter" - android:gravity="end" - android:background="@null"/> + android:gravity="end"/> </com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml index bb6efcec1a70..e0057fe64fd2 100644 --- a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml +++ b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml @@ -14,7 +14,8 @@ ~ limitations under the License. --> -<merge xmlns:android="http://schemas.android.com/apk/res/android"> +<merge xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> <ProgressBar android:id="@+id/progress_bar" style="?android:attr/progressBarStyleHorizontal" @@ -30,7 +31,8 @@ android:layout_height="40dp" android:padding="9dp" android:contentDescription="@string/maximize_button_text" + android:tint="?androidprv:attr/materialColorOnSurface" + android:background="?android:selectableItemBackgroundBorderless" android:src="@drawable/decor_desktop_mode_maximize_button_dark" - android:scaleType="fitCenter" - android:background="@drawable/rounded_button"/> + android:scaleType="fitCenter" /> </merge>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index cbfa74e9332b..48e6428524ae 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -101,6 +101,10 @@ <dimen name="split_divider_bar_width">10dp</dimen> <dimen name="split_divider_corner_size">42dp</dimen> + <!-- The distance from the edge of the screen to invoke splitscreen when the user is dragging + an intent that can be launched into split. --> + <dimen name="drag_launchable_intent_edge_margin">48dp</dimen> + <!-- One-Handed Mode --> <!-- Threshold for dragging distance to enable one-handed mode --> <dimen name="gestures_onehanded_drag_threshold">20dp</dimen> 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 15350fb19209..96aaf02cb5e3 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 @@ -1799,11 +1799,12 @@ public class BubbleController implements ConfigurationChangeListener, @Override public void removeBubble(Bubble removedBubble) { if (mLayerView != null) { - mLayerView.removeBubble(removedBubble); - if (!mBubbleData.hasBubbles() && !isStackExpanded()) { - mLayerView.setVisibility(INVISIBLE); - removeFromWindowManagerMaybe(); - } + mLayerView.removeBubble(removedBubble, () -> { + if (!mBubbleData.hasBubbles() && !isStackExpanded()) { + mLayerView.setVisibility(INVISIBLE); + removeFromWindowManagerMaybe(); + } + }); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index 78a41f759d96..42799d975e1b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -242,13 +242,17 @@ public class BubbleBarLayerView extends FrameLayout } /** Removes the given {@code bubble}. */ - public void removeBubble(Bubble bubble) { + public void removeBubble(Bubble bubble, Runnable endAction) { + Runnable cleanUp = () -> { + bubble.cleanupViews(); + endAction.run(); + }; if (mBubbleData.getBubbles().isEmpty()) { // we're removing the last bubble. collapse the expanded view and cleanup bubble views // at the end. - collapse(bubble::cleanupViews); + collapse(cleanUp); } else { - bubble.cleanupViews(); + cleanUp.run(); } } @@ -264,6 +268,9 @@ public class BubbleBarLayerView extends FrameLayout */ public void collapse(@Nullable Runnable endAction) { if (!mIsExpanded) { + if (endAction != null) { + endAction.run(); + } return; } mIsExpanded = false; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java index 1afbdf90eac0..7da1b23dd5b1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java @@ -59,7 +59,6 @@ import androidx.annotation.BinderThread; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; -import com.android.internal.logging.InstanceId; import com.android.internal.logging.UiEventLogger; import com.android.internal.protolog.common.ProtoLog; import com.android.launcher3.icons.IconProvider; @@ -316,12 +315,11 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll return false; } // TODO(b/290391688): Also update the session data with task stack changes - InstanceId loggerSessionId = mLogger.logStart(event); - pd.activeDragCount++; - pd.dragSession = new DragSession(mContext, ActivityTaskManager.getInstance(), + pd.dragSession = new DragSession(ActivityTaskManager.getInstance(), mDisplayController.getDisplayLayout(displayId), event.getClipData()); pd.dragSession.update(); - pd.dragLayout.prepare(pd.dragSession, loggerSessionId); + pd.activeDragCount++; + pd.dragLayout.prepare(pd.dragSession, mLogger.logStart(pd.dragSession)); setDropTargetWindowVisibility(pd, View.VISIBLE); notifyListeners(l -> { l.onDragStarted(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java index 2a7dd5aeb341..75b126c47690 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java @@ -53,17 +53,21 @@ public class DragAndDropEventLogger { /** * Logs the start of a drag. */ - public InstanceId logStart(DragEvent event) { - final ClipDescription description = event.getClipDescription(); - final ClipData data = event.getClipData(); - final ClipData.Item item = data.getItemAt(0); - mInstanceId = item.getIntent().getParcelableExtra( - ClipDescription.EXTRA_LOGGING_INSTANCE_ID); + public InstanceId logStart(DragSession session) { + mInstanceId = session.appData != null + ? session.appData.getParcelableExtra(ClipDescription.EXTRA_LOGGING_INSTANCE_ID, + InstanceId.class) + : null; if (mInstanceId == null) { mInstanceId = mIdSequence.newInstanceId(); } - mActivityInfo = item.getActivityInfo(); - log(getStartEnum(description), mActivityInfo); + mActivityInfo = session.activityInfo; + if (session.appData != null) { + log(getStartEnum(session.getClipDescription()), mActivityInfo); + } else { + // TODO(b/255649902): Update this once we have a new enum + log(DragAndDropUiEventEnum.GLOBAL_APP_DRAG_START_ACTIVITY, mActivityInfo); + } return mInstanceId; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java index a31a773a76a0..eb82da8a8e9f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java @@ -29,6 +29,8 @@ import static android.content.Intent.EXTRA_PACKAGE_NAME; import static android.content.Intent.EXTRA_SHORTCUT_ID; import static android.content.Intent.EXTRA_TASK_ID; import static android.content.Intent.EXTRA_USER; +import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; @@ -52,9 +54,11 @@ import android.content.pm.LauncherApps; import android.graphics.Insets; import android.graphics.Rect; import android.graphics.RectF; +import android.os.Build; import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; +import android.util.Log; import android.util.Slog; import androidx.annotation.IntDef; @@ -63,8 +67,10 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.internal.logging.InstanceId; +import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; import java.lang.annotation.Retention; @@ -104,7 +110,9 @@ public class DragAndDropPolicy { void start(DragSession session, InstanceId loggerSessionId) { mLoggerSessionId = loggerSessionId; mSession = session; - RectF disallowHitRegion = (RectF) mSession.dragData.getExtra(EXTRA_DISALLOW_HIT_REGION); + RectF disallowHitRegion = mSession.appData != null + ? (RectF) mSession.appData.getExtra(EXTRA_DISALLOW_HIT_REGION) + : null; if (disallowHitRegion == null) { mDisallowHitRegion.setEmpty(); } else { @@ -223,7 +231,7 @@ public class DragAndDropPolicy { } @VisibleForTesting - void handleDrop(Target target, ClipData data) { + void handleDrop(Target target) { if (target == null || !mTargets.contains(target)) { return; } @@ -238,41 +246,77 @@ public class DragAndDropPolicy { mSplitScreen.onDroppedToSplit(position, mLoggerSessionId); } - final ClipDescription description = data.getDescription(); - final Intent dragData = mSession.dragData; - startClipDescription(description, dragData, position); + if (mSession.appData != null) { + launchApp(mSession, position); + } else { + launchIntent(mSession, position); + } } - private void startClipDescription(ClipDescription description, Intent intent, - @SplitPosition int position) { + /** + * Launches an app provided by SysUI. + */ + private void launchApp(DragSession session, @SplitPosition int position) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching app data at position=%d", + position); + final ClipDescription description = session.getClipDescription(); final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK); final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT); final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic(); baseActivityOpts.setDisallowEnterPictureInPictureWhileLaunching(true); final Bundle opts = baseActivityOpts.toBundle(); - if (intent.hasExtra(EXTRA_ACTIVITY_OPTIONS)) { - opts.putAll(intent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS)); + if (session.appData.hasExtra(EXTRA_ACTIVITY_OPTIONS)) { + opts.putAll(session.appData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS)); } // Put BAL flags to avoid activity start aborted. opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true); opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true); - final UserHandle user = intent.getParcelableExtra(EXTRA_USER); + final UserHandle user = session.appData.getParcelableExtra(EXTRA_USER); if (isTask) { - final int taskId = intent.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID); + final int taskId = session.appData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID); mStarter.startTask(taskId, position, opts); } else if (isShortcut) { - final String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME); - final String id = intent.getStringExtra(EXTRA_SHORTCUT_ID); + final String packageName = session.appData.getStringExtra(EXTRA_PACKAGE_NAME); + final String id = session.appData.getStringExtra(EXTRA_SHORTCUT_ID); mStarter.startShortcut(packageName, id, position, opts, user); } else { - final PendingIntent launchIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT); + final PendingIntent launchIntent = + session.appData.getParcelableExtra(EXTRA_PENDING_INTENT); + if (Build.IS_DEBUGGABLE) { + if (!user.equals(launchIntent.getCreatorUserHandle())) { + Log.e(TAG, "Expected app intent's EXTRA_USER to match pending intent user"); + } + } mStarter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */, position, opts); } } /** + * Launches an intent sender provided by an application. + */ + private void launchIntent(DragSession session, @SplitPosition int position) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching intent at position=%d", + position); + final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic(); + baseActivityOpts.setDisallowEnterPictureInPictureWhileLaunching(true); + // TODO(b/255649902): Rework this so that SplitScreenController can always use the options + // instead of a fillInIntent since it's assuming that the PendingIntent is mutable + baseActivityOpts.setPendingIntentLaunchFlags(FLAG_ACTIVITY_NEW_TASK + | FLAG_ACTIVITY_MULTIPLE_TASK); + + final Bundle opts = baseActivityOpts.toBundle(); + // Put BAL flags to avoid activity start aborted. + opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true); + opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true); + + mStarter.startIntent(session.launchableIntent, + session.launchableIntent.getCreatorUserHandle().getIdentifier(), + null /* fillIntent */, position, opts); + } + + /** * Interface for actually committing the task launches. */ public interface Starter { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java index 619f624ff3bc..ecb53dc17a48 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java @@ -22,6 +22,8 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS; import static android.content.pm.ActivityInfo.CONFIG_UI_MODE; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME; +import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; @@ -38,12 +40,15 @@ import android.app.ActivityManager; import android.app.StatusBarManager; import android.content.Context; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Color; import android.graphics.Insets; import android.graphics.Rect; +import android.graphics.Region; import android.graphics.drawable.Drawable; import android.view.DragEvent; import android.view.SurfaceControl; +import android.view.ViewTreeObserver; import android.view.WindowInsets; import android.view.WindowInsets.Type; import android.widget.LinearLayout; @@ -65,7 +70,8 @@ import java.util.ArrayList; /** * Coordinates the visible drop targets for the current drag within a single display. */ -public class DragLayout extends LinearLayout { +public class DragLayout extends LinearLayout + implements ViewTreeObserver.OnComputeInternalInsetsListener { // While dragging the status bar is hidden. private static final int HIDE_STATUS_BAR_FLAGS = StatusBarManager.DISABLE_NOTIFICATION_ICONS @@ -90,7 +96,9 @@ public class DragLayout extends LinearLayout { private int mDisplayMargin; private int mDividerSize; + private int mLaunchIntentEdgeMargin; private Insets mInsets = Insets.NONE; + private Region mTouchableRegion; private boolean mIsShowing; private boolean mHasDropped; @@ -106,10 +114,11 @@ public class DragLayout extends LinearLayout { mStatusBarManager = context.getSystemService(StatusBarManager.class); mLastConfiguration.setTo(context.getResources().getConfiguration()); - mDisplayMargin = context.getResources().getDimensionPixelSize( - R.dimen.drop_layout_display_margin); - mDividerSize = context.getResources().getDimensionPixelSize( - R.dimen.split_divider_bar_width); + final Resources res = context.getResources(); + mDisplayMargin = res.getDimensionPixelSize(R.dimen.drop_layout_display_margin); + mDividerSize = res.getDimensionPixelSize(R.dimen.split_divider_bar_width); + mLaunchIntentEdgeMargin = + res.getDimensionPixelSize(R.dimen.drag_launchable_intent_edge_margin); // Always use LTR because we assume dropZoneView1 is on the left and 2 is on the right when // showing the highlight. @@ -131,6 +140,66 @@ public class DragLayout extends LinearLayout { } @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mTouchableRegion = Region.obtain(); + getViewTreeObserver().addOnComputeInternalInsetsListener(this); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + getViewTreeObserver().removeOnComputeInternalInsetsListener(this); + mTouchableRegion.recycle(); + } + + @Override + public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inOutInfo) { + if (mSession != null && mSession.launchableIntent != null) { + inOutInfo.touchableRegion.set(mTouchableRegion); + inOutInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION); + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + updateTouchableRegion(); + } + + /** + * Updates the touchable region, this should be called after any configuration changes have + * been applied. + */ + private void updateTouchableRegion() { + mTouchableRegion.setEmpty(); + if (mSession != null && mSession.launchableIntent != null) { + final int width = getMeasuredWidth(); + final int height = getMeasuredHeight(); + if (mIsLeftRightSplit) { + mTouchableRegion.union( + new Rect(0, 0, mInsets.left + mLaunchIntentEdgeMargin, height)); + mTouchableRegion.union( + new Rect(width - mInsets.right - mLaunchIntentEdgeMargin, 0, width, + height)); + } else { + mTouchableRegion.union( + new Rect(0, 0, width, mInsets.top + mLaunchIntentEdgeMargin)); + mTouchableRegion.union( + new Rect(0, height - mInsets.bottom - mLaunchIntentEdgeMargin, width, + height)); + } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, + "Updating drag layout width=%d height=%d touchable region=%s", + width, height, mTouchableRegion); + + // Reapply insets to update the touchable region + requestApplyInsets(); + } + } + + + @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { mInsets = insets.getInsets(Type.tappableElement() | Type.displayCutout()); recomputeDropTargets(); @@ -164,6 +233,7 @@ public class DragLayout extends LinearLayout { mDropZoneView2.onThemeChange(); } mLastConfiguration.setTo(newConfig); + requestLayout(); } private void updateContainerMarginsForSingleTask() { @@ -242,6 +312,7 @@ public class DragLayout extends LinearLayout { mSplitScreenController.getStageBounds(topOrLeftBounds, bottomOrRightBounds); updateDropZoneSizes(topOrLeftBounds, bottomOrRightBounds); } + requestLayout(); } private void updateDropZoneSizesForSingleTask() { @@ -392,7 +463,7 @@ public class DragLayout extends LinearLayout { mHasDropped = true; // Process the drop - mPolicy.handleDrop(mCurrentTarget, event.getClipData()); + mPolicy.handleDrop(mCurrentTarget); // Start animating the drop UI out with the drag surface hide(event, dropCompleteCallback); @@ -505,5 +576,7 @@ public class DragLayout extends LinearLayout { pw.println(innerPrefix + "mIsShowing=" + mIsShowing); pw.println(innerPrefix + "mHasDropped=" + mHasDropped); pw.println(innerPrefix + "mCurrentTarget=" + mCurrentTarget); + pw.println(innerPrefix + "mInsets=" + mInsets); + pw.println(innerPrefix + "mTouchableRegion=" + mTouchableRegion); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java index 353d702e5bc4..8f1bc59af1ef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java @@ -21,12 +21,15 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.app.ActivityManager; import android.app.ActivityTaskManager; +import android.app.PendingIntent; import android.app.WindowConfiguration; import android.content.ClipData; -import android.content.Context; +import android.content.ClipDescription; import android.content.Intent; import android.content.pm.ActivityInfo; +import androidx.annotation.Nullable; + import com.android.wm.shell.common.DisplayLayout; import java.util.List; @@ -39,7 +42,18 @@ public class DragSession { private final ClipData mInitialDragData; final DisplayLayout displayLayout; - Intent dragData; + // The activity info associated with the activity in the appData or the launchableIntent + @Nullable + ActivityInfo activityInfo; + // The intent bundle that includes data about an app-type drag that is started by + // Launcher/SysUI. Only one of appDragData OR launchableIntent will be non-null for a session. + @Nullable + Intent appData; + // A launchable intent that is specified in the ClipData directly. + // Only one of appDragData OR launchableIntent will be non-null for a session. + @Nullable + PendingIntent launchableIntent; + // Stores the current running task at the time that the drag was initiated ActivityManager.RunningTaskInfo runningTaskInfo; @WindowConfiguration.WindowingMode int runningTaskWinMode = WINDOWING_MODE_UNDEFINED; @@ -47,7 +61,7 @@ public class DragSession { int runningTaskActType = ACTIVITY_TYPE_STANDARD; boolean dragItemSupportsSplitscreen; - DragSession(Context context, ActivityTaskManager activityTaskManager, + DragSession(ActivityTaskManager activityTaskManager, DisplayLayout dispLayout, ClipData data) { mActivityTaskManager = activityTaskManager; mInitialDragData = data; @@ -55,6 +69,14 @@ public class DragSession { } /** + * Returns the clip description associated with the drag. + * @return + */ + ClipDescription getClipDescription() { + return mInitialDragData.getDescription(); + } + + /** * Updates the session data based on the current state of the system. */ void update() { @@ -67,9 +89,11 @@ public class DragSession { runningTaskActType = task.getActivityType(); } - final ActivityInfo info = mInitialDragData.getItemAt(0).getActivityInfo(); - dragItemSupportsSplitscreen = info == null - || ActivityInfo.isResizeableMode(info.resizeMode); - dragData = mInitialDragData.getItemAt(0).getIntent(); + activityInfo = mInitialDragData.getItemAt(0).getActivityInfo(); + // TODO: This should technically check & respect config_supportsNonResizableMultiWindow + dragItemSupportsSplitscreen = activityInfo == null + || ActivityInfo.isResizeableMode(activityInfo.resizeMode); + appData = mInitialDragData.getItemAt(0).getIntent(); + launchableIntent = DragUtils.getLaunchIntent(mInitialDragData); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java index f7bcc9477aa1..24f8e186bf76 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java @@ -36,8 +36,21 @@ public class DragUtils { * Returns whether we can handle this particular drag. */ public static boolean canHandleDrag(DragEvent event) { - return event.getClipData().getItemCount() > 0 - && (isAppDrag(event.getClipDescription())); + if (event.getClipData().getItemCount() <= 0) { + // No clip data, ignore this drag + return false; + } + if (isAppDrag(event.getClipDescription())) { + // Clip data contains an app drag initiated from SysUI, handle it + return true; + } + if (com.android.window.flags.Flags.delegateUnhandledDrags() + && getLaunchIntent(event) != null) { + // Clip data contains a launchable intent drag, handle it + return true; + } + // Otherwise ignore + return false; } /** 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 3c374677b949..952e2d4b3b9a 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 @@ -808,6 +808,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Override public void startIntent(PendingIntent intent, int userId1, @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + "startIntent(): intent=%s user=%d fillInIntent=%s position=%d", intent, userId1, + fillInIntent, position); // Flag this as a no-user-action launch to prevent sending user leaving event to the current // top activity since it's going to be put into another side of the split. This prevents the // current top activity from going into pip mode due to user leaving event. @@ -826,6 +829,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1)) .orElse(null); if (taskInfo != null) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + "Found suitable background task=%s", taskInfo); if (ENABLE_SHELL_TRANSITIONS) { mStageCoordinator.startTask(taskInfo.taskId, position, options); } else { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index c1406d052195..caa894fcbbc7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -22,8 +22,10 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.view.InputDevice.SOURCE_TOUCHSCREEN; +import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_HOVER_ENTER; import static android.view.MotionEvent.ACTION_HOVER_EXIT; +import static android.view.MotionEvent.ACTION_UP; import static android.view.WindowInsets.Type.statusBars; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; @@ -59,7 +61,6 @@ import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.View; -import android.view.ViewConfiguration; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -321,8 +322,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final GestureDetector mGestureDetector; private boolean mIsDragging; + private boolean mTouchscreenInUse; private boolean mHasLongClicked; - private boolean mShouldClick; private int mDragPointerId = -1; private final Runnable mCloseMaximizeWindowRunnable; @@ -343,6 +344,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { @Override public void onClick(View v) { + if (mIsDragging) { + mIsDragging = false; + return; + } final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); final int id = v.getId(); if (id == R.id.close_window) { @@ -421,6 +426,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { @Override public boolean onTouch(View v, MotionEvent e) { final int id = v.getId(); + if ((e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN) { + mTouchscreenInUse = e.getActionMasked() != ACTION_UP + && e.getActionMasked() != ACTION_CANCEL; + } if (id != R.id.caption_handle && id != R.id.desktop_mode_caption && id != R.id.open_menu_button && id != R.id.close_window && id != R.id.maximize_window) { @@ -432,31 +441,19 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { if (!mHasLongClicked && id != R.id.maximize_window) { decoration.closeMaximizeMenuIfNeeded(e); } - - final long eventDuration = e.getEventTime() - e.getDownTime(); - final boolean isTouchScreen = - (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN; - final boolean shouldLongClick = isTouchScreen && id == R.id.maximize_window - && !mIsDragging && !mHasLongClicked - && eventDuration >= ViewConfiguration.getLongPressTimeout(); - if (shouldLongClick) { - v.performLongClick(); - mHasLongClicked = true; - return true; - } - return mDragDetector.onMotionEvent(v, e); } @Override public boolean onLongClick(View v) { final int id = v.getId(); - if (id == R.id.maximize_window) { + if (id == R.id.maximize_window && mTouchscreenInUse) { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); moveTaskToFront(decoration.mTaskInfo); if (decoration.isMaximizeMenuActive()) { decoration.closeMaximizeMenu(); } else { + mHasLongClicked = true; decoration.createMaximizeMenu(); } return true; @@ -515,11 +512,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { if (mGestureDetector.onTouchEvent(e)) { return true; } - if (e.getActionMasked() == MotionEvent.ACTION_CANCEL) { - // If a motion event is cancelled, reset mShouldClick so a click is not accidentally - // performed. - mShouldClick = false; - } + final int id = v.getId(); + final boolean touchingButton = (id == R.id.close_window || id == R.id.maximize_window + || id == R.id.open_menu_button); switch (e.getActionMasked()) { case MotionEvent.ACTION_DOWN: { mDragPointerId = e.getPointerId(0); @@ -527,12 +522,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { 0 /* ctrlType */, e.getRawX(0), e.getRawY(0)); mIsDragging = false; - mShouldClick = true; mHasLongClicked = false; - return true; + // Do not consume input event if a button is touched, otherwise it would + // prevent the button's ripple effect from showing. + return !touchingButton; } case MotionEvent.ACTION_MOVE: { - mShouldClick = false; // If a decor's resize drag zone is active, don't also try to reposition it. if (decoration.isHandlingDragResize()) break; decoration.closeMaximizeMenu(); @@ -553,11 +548,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { case MotionEvent.ACTION_CANCEL: { final boolean wasDragging = mIsDragging; if (!wasDragging) { - if (mShouldClick && v != null && !mHasLongClicked) { - v.performClick(); - mShouldClick = false; - return true; - } return false; } if (e.findPointerIndex(mDragPointerId) == -1) { @@ -576,8 +566,15 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { position, new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)), newTaskBounds)); - mIsDragging = false; - return true; + if (touchingButton && !mHasLongClicked) { + // We need the input event to not be consumed here to end the ripple + // effect on the touched button. We will reset drag state in the ensuing + // onClick call that results. + return false; + } else { + mIsDragging = false; + return true; + } } } return true; diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt index ac7380f83233..9a1bd267ea1f 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt @@ -206,6 +206,18 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli } } + @Presubmit + @Test + fun pipLayerRemainInsideVisibleBounds() { + // during the transition we assert the center point is within the display bounds, since it + // might go outside of bounds as we resize from landscape fullscreen to destination bounds, + // and once the animation is over we assert that it's fully within the display bounds, at + // which point the device also performs orientation change from landscape to portrait + flicker.assertLayersVisibleRegion(pipApp.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)) { + regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds) + } + } + /** {@inheritDoc} */ @FlakyTest(bugId = 267424412) @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java index 1b347e01888e..5dd9d8a859d6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java @@ -22,9 +22,11 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY; import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT; import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK; +import static android.content.ClipDescription.MIMETYPE_TEXT_INTENT; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; @@ -46,6 +48,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.quality.Strictness.LENIENT; import android.app.ActivityManager; import android.app.ActivityTaskManager; @@ -61,6 +65,7 @@ import android.content.res.Resources; import android.graphics.Insets; import android.os.RemoteException; import android.view.DisplayInfo; +import android.view.DragEvent; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -70,12 +75,15 @@ import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.draganddrop.DragAndDropPolicy.Target; import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.startingsurface.TaskSnapshotWindow; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.MockitoSession; import java.util.ArrayList; import java.util.Collections; @@ -107,6 +115,7 @@ public class DragAndDropPolicyTest extends ShellTestCase { private DragAndDropPolicy mPolicy; private ClipData mActivityClipData; + private ClipData mLaunchableIntentClipData; private ClipData mNonResizeableActivityClipData; private ClipData mTaskClipData; private ClipData mShortcutClipData; @@ -115,9 +124,16 @@ public class DragAndDropPolicyTest extends ShellTestCase { private ActivityManager.RunningTaskInfo mFullscreenAppTask; private ActivityManager.RunningTaskInfo mNonResizeableFullscreenAppTask; + private MockitoSession mMockitoSession; + @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); + mMockitoSession = mockitoSession() + .strictness(LENIENT) + .mockStatic(DragUtils.class) + .startMocking(); + when(DragUtils.canHandleDrag(any())).thenReturn(true); Resources res = mock(Resources.class); Configuration config = new Configuration(); @@ -134,11 +150,12 @@ public class DragAndDropPolicyTest extends ShellTestCase { mInsets = Insets.of(0, 0, 0, 0); mPolicy = spy(new DragAndDropPolicy(mContext, mSplitScreenStarter, mSplitScreenStarter)); - mActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY); - mNonResizeableActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY); + mActivityClipData = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY); + mLaunchableIntentClipData = createIntentClipData(); + mNonResizeableActivityClipData = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY); setClipDataResizeable(mNonResizeableActivityClipData, false); - mTaskClipData = createClipData(MIMETYPE_APPLICATION_TASK); - mShortcutClipData = createClipData(MIMETYPE_APPLICATION_SHORTCUT); + mTaskClipData = createAppClipData(MIMETYPE_APPLICATION_TASK); + mShortcutClipData = createAppClipData(MIMETYPE_APPLICATION_SHORTCUT); mHomeTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME); mFullscreenAppTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); @@ -149,10 +166,15 @@ public class DragAndDropPolicyTest extends ShellTestCase { setRunningTask(mFullscreenAppTask); } + @After + public void tearDown() { + mMockitoSession.finishMocking(); + } + /** - * Creates a clip data that is by default resizeable. + * Creates an app-based clip data that is by default resizeable. */ - private ClipData createClipData(String mimeType) { + private ClipData createAppClipData(String mimeType) { ClipDescription clipDescription = new ClipDescription(mimeType, new String[] { mimeType }); Intent i = new Intent(); switch (mimeType) { @@ -164,7 +186,9 @@ public class DragAndDropPolicyTest extends ShellTestCase { i.putExtra(Intent.EXTRA_TASK_ID, 12345); break; case MIMETYPE_APPLICATION_ACTIVITY: - i.putExtra(ClipDescription.EXTRA_PENDING_INTENT, mock(PendingIntent.class)); + final PendingIntent pi = mock(PendingIntent.class); + doReturn(android.os.Process.myUserHandle()).when(pi).getCreatorUserHandle(); + i.putExtra(ClipDescription.EXTRA_PENDING_INTENT, pi); break; } i.putExtra(Intent.EXTRA_USER, android.os.Process.myUserHandle()); @@ -175,6 +199,22 @@ public class DragAndDropPolicyTest extends ShellTestCase { return data; } + /** + * Creates an intent-based clip data that is by default resizeable. + */ + private ClipData createIntentClipData() { + ClipDescription clipDescription = new ClipDescription("Intent", + new String[] { MIMETYPE_TEXT_INTENT }); + PendingIntent intent = mock(PendingIntent.class); + when(intent.getCreatorUserHandle()).thenReturn(android.os.Process.myUserHandle()); + ClipData.Item item = new ClipData.Item.Builder() + .setIntentSender(intent.getIntentSender()) + .build(); + ClipData data = new ClipData(clipDescription, item); + when(DragUtils.getLaunchIntent((ClipData) any())).thenReturn(intent); + return data; + } + private ActivityManager.RunningTaskInfo createTaskInfo(int winMode, int actType) { ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo(); info.configuration.windowConfiguration.setActivityType(actType); @@ -204,58 +244,85 @@ public class DragAndDropPolicyTest extends ShellTestCase { @Test public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() { + dragOverFullscreenHome_expectOnlyFullscreenTarget(mActivityClipData); + } + + @Test + public void testDragAppOverFullscreenApp_expectSplitScreenTargets() { + dragOverFullscreenApp_expectSplitScreenTargets(mActivityClipData); + } + + @Test + public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenTargets() { + dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(mActivityClipData); + } + + @Test + public void testDragIntentOverFullscreenHome_expectOnlyFullscreenTarget() { + dragOverFullscreenHome_expectOnlyFullscreenTarget(mLaunchableIntentClipData); + } + + @Test + public void testDragIntentOverFullscreenApp_expectSplitScreenTargets() { + dragOverFullscreenApp_expectSplitScreenTargets(mLaunchableIntentClipData); + } + + @Test + public void testDragIntentOverFullscreenAppPhone_expectVerticalSplitScreenTargets() { + dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(mLaunchableIntentClipData); + } + + private void dragOverFullscreenHome_expectOnlyFullscreenTarget(ClipData data) { doReturn(true).when(mSplitScreenStarter).isLeftRightSplit(); setRunningTask(mHomeTask); - DragSession dragSession = new DragSession(mContext, mActivityTaskManager, - mLandscapeDisplayLayout, mActivityClipData); + DragSession dragSession = new DragSession(mActivityTaskManager, + mLandscapeDisplayLayout, data); dragSession.update(); mPolicy.start(dragSession, mLoggerSessionId); ArrayList<Target> targets = assertExactTargetTypes( mPolicy.getTargets(mInsets), TYPE_FULLSCREEN); - mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); + mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN)); verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(), eq(SPLIT_POSITION_UNDEFINED), any()); } - @Test - public void testDragAppOverFullscreenApp_expectSplitScreenTargets() { + private void dragOverFullscreenApp_expectSplitScreenTargets(ClipData data) { doReturn(true).when(mSplitScreenStarter).isLeftRightSplit(); setRunningTask(mFullscreenAppTask); - DragSession dragSession = new DragSession(mContext, mActivityTaskManager, - mLandscapeDisplayLayout, mActivityClipData); + DragSession dragSession = new DragSession(mActivityTaskManager, + mLandscapeDisplayLayout, data); dragSession.update(); mPolicy.start(dragSession, mLoggerSessionId); ArrayList<Target> targets = assertExactTargetTypes( mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); - mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT), mActivityClipData); + mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT)); verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(), eq(SPLIT_POSITION_TOP_OR_LEFT), any()); reset(mSplitScreenStarter); - mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData); + mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT)); verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any()); } - @Test - public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenTargets() { + private void dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(ClipData data) { doReturn(false).when(mSplitScreenStarter).isLeftRightSplit(); setRunningTask(mFullscreenAppTask); - DragSession dragSession = new DragSession(mContext, mActivityTaskManager, - mPortraitDisplayLayout, mActivityClipData); + DragSession dragSession = new DragSession(mActivityTaskManager, + mPortraitDisplayLayout, data); dragSession.update(); mPolicy.start(dragSession, mLoggerSessionId); ArrayList<Target> targets = assertExactTargetTypes( mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM); - mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP), mActivityClipData); + mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP)); verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(), eq(SPLIT_POSITION_TOP_OR_LEFT), any()); reset(mSplitScreenStarter); - mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData); + mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM)); verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any()); } @@ -263,7 +330,7 @@ public class DragAndDropPolicyTest extends ShellTestCase { @Test public void testTargetHitRects() { setRunningTask(mFullscreenAppTask); - DragSession dragSession = new DragSession(mContext, mActivityTaskManager, + DragSession dragSession = new DragSession(mActivityTaskManager, mLandscapeDisplayLayout, mActivityClipData); dragSession.update(); mPolicy.start(dragSession, mLoggerSessionId); diff --git a/media/java/android/media/RingtoneSelection.java b/media/java/android/media/RingtoneSelection.java deleted file mode 100644 index b7c372182464..000000000000 --- a/media/java/android/media/RingtoneSelection.java +++ /dev/null @@ -1,742 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media; - -import static java.util.Objects.requireNonNull; - -import android.annotation.FlaggedApi; -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.TestApi; -import android.content.ContentProvider; -import android.content.ContentResolver; -import android.net.Uri; -import android.os.UserHandle; -import android.os.vibrator.Flags; -import android.provider.MediaStore; - -import com.android.internal.annotations.VisibleForTesting; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Objects; - -/** - * Immutable representation a desired ringtone, usually originating from a user preference. - * Unlike sound-only Uris, a "silent" setting is an explicit selection value, rather than null. - * - * <p>This representation can be converted into (or from) a URI form for storing within a string - * preference or when using the ringtone picker via {@link RingtoneManager#ACTION_RINGTONE_PICKER}. - * It does not carry any actual media data - it only references the components that make - * up the preference. Initial selections can be built using {@link RingtoneSelection.Builder}. - * - * <p>A RingtoneSelection is typically played by passing into a {@link Ringtone.Builder}, and - * supplementing with contextual defaults from the application. Bad Uris are handled by the - * {@link Ringtone} class - the RingtoneSelection doesn't validate the target of the Uri. - * - * <p>When a RingtoneSelection is created/loaded, the values of its properties are modified - * to be internally consistent and reflect effective values - with the exception of not verifying - * the actual URI content. For example, loading a selection Uri that sets a sound source to - * {@link #SOUND_SOURCE_URI}, but doesn't also have a sound Uri set, will result in this class - * instead returning {@link #SOUND_SOURCE_UNSPECIFIED} from {@link #getSoundSource}. - * - * <h2>Storing preferences</h2> - * - * <p>A ringtone preference can have several states: either unset, set to a ringtone selection Uri, - * or, from prior to the introduction of {@code RingtoneSelection}, set to a sound-only Uri or - * explicitly set to null to indicate silent. - * - * @hide - */ -@TestApi -@FlaggedApi(Flags.FLAG_HAPTICS_CUSTOMIZATION_ENABLED) -public final class RingtoneSelection { - - /** - * The sound source was specified but its value was not recognized. This value is used - * internally for not stripping unrecognised (possibly future) values during processing. - * @hide - */ - public static final int SOUND_SOURCE_UNKNOWN = -1; - - /** - * The sound source is not explicitly specified, so it can follow default behavior for its - * context. - */ - public static final int SOUND_SOURCE_UNSPECIFIED = 0; - - /** - * Sound is explicitly disabled, such as the user having selected "Silent" in the sound picker. - */ - public static final int SOUND_SOURCE_OFF = 1; - - /** - * The sound Uri should be used as the source of sound. - */ - public static final int SOUND_SOURCE_URI = 2; - - /** - * The sound should explicitly use the system default. - * - * <p>This value isn't valid within the system default itself. - */ - public static final int SOUND_SOURCE_SYSTEM_DEFAULT = 3; - - // Note: Value 4 reserved for possibility of SOURCE_SOURCE_APPLICATION_DEFAULT. - - /** - * Directive for how to make sound. - * @hide - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = "SOUND_SOURCE_", value = { - SOUND_SOURCE_UNKNOWN, - SOUND_SOURCE_UNSPECIFIED, - SOUND_SOURCE_OFF, - SOUND_SOURCE_URI, - SOUND_SOURCE_SYSTEM_DEFAULT, - }) - public @interface SoundSource {} - - /** - * The vibration source was specified but its value was not recognized. - * This value is used internally for not stripping unrecognised (possibly - * future) values during processing. - * @hide - */ - public static final int VIBRATION_SOURCE_UNKNOWN = -1; - - /** - * Vibration source is not explicitly specified. If vibration is enabled, this will use the - * first available of {@link #VIBRATION_SOURCE_AUDIO_CHANNEL}, - * {@link #VIBRATION_SOURCE_APPLICATION_DEFAULT}, or {@link #VIBRATION_SOURCE_SYSTEM_DEFAULT}. - */ - public static final int VIBRATION_SOURCE_UNSPECIFIED = 0; - - /** Specifies that vibration is explicitly disabled for this ringtone. */ - public static final int VIBRATION_SOURCE_OFF = 1; - - /** The vibration Uri should be used as the source of vibration. */ - public static final int VIBRATION_SOURCE_URI = 2; - - /** - * The vibration should explicitly use the system default. - * - * <p>This value isn't valid within the system default itself. - */ - public static final int VIBRATION_SOURCE_SYSTEM_DEFAULT = 3; - - /** - * Specifies that vibration should use the vibration provided by the application. This is - * typically the application's own default for the use-case, provided via - * {@link Ringtone.Builder#setVibrationEffect}. For notification channels, this is the vibration - * effect saved on the notification channel. - * - * <p>If no vibration is specified by the application, this value behaves if the source was - * {@link #VIBRATION_SOURCE_UNSPECIFIED}. - * - * <p>This value isn't valid within the system default. - */ - public static final int VIBRATION_SOURCE_APPLICATION_DEFAULT = 4; - - /** - * Specifies that vibration should use haptic audio channels from the - * sound Uri. If the sound URI doesn't have haptic channels, then reverts to the order specified - * by {@link #VIBRATION_SOURCE_UNSPECIFIED}. - */ - // Numeric gap from VIBRATION_SOURCE_APPLICATION_DEFAULT in case we want other common elements. - public static final int VIBRATION_SOURCE_AUDIO_CHANNEL = 10; - - /** - * Specifies that vibration should generate haptic audio channels from the - * audio tracks of the sound Uri. - * - * If the sound Uri already has haptic channels, then behaves as though - * {@link #VIBRATION_SOURCE_AUDIO_CHANNEL} was specified instead. - */ - public static final int VIBRATION_SOURCE_HAPTIC_GENERATOR = 11; - - /** - * Directive for how to vibrate. - * @hide - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = "VIBRATION_SOURCE_", value = { - VIBRATION_SOURCE_UNKNOWN, - VIBRATION_SOURCE_UNSPECIFIED, - VIBRATION_SOURCE_OFF, - VIBRATION_SOURCE_URI, - VIBRATION_SOURCE_APPLICATION_DEFAULT, - VIBRATION_SOURCE_AUDIO_CHANNEL, - VIBRATION_SOURCE_HAPTIC_GENERATOR, - }) - public @interface VibrationSource {} - - /** - * Configures {@link #RingtoneSelection#fromUri} to treat an unrecognized Uri as the sound Uri - * for the returned {@link RingtoneSelection}, with null meaning {@link #SOUND_SOURCE_OFF}, - * and symbolic default URIs ({@link RingtoneManager#getDefaultUri}) meaning - * {@link #SOUND_SOURCE_SYSTEM_DEFAULT}. - * - * <p>This behavior is particularly suited to loading values from older settings that may - * contain a raw sound Uri or null for silent. - * - * <p>An unrecognized Uri is one for which {@link #isRingtoneSelectionUri(Uri)} returns false. - */ - public static final int FROM_URI_RINGTONE_SELECTION_OR_SOUND = 1; - - /** - * Configures {@link #RingtoneSelection#fromUri} to treat an unrecognized Uri as the vibration - * Uri for the returned {@link RingtoneSelection}, with null meaning - * {@link #VIBRATION_SOURCE_OFF} and symbolic default URIs - * ({@link RingtoneManager#getDefaultUri}) meaning {@link #VIBRATION_SOURCE_SYSTEM_DEFAULT}. - * - * <p>An unrecognized Uri is one for which {@link #isRingtoneSelectionUri(Uri)} returns false. - */ - public static final int FROM_URI_RINGTONE_SELECTION_OR_VIBRATION = 2; - - /** - * Configures {@link #RingtoneSelection#fromUri} to treat an unrecognized Uri as an invalid - * value. Null or an invalid values will revert to default behavior correspnoding to - * {@link #DEFAULT_SELECTION_URI_STRING}. Symbolic default URIs - * ({@link RingtoneManager#getDefaultUri}) will set both - * {@link #SOUND_SOURCE_SYSTEM_DEFAULT} and {@link #VIBRATION_SOURCE_SYSTEM_DEFAULT}. - * - * <p>An unrecognized Uri is one for which {@link #isRingtoneSelectionUri(Uri)} returns false, - * which include {@code null}. - */ - public static final int FROM_URI_RINGTONE_SELECTION_ONLY = 3; - - /** - * How to treat values in {@link #fromUri}. - * @hide - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = "FROM_URI_", value = { - FROM_URI_RINGTONE_SELECTION_OR_SOUND, - FROM_URI_RINGTONE_SELECTION_OR_VIBRATION, - FROM_URI_RINGTONE_SELECTION_ONLY - }) - public @interface FromUriBehavior {} - - private static final String BASE_RINGTONE_URI = "content://media/ringtone"; - /** - * String representation of a RingtoneSelection Uri that says to use defaults (equivalent - * to {@code new RingtoneSelection.Builder().build()}). - */ - public static final String DEFAULT_SELECTION_URI_STRING = BASE_RINGTONE_URI; - - private static final String MEDIA_URI_RINGTONE_PATH = "/ringtone"; - - /* Query param keys. */ - private static final String URI_PARAM_SOUND_URI = "su"; - private static final String URI_PARAM_SOUND_SOURCE = "ss"; - private static final String URI_PARAM_VIBRATION_URI = "vu"; - private static final String URI_PARAM_VIBRATION_SOURCE = "vs"; - - /* Common param values */ - private static final String SOURCE_OFF_STRING = "off"; - private static final String SOURCE_SYSTEM_DEFAULT_STRING = "sys"; - - /* Vibration source param values. */ - private static final String VIBRATION_SOURCE_AUDIO_CHANNEL_STRING = "ac"; - private static final String VIBRATION_SOURCE_APPLICATION_DEFAULT_STRING = "app"; - private static final String VIBRATION_SOURCE_HAPTIC_GENERATOR_STRING = "hg"; - - @Nullable - private final Uri mSoundUri; - @SoundSource - private final int mSoundSource; - - @Nullable - private final Uri mVibrationUri; - @VibrationSource - private final int mVibrationSource; - - private RingtoneSelection(@Nullable Uri soundUri, @SoundSource int soundSource, - @Nullable Uri vibrationUri, @VibrationSource int vibrationSource) { - // Enforce guarantees on the source values: revert to unspecified if they depend on - // something that's not set. - // - // The non-public "unknown" value can't appear in a getter result, it's just a reserved - // "null" value and should be treated the same as an unrecognized value. This can be seen - // in Uri parsing. For this and other unrecognized values, we either revert them to the URI - // source, if a Uri was included, or the "unspecified" source otherwise. This can be - // seen in action in the Uri parsing. - // - // The "unspecified" source is a public value meaning that there is no specific - // behavior indicated, and the defaults and fallbacks should be applied. For example, an - // vibration source value of "system default" means to explicitly use the system default - // vibration. However, an "unspecified" vibration source will first see if audio coupled - // or application-default vibrations are available. - mSoundSource = switch (soundSource) { - // Supported explicit values that don't have a Uri. - case SOUND_SOURCE_OFF, SOUND_SOURCE_UNSPECIFIED, SOUND_SOURCE_SYSTEM_DEFAULT -> - soundSource; - // Uri and unknown/unrecognized values: use a Uri if one is present, else revert to - // unspecified. - default -> - soundUri != null ? SOUND_SOURCE_URI : SOUND_SOURCE_UNSPECIFIED; - }; - mVibrationSource = switch (vibrationSource) { - // Enforce vibration sources that require a sound Uri. - case VIBRATION_SOURCE_AUDIO_CHANNEL, VIBRATION_SOURCE_HAPTIC_GENERATOR -> - soundUri != null ? vibrationSource : VIBRATION_SOURCE_UNSPECIFIED; - // Supported explicit values that don't rely on any Uri. - case VIBRATION_SOURCE_OFF, VIBRATION_SOURCE_UNSPECIFIED, - VIBRATION_SOURCE_SYSTEM_DEFAULT, VIBRATION_SOURCE_APPLICATION_DEFAULT -> - vibrationSource; - // Uri and unknown/unrecognized values: use a Uri if one is present, else revert to - // unspecified. - default -> - vibrationUri != null ? VIBRATION_SOURCE_URI : VIBRATION_SOURCE_UNSPECIFIED; - }; - // Clear Uri values if they're un-used by the source. - mSoundUri = mSoundSource == SOUND_SOURCE_URI ? soundUri : null; - mVibrationUri = mVibrationSource == VIBRATION_SOURCE_URI ? vibrationUri : null; - } - - /** - * Returns the stored sound behavior. - */ - @SoundSource - public int getSoundSource() { - return mSoundSource; - } - - /** - * Returns the sound Uri for this selection. This is guaranteed to be non-null if - * {@link #getSoundSource} returns {@link #SOUND_SOURCE_URI}. - */ - @Nullable - public Uri getSoundUri() { - return mSoundUri; - } - - /** - * Returns the selected vibration behavior. - */ - @VibrationSource - public int getVibrationSource() { - return mVibrationSource; - } - - /** - * Returns the vibration Uri for this selection. This is guaranteed to be non-null if - * {@link #getVibrationSource} returns {@link #SOUND_SOURCE_URI}. - */ - @Nullable - public Uri getVibrationUri() { - return mVibrationUri; - } - - /** - * Converts the ringtone selection into a Uri-form, suitable for storing as a user preference - * or returning as a result. - */ - @NonNull - public Uri toUri() { - Uri.Builder builder = new Uri.Builder() - .scheme(ContentResolver.SCHEME_CONTENT) - .authority(MediaStore.AUTHORITY) - .path(MEDIA_URI_RINGTONE_PATH); - if (mSoundUri != null) { - builder.appendQueryParameter(URI_PARAM_SOUND_URI, mSoundUri.toString()); - } - // Only off is explicit for sound sources - String soundSourceStr = soundSourceToString(mSoundSource); - if (soundSourceStr != null) { - builder.appendQueryParameter(URI_PARAM_SOUND_SOURCE, soundSourceStr); - } - if (mVibrationUri != null) { - builder.appendQueryParameter(URI_PARAM_VIBRATION_URI, mVibrationUri.toString()); - } - String vibrationSourceStr = vibrationSourceToString(mVibrationSource); - if (vibrationSourceStr != null) { - builder.appendQueryParameter(URI_PARAM_VIBRATION_SOURCE, vibrationSourceStr); - } - return builder.build(); - } - - /** - * Returns true if the Uri is an encoded {@link RingtoneSelection}. This method doesn't - * validate the parameters of the selection. - * - * @see #fromUri - * @see #toUri - */ - public static boolean isRingtoneSelectionUri(@Nullable Uri uri) { - if (uri == null) { - return false; - } - // Any URI content://media/ringtone - return ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()) - && MediaStore.AUTHORITY.equals( - ContentProvider.getAuthorityWithoutUserId(uri.getAuthority())) - && MEDIA_URI_RINGTONE_PATH.equals(uri.getPath()); - } - - /** - * Strip the specified userId from internal Uris. Non-stripped userIds will typically be - * for work profiles referencing system ringtones from the host user. - * - * This is only for use in RingtoneManager. - * @hide - */ - @VisibleForTesting - public RingtoneSelection getWithoutUserId(int userIdToStrip) { - if (mSoundSource != SOUND_SOURCE_URI && mVibrationSource != VIBRATION_SOURCE_URI) { - return this; - } - - // Ok if uri is null. We only replace explicit references to the specified (current) userId. - int soundUserId = ContentProvider.getUserIdFromUri(mSoundUri, UserHandle.USER_NULL); - int vibrationUserId = ContentProvider.getUserIdFromUri(mVibrationUri, UserHandle.USER_NULL); - boolean needToChangeSound = - soundUserId != UserHandle.USER_NULL && soundUserId == userIdToStrip; - boolean needToChangeVibration = - vibrationUserId != UserHandle.USER_NULL && vibrationUserId == userIdToStrip; - - // Anything to do? - if (!needToChangeSound && !needToChangeVibration) { - return this; - } - - RingtoneSelection.Builder updated = new Builder(this); - // The relevant uris can't be null, because they contain userIdToStrip. - if (needToChangeSound) { - // mSoundUri is not null, so the result of getUriWithoutUserId won't be null. - updated.setSoundSource(ContentProvider.getUriWithoutUserId(mSoundUri)); - } - if (needToChangeVibration) { - updated.setVibrationSource(ContentProvider.getUriWithoutUserId(mVibrationUri)); - } - return updated.build(); - } - - @Override - public String toString() { - return toUri().toString(); - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (!(o instanceof RingtoneSelection other)) { - return false; - } - return this.mSoundSource == other.mSoundSource - && this.mVibrationSource == other.mVibrationSource - && Objects.equals(this.mSoundUri, other.mSoundUri) - && Objects.equals(this.mVibrationUri, other.mVibrationUri); - } - - @Override - public int hashCode() { - return Objects.hash(mSoundSource, mVibrationSource, mSoundUri, mVibrationUri); - } - - /** - * Converts a Uri into a RingtoneSelection. - * - * <p>Null values, Uris that {@link #isRingtoneSelectionUri(Uri)} returns false (except for - * old-style symbolic default Uris {@link RingtoneManager#getDefaultUri}) will be treated - * according to the behaviour specified by the {@code unrecognizedValueBehavior} parameter. - * - * <p>Symbolic default Uris (where {@link RingtoneManager#getDefaultType(Uri)} returns -1, - * will map sources to {@link #SOUND_SOURCE_SYSTEM_DEFAULT} and - * {@link #VIBRATION_SOURCE_SYSTEM_DEFAULT}. - * - * @param uri The Uri to convert, potentially null. - * @param unrecognizedValueBehavior indicates how to treat values for which - * {@link #isRingtoneSelectionUri(Uri)} returns false (including null). - * @return the RingtoneSelection represented by the given uri. - */ - @NonNull - public static RingtoneSelection fromUri(@Nullable Uri uri, - @FromUriBehavior int unrecognizedValueBehavior) { - if (isRingtoneSelectionUri(uri)) { - return parseRingtoneSelectionUri(uri); - } - // Old symbolic default URIs map to the sources suggested by the unrecognized behavior. - // It doesn't always map to both sources because the app may have its own default behavior - // to apply, so non-primary sources are left as unspecified, which will revert to the - // system default in the absence of an app default. - boolean isDefaultUri = RingtoneManager.getDefaultType(uri) > 0; - RingtoneSelection.Builder builder = new RingtoneSelection.Builder(); - switch (unrecognizedValueBehavior) { - case FROM_URI_RINGTONE_SELECTION_ONLY: - if (isDefaultUri) { - builder.setSoundSource(SOUND_SOURCE_SYSTEM_DEFAULT); - builder.setVibrationSource(VIBRATION_SOURCE_SYSTEM_DEFAULT); - } - // Always return unspecified (defaults) for unrecognized ringtone selection Uris. - return builder.build(); - case FROM_URI_RINGTONE_SELECTION_OR_SOUND: - if (uri == null) { - return builder.setSoundSource(SOUND_SOURCE_OFF).build(); - } else if (isDefaultUri) { - return builder.setSoundSource(SOUND_SOURCE_SYSTEM_DEFAULT).build(); - } else { - return builder.setSoundSource(uri).build(); - } - case FROM_URI_RINGTONE_SELECTION_OR_VIBRATION: - if (uri == null) { - return builder.setVibrationSource(VIBRATION_SOURCE_OFF).build(); - } else if (isDefaultUri) { - return builder.setVibrationSource(VIBRATION_SOURCE_SYSTEM_DEFAULT).build(); - } else { - // Unlike sound, there's no legacy settings alias uri for the default. - return builder.setVibrationSource(uri).build(); - } - default: - throw new IllegalArgumentException("Unknown behavior parameter: " - + unrecognizedValueBehavior); - } - } - - /** - * Parses the Uri, which has already been checked for {@link #isRingtoneSelectionUri(Uri)}. - * As a special case to be more compatible, if the RingtoneSelection has a userId specified in - * the authority, then this is pushed down into the sound and vibration Uri, as the selection - * itself is agnostic. - */ - @NonNull - private static RingtoneSelection parseRingtoneSelectionUri(@NonNull Uri uri) { - RingtoneSelection.Builder builder = new RingtoneSelection.Builder(); - int soundSource = stringToSoundSource(uri.getQueryParameter(URI_PARAM_SOUND_SOURCE)); - int vibrationSource = stringToVibrationSource( - uri.getQueryParameter(URI_PARAM_VIBRATION_SOURCE)); - Uri soundUri = getParamAsUri(uri, URI_PARAM_SOUND_URI); - Uri vibrationUri = getParamAsUri(uri, URI_PARAM_VIBRATION_URI); - - // Add userId if necessary. This won't override an existing one in the sound/vib Uris. - int userIdFromAuthority = ContentProvider.getUserIdFromAuthority( - uri.getAuthority(), UserHandle.USER_NULL); - if (userIdFromAuthority != UserHandle.USER_NULL) { - // Won't override existing user id. - if (soundUri != null) { - soundUri = ContentProvider.maybeAddUserId(soundUri, userIdFromAuthority); - } - if (vibrationUri != null) { - vibrationUri = ContentProvider.maybeAddUserId(vibrationUri, userIdFromAuthority); - } - } - - // Set sound uri if present, but map system default Uris to the system default source. - if (soundUri != null) { - if (RingtoneManager.getDefaultType(uri) >= 0) { - builder.setSoundSource(SOUND_SOURCE_SYSTEM_DEFAULT); - } else { - builder.setSoundSource(soundUri); - } - } - if (vibrationUri != null) { - builder.setVibrationSource(vibrationUri); - } - - // Don't set the source if there's a URI and the source is default, because that will - // override the Uri source set above. In effect, we prioritise "explicit" sources over - // an implicit Uri source - except for "default", which isn't really explicit. - if (soundSource != SOUND_SOURCE_UNSPECIFIED || soundUri == null) { - builder.setSoundSource(soundSource); - } - if (vibrationSource != VIBRATION_SOURCE_UNSPECIFIED || vibrationUri == null) { - builder.setVibrationSource(vibrationSource); - } - return builder.build(); - } - - @Nullable - private static Uri getParamAsUri(@NonNull Uri uri, String param) { - // This returns the uri-decoded value, no need to further decode. - String value = uri.getQueryParameter(param); - if (value == null) { - return null; - } - return Uri.parse(value); - } - - /** - * Converts the {@link SoundSource} to the uri query param value for it, or null - * if the sound source is default, unknown, or implicit (uri). - */ - @Nullable - private static String soundSourceToString(@SoundSource int soundSource) { - return switch (soundSource) { - case SOUND_SOURCE_OFF -> SOURCE_OFF_STRING; - case SOUND_SOURCE_SYSTEM_DEFAULT -> SOURCE_SYSTEM_DEFAULT_STRING; - default -> null; - }; - } - - /** - * Returns the sound source int corresponding to the query string value. Returns - * {@link #SOUND_SOURCE_UNKNOWN} if the value isn't recognised, and - * {@link #SOUND_SOURCE_UNSPECIFIED} if the value is {@code null} (not in the Uri). - */ - @SoundSource - private static int stringToSoundSource(@Nullable String soundSource) { - if (soundSource == null) { - return SOUND_SOURCE_UNSPECIFIED; - } - return switch (soundSource) { - case SOURCE_OFF_STRING -> SOUND_SOURCE_OFF; - case SOURCE_SYSTEM_DEFAULT_STRING -> SOUND_SOURCE_SYSTEM_DEFAULT; - default -> SOUND_SOURCE_UNKNOWN; - }; - } - - /** - * Converts the {@code vibrationSource} to the uri query param value for it, or null - * if the vibration source is default, unknown, or implicit (uri). - */ - @Nullable - private static String vibrationSourceToString(@VibrationSource int vibrationSource) { - return switch (vibrationSource) { - case VIBRATION_SOURCE_OFF -> SOURCE_OFF_STRING; - case VIBRATION_SOURCE_AUDIO_CHANNEL -> VIBRATION_SOURCE_AUDIO_CHANNEL_STRING; - case VIBRATION_SOURCE_HAPTIC_GENERATOR -> VIBRATION_SOURCE_HAPTIC_GENERATOR_STRING; - case VIBRATION_SOURCE_APPLICATION_DEFAULT -> - VIBRATION_SOURCE_APPLICATION_DEFAULT_STRING; - case VIBRATION_SOURCE_SYSTEM_DEFAULT -> SOURCE_SYSTEM_DEFAULT_STRING; - default -> null; - }; - } - - @VibrationSource - private static int stringToVibrationSource(@Nullable String vibrationSource) { - if (vibrationSource == null) { - return VIBRATION_SOURCE_UNSPECIFIED; - } - return switch (vibrationSource) { - case SOURCE_OFF_STRING -> VIBRATION_SOURCE_OFF; - case SOURCE_SYSTEM_DEFAULT_STRING -> VIBRATION_SOURCE_SYSTEM_DEFAULT; - case VIBRATION_SOURCE_AUDIO_CHANNEL_STRING -> VIBRATION_SOURCE_AUDIO_CHANNEL; - case VIBRATION_SOURCE_HAPTIC_GENERATOR_STRING -> VIBRATION_SOURCE_HAPTIC_GENERATOR; - case VIBRATION_SOURCE_APPLICATION_DEFAULT_STRING -> - VIBRATION_SOURCE_APPLICATION_DEFAULT; - default -> VIBRATION_SOURCE_UNKNOWN; - }; - } - - /** - * Builder for {@link RingtoneSelection}. In general, this builder will be used by interfaces - * allowing the user to configure their selection. Once a selection is stored as a Uri, then - * the RingtoneSelection can be loaded directly using {@link RingtoneSelection#fromUri}. - */ - @FlaggedApi(Flags.FLAG_HAPTICS_CUSTOMIZATION_ENABLED) - public static final class Builder { - private Uri mSoundUri; - private Uri mVibrationUri; - @SoundSource private int mSoundSource = SOUND_SOURCE_UNSPECIFIED; - @VibrationSource private int mVibrationSource = VIBRATION_SOURCE_UNSPECIFIED; - - /** - * Creates a new {@link RingtoneSelection} builder. A default ringtone selection has its - * sound and vibration source unspecified, which means they would fall back to app/system - * defaults. - */ - public Builder() {} - - /** - * Creates a builder initialized with the given ringtone selection. - */ - public Builder(@NonNull RingtoneSelection selection) { - requireNonNull(selection); - mSoundSource = selection.getSoundSource(); - mSoundUri = selection.getSoundUri(); - mVibrationSource = selection.getVibrationSource(); - mVibrationUri = selection.getVibrationUri(); - } - - /** - * Sets the desired sound source. - * - * <p>Values other than {@link #SOUND_SOURCE_URI} will clear any previous sound Uri. - * For {@link #SOUND_SOURCE_URI}, the {@link #setSoundSource(Uri)} method should be - * used instead, as setting it here will have no effect unless the Uri is also set. - */ - @NonNull - public Builder setSoundSource(@SoundSource int soundSource) { - mSoundSource = soundSource; - if (soundSource != SOUND_SOURCE_URI && soundSource != SOUND_SOURCE_UNKNOWN) { - // Note that this means the configuration of "silent sound, but use haptic - // generator" is currently not supported. Future support could be added by either - // using the vibration uri in that case, or by having a special - // "setSoundUriForVibrationOnly(Uri)" method that sets sound source to off but - // also retains the Uri. - mSoundUri = null; - } - return this; - } - - /** - * Sets the sound source to {@link #SOUND_SOURCE_URI}, and the sound Uri to the - * specified {@link Uri}. - */ - @NonNull - public Builder setSoundSource(@NonNull Uri soundUri) { - // getCanonicalUri shouldn't return null. If it somehow did, then the - // RingtoneSelection constructor will revert this to unspecified. - mSoundUri = requireNonNull(soundUri).getCanonicalUri(); - mSoundSource = SOUND_SOURCE_URI; - return this; - } - - /** - * Sets the vibration source to the specified value. - * - * <p>Values other than {@link #VIBRATION_SOURCE_URI} will clear any previous vibration Uri. - * For {@link #VIBRATION_SOURCE_URI}, the {@link #setVibrationSource(Uri)} method should be - * used instead, as setting it here will have no effect unless the Uri is also set. - */ - @NonNull - public Builder setVibrationSource(@VibrationSource int vibrationSource) { - mVibrationSource = vibrationSource; - if (vibrationSource != VIBRATION_SOURCE_URI - && vibrationSource != VIBRATION_SOURCE_UNKNOWN) { - mVibrationUri = null; - } - return this; - } - - /** - * Sets the vibration source to {@link #VIBRATION_SOURCE_URI}, and the vibration Uri to the - * specified {@link Uri}. - */ - @NonNull - public Builder setVibrationSource(@NonNull Uri vibrationUri) { - // getCanonicalUri shouldn't return null. If it somehow did, then the - // RingtoneSelection constructor will revert this to unspecified. - mVibrationUri = requireNonNull(vibrationUri).getCanonicalUri(); - mVibrationSource = VIBRATION_SOURCE_URI; - return this; - } - - /** - * Returns the ringtone Uri that was configured. - */ - @NonNull - public RingtoneSelection build() { - return new RingtoneSelection(mSoundUri, mSoundSource, mVibrationUri, mVibrationSource); - } - } -} diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java index 48b476651c91..60ab1a4e42ac 100644 --- a/media/java/android/media/audiopolicy/AudioMix.java +++ b/media/java/android/media/audiopolicy/AudioMix.java @@ -27,7 +27,9 @@ import android.compat.annotation.UnsupportedAppUsage; import android.media.AudioDeviceInfo; import android.media.AudioFormat; import android.media.AudioSystem; +import android.os.Binder; import android.os.Build; +import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -52,6 +54,8 @@ public class AudioMix implements Parcelable { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private int mMixType = MIX_TYPE_INVALID; + private final IBinder mToken; + // written by AudioPolicy int mMixState = MIX_STATE_DISABLED; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -68,7 +72,7 @@ public class AudioMix implements Parcelable { */ private AudioMix(@NonNull AudioMixingRule rule, @NonNull AudioFormat format, int routeFlags, int callbackFlags, - int deviceType, @Nullable String deviceAddress) { + int deviceType, @Nullable String deviceAddress, IBinder token) { mRule = Objects.requireNonNull(rule); mFormat = Objects.requireNonNull(format); mRouteFlags = routeFlags; @@ -76,6 +80,7 @@ public class AudioMix implements Parcelable { mCallbackFlags = callbackFlags; mDeviceSystemType = deviceType; mDeviceAddress = (deviceAddress == null) ? new String("") : deviceAddress; + mToken = token; } // CALLBACK_FLAG_* values: keep in sync with AudioMix::kCbFlag* values defined @@ -273,13 +278,14 @@ public class AudioMix implements Parcelable { return Objects.equals(this.mRouteFlags, that.mRouteFlags) && Objects.equals(this.mRule, that.mRule) && Objects.equals(this.mMixType, that.mMixType) - && Objects.equals(this.mFormat, that.mFormat); + && Objects.equals(this.mFormat, that.mFormat) + && Objects.equals(this.mToken, that.mToken); } /** @hide */ @Override public int hashCode() { - return Objects.hash(mRouteFlags, mRule, mMixType, mFormat); + return Objects.hash(mRouteFlags, mRule, mMixType, mFormat, mToken); } @Override @@ -298,6 +304,7 @@ public class AudioMix implements Parcelable { dest.writeString8(mDeviceAddress); mFormat.writeToParcel(dest, flags); mRule.writeToParcel(dest, flags); + dest.writeStrongBinder(mToken); } public static final @NonNull Parcelable.Creator<AudioMix> CREATOR = new Parcelable.Creator<>() { @@ -317,6 +324,7 @@ public class AudioMix implements Parcelable { mixBuilder.setDevice(p.readInt(), p.readString8()); mixBuilder.setFormat(AudioFormat.CREATOR.createFromParcel(p)); mixBuilder.setMixingRule(AudioMixingRule.CREATOR.createFromParcel(p)); + mixBuilder.setToken(p.readStrongBinder()); return mixBuilder.build(); } @@ -339,6 +347,7 @@ public class AudioMix implements Parcelable { private AudioFormat mFormat = null; private int mRouteFlags = 0; private int mCallbackFlags = 0; + private IBinder mToken = null; // an AudioSystem.DEVICE_* value, not AudioDeviceInfo.TYPE_* private int mDeviceSystemType = AudioSystem.DEVICE_NONE; private String mDeviceAddress = null; @@ -380,6 +389,15 @@ public class AudioMix implements Parcelable { /** * @hide + * Only used by AudioMix internally. + */ + Builder setToken(IBinder token) { + mToken = token; + return this; + } + + /** + * @hide * Only used by AudioPolicyConfig, not a public API. * @param callbackFlags which callbacks are called from native * @return the same Builder instance. @@ -540,8 +558,13 @@ public class AudioMix implements Parcelable { throw new IllegalArgumentException(error); } } + + if (mToken == null) { + mToken = new Binder(); + } + return new AudioMix(mRule, mFormat, mRouteFlags, mCallbackFlags, mDeviceSystemType, - mDeviceAddress); + mDeviceAddress, mToken); } private int getLoopbackDeviceSystemTypeForAudioMixingRule(AudioMixingRule rule) { diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java index bbe461c95ed9..508c0a2b9a21 100644 --- a/media/java/android/media/audiopolicy/AudioPolicy.java +++ b/media/java/android/media/audiopolicy/AudioPolicy.java @@ -337,7 +337,9 @@ public class AudioPolicy { /** * Update the current configuration of the set of audio mixes by adding new ones, while - * keeping the policy registered. + * keeping the policy registered. If any of the provided audio mixes is invalid then none of + * the passed mixes will be registered. + * * This method can only be called on a registered policy. * @param mixes the list of {@link AudioMix} to add * @return {@link AudioManager#SUCCESS} if the change was successful, {@link AudioManager#ERROR} @@ -375,12 +377,15 @@ public class AudioPolicy { } /** - * Update the current configuration of the set of audio mixes by removing some, while - * keeping the policy registered. - * This method can only be called on a registered policy. + * Update the current configuration of the set of audio mixes for this audio policy by + * removing some, while keeping the policy registered. Will unregister all provided audio + * mixes, if possible. + * + * This method can only be called on a registered policy and only affects this current policy. * @param mixes the list of {@link AudioMix} to remove * @return {@link AudioManager#SUCCESS} if the change was successful, {@link AudioManager#ERROR} - * otherwise. + * otherwise. If only some of the provided audio mixes were detached but any one mix + * failed to be detached, this method returns {@link AudioManager#ERROR}. */ public int detachMixes(@NonNull List<AudioMix> mixes) { if (mixes == null) { @@ -394,7 +399,6 @@ public class AudioPolicy { for (AudioMix mix : mixes) { if (mix == null) { throw new IllegalArgumentException("Illegal null AudioMix in detachMixes"); - // TODO also check mix is currently contained in list of mixes } else { zeMixes.add(mix); } diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java index d277c7dfbea4..5e7c7c453df3 100644 --- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java +++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java @@ -228,7 +228,7 @@ public class AudioPolicyConfig implements Parcelable { } } - private void setMixRegistration(@NonNull final AudioMix mix) { + protected void setMixRegistration(@NonNull final AudioMix mix) { if (!mRegistrationId.isEmpty()) { if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) == AudioMix.ROUTE_FLAG_LOOP_BACK) { @@ -246,7 +246,9 @@ public class AudioPolicyConfig implements Parcelable { @GuardedBy("mMixes") protected void add(@NonNull ArrayList<AudioMix> mixes) { for (AudioMix mix : mixes) { - setMixRegistration(mix); + if (mix.getRegistration() == null || mix.getRegistration().isEmpty()) { + setMixRegistration(mix); + } mMixes.add(mix); } } diff --git a/packages/CredentialManager/res/drawable/ic_passkey_24.xml b/packages/CredentialManager/res/drawable/ic_passkey_24.xml index a2c4f374caf1..5298f9eda88e 100644 --- a/packages/CredentialManager/res/drawable/ic_passkey_24.xml +++ b/packages/CredentialManager/res/drawable/ic_passkey_24.xml @@ -13,16 +13,14 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - -<vector - android:alpha="0.8" - android:height="24dp" - android:viewportHeight="24" - android:viewportWidth="24" - android:width="24dp" - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools"> - <path android:fillColor="#4C463C" android:fillType="evenOdd" android:pathData="M22.18,14.09C22.18,15.364 21.408,16.459 20.306,16.931L21.247,17.872L20.219,18.9L21.247,19.928L19.068,22.107L18.099,21.138L18.099,17.017C16.878,16.604 16,15.45 16,14.09C16,12.383 17.383,11 19.09,11C20.796,11 22.18,12.383 22.18,14.09ZM20.692,14.091C20.692,14.976 19.975,15.693 19.09,15.693C18.205,15.693 17.488,14.976 17.488,14.091C17.488,13.206 18.205,12.488 19.09,12.488C19.975,12.488 20.692,13.206 20.692,14.091Z"/> - <path android:fillColor="#4C463C" android:pathData="M14.978,8.476C14.978,10.865 13.041,12.802 10.652,12.802C8.263,12.802 6.326,10.865 6.326,8.476C6.326,6.087 8.263,4.15 10.652,4.15C13.041,4.15 14.978,6.087 14.978,8.476Z"/> - <path android:fillColor="#4C463C" android:pathData="M2,19.263C2,16.39 7.762,14.937 10.652,14.937C11.782,14.937 13.353,15.16 14.845,15.602C15.177,16.491 15.804,17.236 16.607,17.717V21.454H2V19.263Z"/> +<!--LINT.IfChange--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M120,800L120,688Q120,654 137.5,625.5Q155,597 184,582Q246,551 310,535.5Q374,520 440,520Q460,520 480,521.5Q500,523 520,526Q516,584 541,635.5Q566,687 614,720L614,800L120,800ZM760,920L700,860L700,674Q656,661 628,624.5Q600,588 600,540Q600,482 641,441Q682,400 740,400Q798,400 839,441Q880,482 880,540Q880,585 854.5,620Q829,655 790,670L840,720L780,780L840,840L760,920ZM440,480Q374,480 327,433Q280,386 280,320Q280,254 327,207Q374,160 440,160Q506,160 553,207Q600,254 600,320Q600,386 553,433Q506,480 440,480ZM740,560Q757,560 768.5,548.5Q780,537 780,520Q780,503 768.5,491.5Q757,480 740,480Q723,480 711.5,491.5Q700,503 700,520Q700,537 711.5,548.5Q723,560 740,560Z"/> </vector> +<!--LINT.ThenChange(/packages/CredentialManager/shared/res/drawable/ic_passkey_24.xml)-->
\ No newline at end of file diff --git a/packages/CredentialManager/shared/Android.bp b/packages/CredentialManager/shared/Android.bp index 47ca944c479c..f8ee96ebe7e4 100644 --- a/packages/CredentialManager/shared/Android.bp +++ b/packages/CredentialManager/shared/Android.bp @@ -11,6 +11,7 @@ android_library { name: "CredentialManagerShared", manifest: "AndroidManifest.xml", srcs: ["src/**/*.kt"], + resource_dirs: ["res"], static_libs: [ "androidx.activity_activity-compose", "androidx.core_core-ktx", diff --git a/packages/CredentialManager/shared/res/drawable/ic_passkey_24.xml b/packages/CredentialManager/shared/res/drawable/ic_passkey_24.xml new file mode 100644 index 000000000000..b81f7c500753 --- /dev/null +++ b/packages/CredentialManager/shared/res/drawable/ic_passkey_24.xml @@ -0,0 +1,26 @@ +<!-- + ~ Copyright (C) 2024 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. + --> +<!--LINT.IfChange--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M120,800L120,688Q120,654 137.5,625.5Q155,597 184,582Q246,551 310,535.5Q374,520 440,520Q460,520 480,521.5Q500,523 520,526Q516,584 541,635.5Q566,687 614,720L614,800L120,800ZM760,920L700,860L700,674Q656,661 628,624.5Q600,588 600,540Q600,482 641,441Q682,400 740,400Q798,400 839,441Q880,482 880,540Q880,585 854.5,620Q829,655 790,670L840,720L780,780L840,840L760,920ZM440,480Q374,480 327,433Q280,386 280,320Q280,254 327,207Q374,160 440,160Q506,160 553,207Q600,254 600,320Q600,386 553,433Q506,480 440,480ZM740,560Q757,560 768.5,548.5Q780,537 780,520Q780,503 768.5,491.5Q757,480 740,480Q723,480 711.5,491.5Q700,503 700,520Q700,537 711.5,548.5Q723,560 740,560Z"/> +</vector> +<!--LINT.ThenChange(/packages/CredentialManager/res/drawable/ic_passkey_24.xml)-->
\ No newline at end of file diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt index 02320a193602..39d3f42e8bf4 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt @@ -39,6 +39,7 @@ import androidx.credentials.provider.PasswordCredentialEntry import androidx.credentials.provider.PublicKeyCredentialEntry import androidx.credentials.provider.RemoteEntry import com.android.credentialmanager.IS_AUTO_SELECTED_KEY +import com.android.credentialmanager.R import com.android.credentialmanager.model.get.ActionEntryInfo import com.android.credentialmanager.model.get.AuthenticationEntryInfo import com.android.credentialmanager.model.get.CredentialEntryInfo @@ -147,7 +148,9 @@ private fun getCredentialOptionInfoList( credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(), userName = credentialEntry.username.toString(), displayName = credentialEntry.displayName?.toString(), - icon = credentialEntry.icon.loadDrawable(context), + icon = if (credentialEntry.hasDefaultIcon) + context.getDrawable(R.drawable.ic_passkey_24) + else credentialEntry.icon.loadDrawable(context), shouldTintIcon = credentialEntry.hasDefaultIcon, lastUsedTimeMillis = credentialEntry.lastUsedTime, isAutoSelectable = credentialEntry.isAutoSelectAllowed && diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index 5830b9fc1028..ccf401da5a4c 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -282,6 +282,8 @@ class CreateFlowUtils { val appPreferredDefaultProviderId: String? = if (!requestInfo.hasPermissionToOverrideDefault()) null else createCredentialRequestJetpack?.displayInfo?.preferDefaultProvider + val typeDisplayIcon = createCredentialRequestJetpack?.displayInfo?.credentialTypeIcon + ?.loadDrawable(context) return when (createCredentialRequestJetpack) { is CreatePasswordRequest -> RequestDisplayInfo( createCredentialRequestJetpack.id, @@ -302,7 +304,6 @@ class CreateFlowUtils { newRequestDisplayInfoFromPasskeyJson( requestJson = createCredentialRequestJetpack.requestJson, appLabel = appLabel, - context = context, preferImmediatelyAvailableCredentials = createCredentialRequestJetpack.preferImmediatelyAvailableCredentials, appPreferredDefaultProviderId = appPreferredDefaultProviderId, @@ -311,6 +312,7 @@ class CreateFlowUtils { // the passkey type. For now, directly parse it ourselves. isAutoSelectRequest = createCredentialRequest.credentialData.getBoolean( Constants.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS, false), + typeIcon = context.getDrawable(R.drawable.ic_passkey_24) ?: return null, ) } is CreateCustomCredentialRequest -> { @@ -323,7 +325,7 @@ class CreateFlowUtils { subtitle = displayInfo.userDisplayName?.toString(), type = CredentialType.UNKNOWN, appName = appLabel, - typeIcon = displayInfo.credentialTypeIcon?.loadDrawable(context) + typeIcon = typeDisplayIcon ?: context.getDrawable(R.drawable.ic_other_sign_in_24) ?: return null, preferImmediatelyAvailableCredentials = createCredentialRequestJetpack.preferImmediatelyAvailableCredentials, @@ -502,7 +504,7 @@ class CreateFlowUtils { private fun newRequestDisplayInfoFromPasskeyJson( requestJson: String, appLabel: String, - context: Context, + typeIcon: Drawable, preferImmediatelyAvailableCredentials: Boolean, appPreferredDefaultProviderId: String?, userSetDefaultProviderIds: Set<String>, @@ -525,7 +527,7 @@ class CreateFlowUtils { displayname, CredentialType.PASSKEY, appLabel, - context.getDrawable(R.drawable.ic_passkey_24) ?: return null, + typeIcon, preferImmediatelyAvailableCredentials, appPreferredDefaultProviderId, userSetDefaultProviderIds, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index 72775500e7c5..ccc46604ff98 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -554,15 +554,11 @@ fun CredentialEntryRow( if (credentialEntryInfo.icon == null) painterResource(R.drawable.ic_other_sign_in_24) else null, entryHeadlineText = username, - entrySecondLineText = if ( - credentialEntryInfo.credentialType == CredentialType.PASSWORD) { - "••••••••••••" - } else { - val itemsToDisplay = listOf( + entrySecondLineText = listOf( displayName, credentialEntryInfo.credentialTypeDisplayName, credentialEntryInfo.providerDisplayName - ).filterNot(TextUtils::isEmpty) + ).filterNot(TextUtils::isEmpty).let { itemsToDisplay -> if (itemsToDisplay.isEmpty()) null else itemsToDisplay.joinToString( separator = stringResource(R.string.get_dialog_sign_in_type_username_separator) diff --git a/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar.xml new file mode 100644 index 000000000000..d9a417f1ea99 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar.xml @@ -0,0 +1,37 @@ +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="14dp" + android:height="14dp" + android:viewportWidth="14.0" + android:viewportHeight="14.0"> + <path + android:pathData="M8.25,3L9.25,3A0.5,0.5 0,0 1,9.75 3.5L9.75,13.5A0.5,0.5 0,0 1,9.25 14L8.25,14A0.5,0.5 0,0 1,7.75 13.5L7.75,3.5A0.5,0.5 0,0 1,8.25 3z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M11.75,0L12.75,0A0.5,0.5 0,0 1,13.25 0.5L13.25,13.5A0.5,0.5 0,0 1,12.75 14L11.75,14A0.5,0.5 0,0 1,11.25 13.5L11.25,0.5A0.5,0.5 0,0 1,11.75 0z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M1.25,10L2.25,10A0.5,0.5 0,0 1,2.75 10.5L2.75,13.5A0.5,0.5 0,0 1,2.25 14L1.25,14A0.5,0.5 0,0 1,0.75 13.5L0.75,10.5A0.5,0.5 0,0 1,1.25 10z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M4.75,6.5L5.75,6.5A0.5,0.5 0,0 1,6.25 7L6.25,13.5A0.5,0.5 0,0 1,5.75 14L4.75,14A0.5,0.5 0,0 1,4.25 13.5L4.25,7A0.5,0.5 0,0 1,4.75 6.5z" + android:fillAlpha="0.24" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml new file mode 100644 index 000000000000..facc285a45ca --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml @@ -0,0 +1,28 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="15dp" + android:height="14dp" + android:viewportWidth="15.0" + android:viewportHeight="14.0"> + <path + android:pathData="M7,3.5C7,3.224 7.224,3 7.5,3H8.5C8.776,3 9,3.224 9,3.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V3.5Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M0,10.5C0,10.224 0.224,10 0.5,10H1.5C1.776,10 2,10.224 2,10.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V10.5Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M3.5,7C3.5,6.724 3.724,6.5 4,6.5H5C5.276,6.5 5.5,6.724 5.5,7V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V7Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M11,0C10.724,0 10.5,0.224 10.5,0.5V3H12.5V0.5C12.5,0.224 12.276,0 12,0H11Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M12.25,13C12.25,12.448 12.698,12 13.25,12C13.802,12 14.25,12.448 14.25,13C14.25,13.552 13.802,14 13.25,14C12.698,14 12.25,13.552 12.25,13Z" + android:fillColor="#000"/> + <path + android:pathData="M12.25,5C12.25,4.724 12.474,4.5 12.75,4.5H13.75C14.026,4.5 14.25,4.724 14.25,5V10C14.25,10.276 14.026,10.5 13.75,10.5H12.75C12.474,10.5 12.25,10.276 12.25,10V5Z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar.xml new file mode 100644 index 000000000000..2c05a938c2cf --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar.xml @@ -0,0 +1,41 @@ +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="16dp" + android:height="14dp" + android:viewportWidth="16.0" + android:viewportHeight="14.0"> + <path + android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z" + android:fillAlpha="0.24" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar_error.xml new file mode 100644 index 000000000000..328e45ec7e19 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar_error.xml @@ -0,0 +1,32 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="18dp" + android:height="14dp" + android:viewportWidth="18.0" + android:viewportHeight="14.0"> + <path + android:pathData="M14,0.5C14,0.224 14.224,0 14.5,0H15.5C15.776,0 16,0.224 16,0.5V3H14V0.5Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M0.5,11C0.224,11 0,11.224 0,11.5V13.5C0,13.776 0.224,14 0.5,14H1.5C1.776,14 2,13.776 2,13.5V11.5C2,11.224 1.776,11 1.5,11H0.5Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M4,8C3.724,8 3.5,8.224 3.5,8.5V13.5C3.5,13.776 3.724,14 4,14H5C5.276,14 5.5,13.776 5.5,13.5V8.5C5.5,8.224 5.276,8 5,8H4Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z" + android:fillColor="#000"/> + <path + android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar.xml new file mode 100644 index 000000000000..b9054ba7a4e3 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar.xml @@ -0,0 +1,36 @@ +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="14dp" + android:height="14dp" + android:viewportWidth="14.0" + android:viewportHeight="14.0"> + <path + android:pathData="M8.25,3L9.25,3A0.5,0.5 0,0 1,9.75 3.5L9.75,13.5A0.5,0.5 0,0 1,9.25 14L8.25,14A0.5,0.5 0,0 1,7.75 13.5L7.75,3.5A0.5,0.5 0,0 1,8.25 3z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M11.75,0L12.75,0A0.5,0.5 0,0 1,13.25 0.5L13.25,13.5A0.5,0.5 0,0 1,12.75 14L11.75,14A0.5,0.5 0,0 1,11.25 13.5L11.25,0.5A0.5,0.5 0,0 1,11.75 0z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M1.25,10L2.25,10A0.5,0.5 0,0 1,2.75 10.5L2.75,13.5A0.5,0.5 0,0 1,2.25 14L1.25,14A0.5,0.5 0,0 1,0.75 13.5L0.75,10.5A0.5,0.5 0,0 1,1.25 10z" + android:fillColor="#000"/> + <path + android:pathData="M4.75,6.5L5.75,6.5A0.5,0.5 0,0 1,6.25 7L6.25,13.5A0.5,0.5 0,0 1,5.75 14L4.75,14A0.5,0.5 0,0 1,4.25 13.5L4.25,7A0.5,0.5 0,0 1,4.75 6.5z" + android:fillAlpha="0.24" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml new file mode 100644 index 000000000000..03a93491491c --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml @@ -0,0 +1,27 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="15dp" + android:height="14dp" + android:viewportWidth="15.0" + android:viewportHeight="14.0"> + <path + android:pathData="M7,3.5C7,3.224 7.224,3 7.5,3H8.5C8.776,3 9,3.224 9,3.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V3.5Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M0,10.5C0,10.224 0.224,10 0.5,10H1.5C1.776,10 2,10.224 2,10.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V10.5Z" + android:fillColor="#000"/> + <path + android:pathData="M3.5,7C3.5,6.724 3.724,6.5 4,6.5H5C5.276,6.5 5.5,6.724 5.5,7V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V7Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M11,0C10.724,0 10.5,0.224 10.5,0.5V3H12.5V0.5C12.5,0.224 12.276,0 12,0H11Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M12.25,13C12.25,12.448 12.698,12 13.25,12C13.802,12 14.25,12.448 14.25,13C14.25,13.552 13.802,14 13.25,14C12.698,14 12.25,13.552 12.25,13Z" + android:fillColor="#000"/> + <path + android:pathData="M12.25,5C12.25,4.724 12.474,4.5 12.75,4.5H13.75C14.026,4.5 14.25,4.724 14.25,5V10C14.25,10.276 14.026,10.5 13.75,10.5H12.75C12.474,10.5 12.25,10.276 12.25,10V5Z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar.xml new file mode 100644 index 000000000000..774e91794df3 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar.xml @@ -0,0 +1,40 @@ +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="16dp" + android:height="14dp" + android:viewportWidth="16.0" + android:viewportHeight="14.0"> + <path + android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z" + android:fillColor="#000"/> + <path + android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z" + android:fillAlpha="0.24" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar_error.xml new file mode 100644 index 000000000000..343ec1b2e50f --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar_error.xml @@ -0,0 +1,31 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="18dp" + android:height="14dp" + android:viewportWidth="18.0" + android:viewportHeight="14.0"> + <path + android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M0,11.5C0,11.224 0.224,11 0.5,11H1.5C1.776,11 2,11.224 2,11.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V11.5Z" + android:fillColor="#000"/> + <path + android:pathData="M3.5,8.5C3.5,8.224 3.724,8 4,8H5C5.276,8 5.5,8.224 5.5,8.5V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V8.5Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M14.5,0C14.224,0 14,0.224 14,0.5V3H16V0.5C16,0.224 15.776,0 15.5,0H14.5Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z" + android:fillColor="#000"/> + <path + android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar.xml new file mode 100644 index 000000000000..b699203dd652 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar.xml @@ -0,0 +1,35 @@ +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="14dp" + android:height="14dp" + android:viewportWidth="14.0" + android:viewportHeight="14.0"> + <path + android:pathData="M8.25,3L9.25,3A0.5,0.5 0,0 1,9.75 3.5L9.75,13.5A0.5,0.5 0,0 1,9.25 14L8.25,14A0.5,0.5 0,0 1,7.75 13.5L7.75,3.5A0.5,0.5 0,0 1,8.25 3z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M11.75,0L12.75,0A0.5,0.5 0,0 1,13.25 0.5L13.25,13.5A0.5,0.5 0,0 1,12.75 14L11.75,14A0.5,0.5 0,0 1,11.25 13.5L11.25,0.5A0.5,0.5 0,0 1,11.75 0z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M1.25,10L2.25,10A0.5,0.5 0,0 1,2.75 10.5L2.75,13.5A0.5,0.5 0,0 1,2.25 14L1.25,14A0.5,0.5 0,0 1,0.75 13.5L0.75,10.5A0.5,0.5 0,0 1,1.25 10z" + android:fillColor="#000"/> + <path + android:pathData="M4.75,6.5L5.75,6.5A0.5,0.5 0,0 1,6.25 7L6.25,13.5A0.5,0.5 0,0 1,5.75 14L4.75,14A0.5,0.5 0,0 1,4.25 13.5L4.25,7A0.5,0.5 0,0 1,4.75 6.5z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar_error.xml new file mode 100644 index 000000000000..ba8649b23b38 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar_error.xml @@ -0,0 +1,26 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="15dp" + android:height="14dp" + android:viewportWidth="15.0" + android:viewportHeight="14.0"> + <path + android:pathData="M7,3.5C7,3.224 7.224,3 7.5,3H8.5C8.776,3 9,3.224 9,3.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V3.5Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M0,10.5C0,10.224 0.224,10 0.5,10H1.5C1.776,10 2,10.224 2,10.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V10.5Z" + android:fillColor="#000"/> + <path + android:pathData="M3.5,7C3.5,6.724 3.724,6.5 4,6.5H5C5.276,6.5 5.5,6.724 5.5,7V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V7Z" + android:fillColor="#000"/> + <path + android:pathData="M11,0C10.724,0 10.5,0.224 10.5,0.5V3H12.5V0.5C12.5,0.224 12.276,0 12,0H11Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M12.25,13C12.25,12.448 12.698,12 13.25,12C13.802,12 14.25,12.448 14.25,13C14.25,13.552 13.802,14 13.25,14C12.698,14 12.25,13.552 12.25,13Z" + android:fillColor="#000"/> + <path + android:pathData="M12.25,5C12.25,4.724 12.474,4.5 12.75,4.5H13.75C14.026,4.5 14.25,4.724 14.25,5V10C14.25,10.276 14.026,10.5 13.75,10.5H12.75C12.474,10.5 12.25,10.276 12.25,10V5Z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar.xml new file mode 100644 index 000000000000..43fa734c0c8d --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar.xml @@ -0,0 +1,39 @@ +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="16dp" + android:height="14dp" + android:viewportWidth="16.0" + android:viewportHeight="14.0"> + <path + android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z" + android:fillColor="#000"/> + <path + android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar_error.xml new file mode 100644 index 000000000000..6309e1772d4a --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar_error.xml @@ -0,0 +1,30 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="18dp" + android:height="14dp" + android:viewportWidth="18.0" + android:viewportHeight="14.0"> + <path + android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M0,11.5C0,11.224 0.224,11 0.5,11H1.5C1.776,11 2,11.224 2,11.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V11.5Z" + android:fillColor="#000"/> + <path + android:pathData="M3.5,8.5C3.5,8.224 3.724,8 4,8H5C5.276,8 5.5,8.224 5.5,8.5V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V8.5Z" + android:fillColor="#000"/> + <path + android:pathData="M14.5,0C14.224,0 14,0.224 14,0.5V3H16V0.5C16,0.224 15.776,0 15.5,0H14.5Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z" + android:fillColor="#000"/> + <path + android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar.xml new file mode 100644 index 000000000000..6a218b310b3a --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar.xml @@ -0,0 +1,34 @@ +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="14dp" + android:height="14dp" + android:viewportWidth="14.0" + android:viewportHeight="14.0"> + <path + android:pathData="M8.25,3L9.25,3A0.5,0.5 0,0 1,9.75 3.5L9.75,13.5A0.5,0.5 0,0 1,9.25 14L8.25,14A0.5,0.5 0,0 1,7.75 13.5L7.75,3.5A0.5,0.5 0,0 1,8.25 3z" + android:fillColor="#000"/> + <path + android:pathData="M11.75,0L12.75,0A0.5,0.5 0,0 1,13.25 0.5L13.25,13.5A0.5,0.5 0,0 1,12.75 14L11.75,14A0.5,0.5 0,0 1,11.25 13.5L11.25,0.5A0.5,0.5 0,0 1,11.75 0z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M1.25,10L2.25,10A0.5,0.5 0,0 1,2.75 10.5L2.75,13.5A0.5,0.5 0,0 1,2.25 14L1.25,14A0.5,0.5 0,0 1,0.75 13.5L0.75,10.5A0.5,0.5 0,0 1,1.25 10z" + android:fillColor="#000"/> + <path + android:pathData="M4.75,6.5L5.75,6.5A0.5,0.5 0,0 1,6.25 7L6.25,13.5A0.5,0.5 0,0 1,5.75 14L4.75,14A0.5,0.5 0,0 1,4.25 13.5L4.25,7A0.5,0.5 0,0 1,4.75 6.5z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml new file mode 100644 index 000000000000..27433c79e8bb --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml @@ -0,0 +1,25 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="15dp" + android:height="14dp" + android:viewportWidth="15.0" + android:viewportHeight="14.0"> + <path + android:pathData="M7,3.5C7,3.224 7.224,3 7.5,3H8.5C8.776,3 9,3.224 9,3.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V3.5Z" + android:fillColor="#000"/> + <path + android:pathData="M0,10.5C0,10.224 0.224,10 0.5,10H1.5C1.776,10 2,10.224 2,10.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V10.5Z" + android:fillColor="#000"/> + <path + android:pathData="M3.5,7C3.5,6.724 3.724,6.5 4,6.5H5C5.276,6.5 5.5,6.724 5.5,7V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V7Z" + android:fillColor="#000"/> + <path + android:pathData="M11,0C10.724,0 10.5,0.224 10.5,0.5V3H12.5V0.5C12.5,0.224 12.276,0 12,0H11Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M12.25,13C12.25,12.448 12.698,12 13.25,12C13.802,12 14.25,12.448 14.25,13C14.25,13.552 13.802,14 13.25,14C12.698,14 12.25,13.552 12.25,13Z" + android:fillColor="#000"/> + <path + android:pathData="M12.25,5C12.25,4.724 12.474,4.5 12.75,4.5H13.75C14.026,4.5 14.25,4.724 14.25,5V10C14.25,10.276 14.026,10.5 13.75,10.5H12.75C12.474,10.5 12.25,10.276 12.25,10V5Z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar.xml new file mode 100644 index 000000000000..158ae016ffb5 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar.xml @@ -0,0 +1,38 @@ +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="16dp" + android:height="14dp" + android:viewportWidth="16.0" + android:viewportHeight="14.0"> + <path + android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z" + android:fillColor="#000"/> + <path + android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z" + android:fillColor="#000"/> + <path + android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar_error.xml new file mode 100644 index 000000000000..e0517cfdfeee --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar_error.xml @@ -0,0 +1,29 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="18dp" + android:height="14dp" + android:viewportWidth="18.0" + android:viewportHeight="14.0"> + <path + android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z" + android:fillColor="#000"/> + <path + android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M0,11.5C0,11.224 0.224,11 0.5,11H1.5C1.776,11 2,11.224 2,11.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V11.5Z" + android:fillColor="#000"/> + <path + android:pathData="M3.5,8.5C3.5,8.224 3.724,8 4,8H5C5.276,8 5.5,8.224 5.5,8.5V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V8.5Z" + android:fillColor="#000"/> + <path + android:pathData="M14.5,0C14.224,0 14,0.224 14,0.5V3H16V0.5C16,0.224 15.776,0 15.5,0H14.5Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z" + android:fillColor="#000"/> + <path + android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar.xml new file mode 100644 index 000000000000..1ebd3965f36f --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar.xml @@ -0,0 +1,33 @@ +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="14dp" + android:height="14dp" + android:viewportWidth="14.0" + android:viewportHeight="14.0"> + <path + android:pathData="M8.25,3L9.25,3A0.5,0.5 0,0 1,9.75 3.5L9.75,13.5A0.5,0.5 0,0 1,9.25 14L8.25,14A0.5,0.5 0,0 1,7.75 13.5L7.75,3.5A0.5,0.5 0,0 1,8.25 3z" + android:fillColor="#000"/> + <path + android:pathData="M11.75,0L12.75,0A0.5,0.5 0,0 1,13.25 0.5L13.25,13.5A0.5,0.5 0,0 1,12.75 14L11.75,14A0.5,0.5 0,0 1,11.25 13.5L11.25,0.5A0.5,0.5 0,0 1,11.75 0z" + android:fillColor="#000"/> + <path + android:pathData="M1.25,10L2.25,10A0.5,0.5 0,0 1,2.75 10.5L2.75,13.5A0.5,0.5 0,0 1,2.25 14L1.25,14A0.5,0.5 0,0 1,0.75 13.5L0.75,10.5A0.5,0.5 0,0 1,1.25 10z" + android:fillColor="#000"/> + <path + android:pathData="M4.75,6.5L5.75,6.5A0.5,0.5 0,0 1,6.25 7L6.25,13.5A0.5,0.5 0,0 1,5.75 14L4.75,14A0.5,0.5 0,0 1,4.25 13.5L4.25,7A0.5,0.5 0,0 1,4.75 6.5z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml new file mode 100644 index 000000000000..4473c29d0866 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml @@ -0,0 +1,24 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="15dp" + android:height="14dp" + android:viewportWidth="15.0" + android:viewportHeight="14.0"> + <path + android:pathData="M7,3.5C7,3.224 7.224,3 7.5,3H8.5C8.776,3 9,3.224 9,3.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V3.5Z" + android:fillColor="#000"/> + <path + android:pathData="M0,10.5C0,10.224 0.224,10 0.5,10H1.5C1.776,10 2,10.224 2,10.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V10.5Z" + android:fillColor="#000"/> + <path + android:pathData="M3.5,7C3.5,6.724 3.724,6.5 4,6.5H5C5.276,6.5 5.5,6.724 5.5,7V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V7Z" + android:fillColor="#000"/> + <path + android:pathData="M11,0C10.724,0 10.5,0.224 10.5,0.5V3H12.5V0.5C12.5,0.224 12.276,0 12,0H11Z" + android:fillColor="#000"/> + <path + android:pathData="M12.25,13C12.25,12.448 12.698,12 13.25,12C13.802,12 14.25,12.448 14.25,13C14.25,13.552 13.802,14 13.25,14C12.698,14 12.25,13.552 12.25,13Z" + android:fillColor="#000"/> + <path + android:pathData="M12.25,5C12.25,4.724 12.474,4.5 12.75,4.5H13.75C14.026,4.5 14.25,4.724 14.25,5V10C14.25,10.276 14.026,10.5 13.75,10.5H12.75C12.474,10.5 12.25,10.276 12.25,10V5Z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar.xml new file mode 100644 index 000000000000..1ed6ac86b21a --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar.xml @@ -0,0 +1,37 @@ +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="16dp" + android:height="14dp" + android:viewportWidth="16.0" + android:viewportHeight="14.0"> + <path + android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z" + android:fillColor="#000"/> + <path + android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z" + android:fillColor="#000"/> + <path + android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z" + android:fillColor="#000"/> + <path + android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar_error.xml new file mode 100644 index 000000000000..703e3acd5f75 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar_error.xml @@ -0,0 +1,28 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="18dp" + android:height="14dp" + android:viewportWidth="18.0" + android:viewportHeight="14.0"> + <path + android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z" + android:fillColor="#000"/> + <path + android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z" + android:fillColor="#000"/> + <path + android:pathData="M0,11.5C0,11.224 0.224,11 0.5,11H1.5C1.776,11 2,11.224 2,11.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V11.5Z" + android:fillColor="#000"/> + <path + android:pathData="M3.5,8.5C3.5,8.224 3.724,8 4,8H5C5.276,8 5.5,8.224 5.5,8.5V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V8.5Z" + android:fillColor="#000"/> + <path + android:pathData="M14.5,0C14.224,0 14,0.224 14,0.5V3H16V0.5C16,0.224 15.776,0 15.5,0H14.5Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z" + android:fillColor="#000"/> + <path + android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar.xml new file mode 100644 index 000000000000..420ffb601e8f --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar.xml @@ -0,0 +1,36 @@ +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="16dp" + android:height="14dp" + android:viewportWidth="16.0" + android:viewportHeight="14.0"> + <path + android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z" + android:fillColor="#000"/> + <path + android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z" + android:fillColor="#000"/> + <path + android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z" + android:fillColor="#000"/> + <path + android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z" + android:fillColor="#000"/> + <path + android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar_error.xml new file mode 100644 index 000000000000..e63ca77e9db1 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar_error.xml @@ -0,0 +1,27 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="18dp" + android:height="14dp" + android:viewportWidth="18.0" + android:viewportHeight="14.0"> + <path + android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z" + android:fillColor="#000"/> + <path + android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z" + android:fillColor="#000"/> + <path + android:pathData="M0,11.5C0,11.224 0.224,11 0.5,11H1.5C1.776,11 2,11.224 2,11.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V11.5Z" + android:fillColor="#000"/> + <path + android:pathData="M3.5,8.5C3.5,8.224 3.724,8 4,8H5C5.276,8 5.5,8.224 5.5,8.5V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V8.5Z" + android:fillColor="#000"/> + <path + android:pathData="M14.5,0C14.224,0 14,0.224 14,0.5V3H16V0.5C16,0.224 15.776,0 15.5,0H14.5Z" + android:fillColor="#000"/> + <path + android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z" + android:fillColor="#000"/> + <path + android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_mobile_level_list.xml b/packages/SettingsLib/res/drawable/ic_mobile_level_list.xml new file mode 100644 index 000000000000..6ec6793ea233 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_mobile_level_list.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<!-- In order to pack the 0-4 and 0-5 ranges into a single element, we use a range offset. See +SignalDrawable.java for usage. --> +<level-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:minLevel="0" android:maxLevel="0" android:drawable="@drawable/ic_mobile_0_4_bar" /> + <item android:minLevel="1" android:maxLevel="1" android:drawable="@drawable/ic_mobile_1_4_bar" /> + <item android:minLevel="2" android:maxLevel="2" android:drawable="@drawable/ic_mobile_2_4_bar" /> + <item android:minLevel="3" android:maxLevel="3" android:drawable="@drawable/ic_mobile_3_4_bar" /> + <item android:minLevel="4" android:maxLevel="4" android:drawable="@drawable/ic_mobile_4_4_bar" /> + <item android:minLevel="10" android:maxLevel="10" android:drawable="@drawable/ic_mobile_0_5_bar" /> + <item android:minLevel="11" android:maxLevel="11" android:drawable="@drawable/ic_mobile_1_5_bar" /> + <item android:minLevel="12" android:maxLevel="12" android:drawable="@drawable/ic_mobile_2_5_bar" /> + <item android:minLevel="13" android:maxLevel="13" android:drawable="@drawable/ic_mobile_3_5_bar" /> + <item android:minLevel="14" android:maxLevel="14" android:drawable="@drawable/ic_mobile_4_5_bar" /> + <item android:minLevel="15" android:maxLevel="15" android:drawable="@drawable/ic_mobile_5_5_bar" /> + + <!-- Error states, so we don't have to draw them manually anymore --> + <item android:minLevel="20" android:maxLevel="20" android:drawable="@drawable/ic_mobile_0_4_bar_error" /> + <item android:minLevel="21" android:maxLevel="21" android:drawable="@drawable/ic_mobile_1_4_bar_error" /> + <item android:minLevel="22" android:maxLevel="22" android:drawable="@drawable/ic_mobile_2_4_bar_error" /> + <item android:minLevel="23" android:maxLevel="23" android:drawable="@drawable/ic_mobile_3_4_bar_error" /> + <item android:minLevel="24" android:maxLevel="24" android:drawable="@drawable/ic_mobile_4_4_bar_error" /> + + <item android:minLevel="30" android:maxLevel="30" android:drawable="@drawable/ic_mobile_0_5_bar_error" /> + <item android:minLevel="31" android:maxLevel="31" android:drawable="@drawable/ic_mobile_1_5_bar_error" /> + <item android:minLevel="32" android:maxLevel="32" android:drawable="@drawable/ic_mobile_2_5_bar_error" /> + <item android:minLevel="33" android:maxLevel="33" android:drawable="@drawable/ic_mobile_3_5_bar_error" /> + <item android:minLevel="34" android:maxLevel="34" android:drawable="@drawable/ic_mobile_4_5_bar_error" /> + <item android:minLevel="35" android:maxLevel="35" android:drawable="@drawable/ic_mobile_5_5_bar_error" /> +</level-list> diff --git a/packages/SettingsLib/res/drawable/ic_wifi_0.xml b/packages/SettingsLib/res/drawable/ic_wifi_0.xml new file mode 100644 index 000000000000..8ff65540c505 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_wifi_0.xml @@ -0,0 +1,37 @@ +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="18dp" + android:height="13dp" + android:viewportWidth="18.0" + android:viewportHeight="13.0"> + <path + android:pathData="M0.523,3.314C0.32,3.502 0.32,3.819 0.516,4.015L1.223,4.722C1.418,4.917 1.734,4.916 1.938,4.73C5.936,1.09 12.066,1.09 16.064,4.73C16.268,4.916 16.584,4.917 16.779,4.722L17.486,4.015C17.682,3.819 17.682,3.502 17.479,3.314C12.698,-1.105 5.304,-1.105 0.523,3.314Z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M15.011,6.49C15.207,6.294 15.207,5.976 15.002,5.792C11.592,2.736 6.411,2.736 3,5.792C2.795,5.976 2.795,6.294 2.991,6.49L3.698,7.197C3.893,7.392 4.209,7.39 4.417,7.209C7.042,4.93 10.96,4.93 13.585,7.209C13.793,7.39 14.109,7.392 14.304,7.197L15.011,6.49Z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M5.465,8.964C5.27,8.769 5.269,8.45 5.481,8.273C7.515,6.576 10.487,6.576 12.521,8.273C12.733,8.45 12.732,8.769 12.537,8.964L11.83,9.672C11.634,9.867 11.319,9.863 11.099,9.698C9.859,8.767 8.143,8.767 6.904,9.698C6.683,9.863 6.368,9.867 6.173,9.672L5.465,8.964Z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M10.062,11.439C10.257,11.244 10.259,10.92 10.022,10.779C9.395,10.407 8.608,10.407 7.98,10.779C7.743,10.92 7.745,11.244 7.94,11.439L8.647,12.146C8.843,12.342 9.159,12.342 9.355,12.146L10.062,11.439Z" + android:fillAlpha="0.24" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_wifi_0_error.xml b/packages/SettingsLib/res/drawable/ic_wifi_0_error.xml new file mode 100644 index 000000000000..db31b9dd0fcd --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_wifi_0_error.xml @@ -0,0 +1,28 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="17dp" + android:height="13dp" + android:viewportWidth="17.0" + android:viewportHeight="13.0"> + <path + android:pathData="M0.146,4.015C-0.05,3.819 -0.05,3.502 0.153,3.314C4.002,-0.244 9.545,-0.937 14.055,1.234C13.339,1.449 12.792,2.053 12.66,2.801C8.998,1.281 4.65,1.924 1.568,4.73C1.364,4.916 1.048,4.917 0.853,4.722L0.146,4.015Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M12.63,4.435C9.406,2.836 5.424,3.288 2.63,5.792C2.424,5.976 2.425,6.294 2.621,6.49L3.328,7.197C3.523,7.392 3.839,7.39 4.047,7.209C6.484,5.094 10.033,4.942 12.63,6.753V4.435Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M5.095,8.964C4.9,8.769 4.899,8.45 5.111,8.273C7.145,6.576 10.117,6.576 12.151,8.273C12.363,8.45 12.362,8.769 12.166,8.964L11.459,9.672C11.264,9.867 10.949,9.863 10.728,9.698C9.489,8.767 7.773,8.767 6.533,9.698C6.313,9.863 5.998,9.867 5.802,9.672L5.095,8.964Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M9.652,10.779C9.889,10.92 9.887,11.244 9.692,11.439L8.984,12.146C8.789,12.342 8.473,12.342 8.277,12.146L7.57,11.439C7.375,11.244 7.373,10.92 7.61,10.779C8.237,10.407 9.024,10.407 9.652,10.779Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M14.63,11.15C14.63,10.598 15.078,10.15 15.63,10.15C16.182,10.15 16.63,10.598 16.63,11.15C16.63,11.703 16.182,12.15 15.63,12.15C15.078,12.15 14.63,11.703 14.63,11.15Z" + android:fillColor="#000"/> + <path + android:pathData="M14.63,3.15C14.63,2.874 14.854,2.65 15.13,2.65H16.13C16.406,2.65 16.63,2.874 16.63,3.15V8.15C16.63,8.427 16.406,8.65 16.13,8.65H15.13C14.854,8.65 14.63,8.427 14.63,8.15V3.15Z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_wifi_1.xml b/packages/SettingsLib/res/drawable/ic_wifi_1.xml new file mode 100644 index 000000000000..e170f1dadc94 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_wifi_1.xml @@ -0,0 +1,36 @@ +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="18dp" + android:height="13dp" + android:viewportWidth="18.0" + android:viewportHeight="13.0"> + <path + android:pathData="M0.523,3.314C0.32,3.502 0.32,3.819 0.516,4.015L1.223,4.722C1.418,4.917 1.734,4.916 1.938,4.73C5.936,1.09 12.066,1.09 16.064,4.73C16.268,4.916 16.584,4.917 16.779,4.722L17.486,4.015C17.682,3.819 17.682,3.502 17.479,3.314C12.698,-1.105 5.304,-1.105 0.523,3.314Z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M15.011,6.49C15.207,6.294 15.207,5.976 15.002,5.792C11.592,2.736 6.411,2.736 3,5.792C2.795,5.976 2.795,6.294 2.991,6.49L3.698,7.197C3.893,7.392 4.209,7.39 4.417,7.209C7.042,4.93 10.96,4.93 13.585,7.209C13.793,7.39 14.109,7.392 14.304,7.197L15.011,6.49Z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M5.465,8.964C5.27,8.769 5.269,8.45 5.481,8.273C7.515,6.576 10.487,6.576 12.521,8.273C12.733,8.45 12.732,8.769 12.537,8.964L11.83,9.672C11.634,9.867 11.319,9.863 11.099,9.698C9.859,8.767 8.143,8.767 6.904,9.698C6.683,9.863 6.368,9.867 6.173,9.672L5.465,8.964Z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M10.062,11.439C10.257,11.244 10.259,10.92 10.022,10.779C9.395,10.407 8.608,10.407 7.98,10.779C7.743,10.92 7.745,11.244 7.94,11.439L8.647,12.146C8.843,12.342 9.159,12.342 9.355,12.146L10.062,11.439Z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_wifi_1_error.xml b/packages/SettingsLib/res/drawable/ic_wifi_1_error.xml new file mode 100644 index 000000000000..a4d6a5c00b15 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_wifi_1_error.xml @@ -0,0 +1,27 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="17dp" + android:height="13dp" + android:viewportWidth="17.0" + android:viewportHeight="13.0"> + <path + android:pathData="M0.146,4.015C-0.05,3.819 -0.05,3.502 0.153,3.314C4.002,-0.244 9.545,-0.937 14.055,1.234C13.339,1.449 12.792,2.053 12.66,2.801C8.998,1.281 4.65,1.924 1.568,4.73C1.364,4.916 1.048,4.917 0.853,4.722L0.146,4.015Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M12.63,4.435C9.406,2.836 5.424,3.288 2.63,5.792C2.424,5.976 2.425,6.294 2.621,6.49L3.328,7.197C3.523,7.392 3.839,7.39 4.047,7.209C6.484,5.094 10.033,4.942 12.63,6.753V4.435Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M5.095,8.964C4.9,8.769 4.899,8.45 5.111,8.273C7.145,6.576 10.117,6.576 12.151,8.273C12.363,8.45 12.362,8.769 12.166,8.964L11.459,9.672C11.264,9.867 10.949,9.863 10.728,9.698C9.489,8.767 7.773,8.767 6.533,9.698C6.313,9.863 5.998,9.867 5.802,9.672L5.095,8.964Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M9.652,10.779C9.889,10.92 9.887,11.244 9.692,11.439L8.984,12.146C8.789,12.342 8.473,12.342 8.277,12.146L7.57,11.439C7.375,11.244 7.373,10.92 7.61,10.779C8.237,10.407 9.024,10.407 9.652,10.779Z" + android:fillColor="#000"/> + <path + android:pathData="M14.63,11.15C14.63,10.598 15.078,10.15 15.63,10.15C16.182,10.15 16.63,10.598 16.63,11.15C16.63,11.703 16.182,12.15 15.63,12.15C15.078,12.15 14.63,11.703 14.63,11.15Z" + android:fillColor="#000"/> + <path + android:pathData="M14.63,3.15C14.63,2.874 14.854,2.65 15.13,2.65H16.13C16.406,2.65 16.63,2.874 16.63,3.15V8.15C16.63,8.427 16.406,8.65 16.13,8.65H15.13C14.854,8.65 14.63,8.427 14.63,8.15V3.15Z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_wifi_2.xml b/packages/SettingsLib/res/drawable/ic_wifi_2.xml new file mode 100644 index 000000000000..fc62267ad5b0 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_wifi_2.xml @@ -0,0 +1,35 @@ +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="18dp" + android:height="13dp" + android:viewportWidth="18.0" + android:viewportHeight="13.0"> + <path + android:pathData="M0.523,3.314C0.32,3.502 0.32,3.819 0.516,4.015L1.223,4.722C1.418,4.917 1.734,4.916 1.938,4.73C5.936,1.09 12.066,1.09 16.064,4.73C16.268,4.916 16.584,4.917 16.779,4.722L17.486,4.015C17.682,3.819 17.682,3.502 17.479,3.314C12.698,-1.105 5.304,-1.105 0.523,3.314Z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M15.011,6.49C15.207,6.294 15.207,5.976 15.002,5.792C11.592,2.736 6.411,2.736 3,5.792C2.795,5.976 2.795,6.294 2.991,6.49L3.698,7.197C3.893,7.392 4.209,7.39 4.417,7.209C7.042,4.93 10.96,4.93 13.585,7.209C13.793,7.39 14.109,7.392 14.304,7.197L15.011,6.49Z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M5.465,8.964C5.27,8.769 5.269,8.45 5.481,8.273C7.515,6.576 10.487,6.576 12.521,8.273C12.733,8.45 12.732,8.769 12.537,8.964L11.83,9.672C11.634,9.867 11.319,9.863 11.099,9.698C9.859,8.767 8.143,8.767 6.904,9.698C6.683,9.863 6.368,9.867 6.173,9.672L5.465,8.964Z" + android:fillColor="#000"/> + <path + android:pathData="M10.062,11.439C10.257,11.244 10.259,10.92 10.022,10.779C9.395,10.407 8.608,10.407 7.98,10.779C7.743,10.92 7.745,11.244 7.94,11.439L8.647,12.146C8.843,12.342 9.159,12.342 9.355,12.146L10.062,11.439Z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_wifi_2_error.xml b/packages/SettingsLib/res/drawable/ic_wifi_2_error.xml new file mode 100644 index 000000000000..65f40eff1ca8 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_wifi_2_error.xml @@ -0,0 +1,26 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="17dp" + android:height="13dp" + android:viewportWidth="17.0" + android:viewportHeight="13.0"> + <path + android:pathData="M0.146,4.015C-0.05,3.819 -0.05,3.502 0.153,3.314C4.002,-0.244 9.545,-0.937 14.055,1.234C13.339,1.449 12.792,2.053 12.66,2.801C8.998,1.281 4.65,1.924 1.568,4.73C1.364,4.916 1.048,4.917 0.853,4.722L0.146,4.015Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M12.63,4.435C9.406,2.836 5.424,3.288 2.63,5.792C2.424,5.976 2.425,6.294 2.621,6.49L3.328,7.197C3.523,7.392 3.839,7.39 4.047,7.209C6.484,5.094 10.033,4.942 12.63,6.753V4.435Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M5.095,8.964C4.9,8.769 4.899,8.45 5.111,8.273C7.145,6.576 10.117,6.576 12.151,8.273C12.363,8.45 12.362,8.769 12.166,8.964L11.459,9.672C11.264,9.867 10.949,9.863 10.728,9.698C9.489,8.767 7.773,8.767 6.533,9.698C6.313,9.863 5.998,9.867 5.802,9.672L5.095,8.964Z" + android:fillColor="#000"/> + <path + android:pathData="M9.652,10.779C9.889,10.92 9.887,11.244 9.692,11.439L8.984,12.146C8.789,12.342 8.473,12.342 8.277,12.146L7.57,11.439C7.375,11.244 7.373,10.92 7.61,10.779C8.237,10.407 9.024,10.407 9.652,10.779Z" + android:fillColor="#000"/> + <path + android:pathData="M14.63,11.15C14.63,10.598 15.078,10.15 15.63,10.15C16.182,10.15 16.63,10.598 16.63,11.15C16.63,11.703 16.182,12.15 15.63,12.15C15.078,12.15 14.63,11.703 14.63,11.15Z" + android:fillColor="#000"/> + <path + android:pathData="M14.63,3.15C14.63,2.874 14.854,2.65 15.13,2.65H16.13C16.406,2.65 16.63,2.874 16.63,3.15V8.15C16.63,8.427 16.406,8.65 16.13,8.65H15.13C14.854,8.65 14.63,8.427 14.63,8.15V3.15Z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_wifi_3.xml b/packages/SettingsLib/res/drawable/ic_wifi_3.xml new file mode 100644 index 000000000000..9079daf922b8 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_wifi_3.xml @@ -0,0 +1,34 @@ +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="18dp" + android:height="13dp" + android:viewportWidth="18.0" + android:viewportHeight="13.0"> + <path + android:pathData="M0.523,3.314C0.32,3.502 0.32,3.819 0.516,4.015L1.223,4.722C1.418,4.917 1.734,4.916 1.938,4.73C5.936,1.09 12.066,1.09 16.064,4.73C16.268,4.916 16.584,4.917 16.779,4.722L17.486,4.015C17.682,3.819 17.682,3.502 17.479,3.314C12.698,-1.105 5.304,-1.105 0.523,3.314Z" + android:fillAlpha="0.24" + android:fillColor="#000"/> + <path + android:pathData="M15.011,6.49C15.207,6.294 15.207,5.976 15.002,5.792C11.592,2.736 6.411,2.736 3,5.792C2.795,5.976 2.795,6.294 2.991,6.49L3.698,7.197C3.893,7.392 4.209,7.39 4.417,7.209C7.042,4.93 10.96,4.93 13.585,7.209C13.793,7.39 14.109,7.392 14.304,7.197L15.011,6.49Z" + android:fillColor="#000"/> + <path + android:pathData="M5.465,8.964C5.27,8.769 5.269,8.45 5.481,8.273C7.515,6.576 10.487,6.576 12.521,8.273C12.733,8.45 12.732,8.769 12.537,8.964L11.83,9.672C11.634,9.867 11.319,9.863 11.099,9.698C9.859,8.767 8.143,8.767 6.904,9.698C6.683,9.863 6.368,9.867 6.173,9.672L5.465,8.964Z" + android:fillColor="#000"/> + <path + android:pathData="M10.062,11.439C10.257,11.244 10.259,10.92 10.022,10.779C9.395,10.407 8.608,10.407 7.98,10.779C7.743,10.92 7.745,11.244 7.94,11.439L8.647,12.146C8.843,12.342 9.159,12.342 9.355,12.146L10.062,11.439Z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_wifi_3_error.xml b/packages/SettingsLib/res/drawable/ic_wifi_3_error.xml new file mode 100644 index 000000000000..940781bbb1ca --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_wifi_3_error.xml @@ -0,0 +1,25 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="17dp" + android:height="13dp" + android:viewportWidth="17.0" + android:viewportHeight="13.0"> + <path + android:pathData="M0.146,4.015C-0.05,3.819 -0.05,3.502 0.153,3.314C4.002,-0.244 9.545,-0.937 14.055,1.234C13.339,1.449 12.792,2.053 12.66,2.801C8.998,1.281 4.65,1.924 1.568,4.73C1.364,4.916 1.048,4.917 0.853,4.722L0.146,4.015Z" + android:fillAlpha="0.3" + android:fillColor="#000"/> + <path + android:pathData="M12.63,4.435C9.406,2.836 5.424,3.288 2.63,5.792C2.424,5.976 2.425,6.294 2.621,6.49L3.328,7.197C3.523,7.392 3.839,7.39 4.047,7.209C6.484,5.094 10.033,4.942 12.63,6.753V4.435Z" + android:fillColor="#000"/> + <path + android:pathData="M5.095,8.964C4.9,8.769 4.899,8.45 5.111,8.273C7.145,6.576 10.117,6.576 12.151,8.273C12.363,8.45 12.362,8.769 12.166,8.964L11.459,9.672C11.264,9.867 10.949,9.863 10.728,9.698C9.489,8.767 7.773,8.767 6.533,9.698C6.313,9.863 5.998,9.867 5.802,9.672L5.095,8.964Z" + android:fillColor="#000"/> + <path + android:pathData="M9.652,10.779C9.889,10.92 9.887,11.244 9.692,11.439L8.984,12.146C8.789,12.342 8.473,12.342 8.277,12.146L7.57,11.439C7.375,11.244 7.373,10.92 7.61,10.779C8.237,10.407 9.024,10.407 9.652,10.779Z" + android:fillColor="#000"/> + <path + android:pathData="M14.63,11.15C14.63,10.598 15.078,10.15 15.63,10.15C16.182,10.15 16.63,10.598 16.63,11.15C16.63,11.703 16.182,12.15 15.63,12.15C15.078,12.15 14.63,11.703 14.63,11.15Z" + android:fillColor="#000"/> + <path + android:pathData="M14.63,3.15C14.63,2.874 14.854,2.65 15.13,2.65H16.13C16.406,2.65 16.63,2.874 16.63,3.15V8.15C16.63,8.427 16.406,8.65 16.13,8.65H15.13C14.854,8.65 14.63,8.427 14.63,8.15V3.15Z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_wifi_4.xml b/packages/SettingsLib/res/drawable/ic_wifi_4.xml new file mode 100644 index 000000000000..6185e4a83332 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_wifi_4.xml @@ -0,0 +1,33 @@ +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="18dp" + android:height="13dp" + android:viewportWidth="18.0" + android:viewportHeight="13.0"> + <path + android:pathData="M0.523,3.314C0.32,3.502 0.32,3.819 0.516,4.015L1.223,4.722C1.418,4.917 1.734,4.916 1.938,4.73C5.936,1.09 12.066,1.09 16.064,4.73C16.268,4.916 16.584,4.917 16.779,4.722L17.486,4.015C17.682,3.819 17.682,3.502 17.479,3.314C12.698,-1.105 5.304,-1.105 0.523,3.314Z" + android:fillColor="#000"/> + <path + android:pathData="M15.011,6.49C15.207,6.294 15.207,5.976 15.002,5.792C11.592,2.736 6.411,2.736 3,5.792C2.795,5.976 2.795,6.294 2.991,6.49L3.698,7.197C3.893,7.392 4.209,7.39 4.417,7.209C7.042,4.93 10.96,4.93 13.585,7.209C13.793,7.39 14.109,7.392 14.304,7.197L15.011,6.49Z" + android:fillColor="#000"/> + <path + android:pathData="M5.465,8.964C5.27,8.769 5.269,8.45 5.481,8.273C7.515,6.576 10.487,6.576 12.521,8.273C12.733,8.45 12.732,8.769 12.537,8.964L11.83,9.672C11.634,9.867 11.319,9.863 11.099,9.698C9.859,8.767 8.143,8.767 6.904,9.698C6.683,9.863 6.368,9.867 6.173,9.672L5.465,8.964Z" + android:fillColor="#000"/> + <path + android:pathData="M10.062,11.439C10.257,11.244 10.259,10.92 10.022,10.779C9.395,10.407 8.608,10.407 7.98,10.779C7.743,10.92 7.745,11.244 7.94,11.439L8.647,12.146C8.843,12.342 9.159,12.342 9.355,12.146L10.062,11.439Z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_wifi_4_error.xml b/packages/SettingsLib/res/drawable/ic_wifi_4_error.xml new file mode 100644 index 000000000000..715aaa0982e9 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_wifi_4_error.xml @@ -0,0 +1,24 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="17dp" + android:height="13dp" + android:viewportWidth="17.0" + android:viewportHeight="13.0"> + <path + android:pathData="M0.146,4.015C-0.05,3.819 -0.05,3.502 0.153,3.314C4.002,-0.244 9.545,-0.937 14.055,1.234C13.339,1.449 12.792,2.053 12.66,2.801C8.998,1.281 4.65,1.924 1.568,4.73C1.364,4.916 1.048,4.917 0.853,4.722L0.146,4.015Z" + android:fillColor="#000"/> + <path + android:pathData="M12.63,4.435C9.406,2.836 5.424,3.288 2.63,5.792C2.424,5.976 2.425,6.294 2.621,6.49L3.328,7.197C3.523,7.392 3.839,7.39 4.047,7.209C6.484,5.094 10.033,4.942 12.63,6.753V4.435Z" + android:fillColor="#000"/> + <path + android:pathData="M5.095,8.964C4.9,8.769 4.899,8.45 5.111,8.273C7.145,6.576 10.117,6.576 12.151,8.273C12.363,8.45 12.362,8.769 12.166,8.964L11.459,9.672C11.264,9.867 10.949,9.863 10.728,9.698C9.489,8.767 7.773,8.767 6.533,9.698C6.313,9.863 5.998,9.867 5.802,9.672L5.095,8.964Z" + android:fillColor="#000"/> + <path + android:pathData="M9.652,10.779C9.889,10.92 9.887,11.244 9.692,11.439L8.984,12.146C8.789,12.342 8.473,12.342 8.277,12.146L7.57,11.439C7.375,11.244 7.373,10.92 7.61,10.779C8.237,10.407 9.024,10.407 9.652,10.779Z" + android:fillColor="#000"/> + <path + android:pathData="M14.63,11.15C14.63,10.598 15.078,10.15 15.63,10.15C16.182,10.15 16.63,10.598 16.63,11.15C16.63,11.703 16.182,12.15 15.63,12.15C15.078,12.15 14.63,11.703 14.63,11.15Z" + android:fillColor="#000"/> + <path + android:pathData="M14.63,3.15C14.63,2.874 14.854,2.65 15.13,2.65H16.13C16.406,2.65 16.63,2.874 16.63,3.15V8.15C16.63,8.427 16.406,8.65 16.13,8.65H15.13C14.854,8.65 14.63,8.427 14.63,8.15V3.15Z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java index 9a19f9368449..ef0f6cbc6ed9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java +++ b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java @@ -14,6 +14,8 @@ package com.android.settingslib.graph; +import static com.android.settingslib.flags.Flags.newStatusBarIcons; + import android.animation.ArgbEvaluator; import android.annotation.IntRange; import android.content.Context; @@ -67,6 +69,9 @@ public class SignalDrawable extends DrawableWrapper { private static final long DOT_DELAY = 1000; + // Check the config for which icon we want to use + private static final int ICON_RES = SignalDrawable.getIconRes(); + private final Paint mForegroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint mTransparentPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final int mDarkModeFillColor; @@ -85,7 +90,7 @@ public class SignalDrawable extends DrawableWrapper { private int mCurrentDot; public SignalDrawable(Context context) { - super(context.getDrawable(com.android.internal.R.drawable.ic_signal_cellular)); + super(context.getDrawable(ICON_RES)); final String attributionPathString = context.getString( com.android.internal.R.string.config_signalAttributionPath); mAttributionPath.set(PathParser.createPathFromPathData(attributionPathString)); @@ -147,9 +152,17 @@ public class SignalDrawable extends DrawableWrapper { private int unpackLevel(int packedState) { int numBins = (packedState & NUM_LEVEL_MASK) >> NUM_LEVEL_SHIFT; + int cutOutOffset = 0; int levelOffset = numBins == (CellSignalStrength.getNumSignalStrengthLevels() + 1) ? 10 : 0; int level = (packedState & LEVEL_MASK); - return level + levelOffset; + + if (newStatusBarIcons()) { + if (isInState(STATE_CUT)) { + cutOutOffset = 20; + } + } + + return level + levelOffset + cutOutOffset; } public void setDarkIntensity(float darkIntensity) { @@ -214,7 +227,7 @@ public class SignalDrawable extends DrawableWrapper { drawDotAndPadding(x - dotSpacing * 2, y, dotPadding, dotSize, 0); canvas.drawPath(mCutoutPath, mTransparentPaint); canvas.drawPath(mForegroundPath, mForegroundPaint); - } else if (isInState(STATE_CUT)) { + } else if (!newStatusBarIcons() && isInState(STATE_CUT)) { float cutX = (mCutoutWidthFraction * width / VIEWPORT); float cutY = (mCutoutHeightFraction * height / VIEWPORT); mCutoutPath.moveTo(width, height); @@ -300,4 +313,12 @@ public class SignalDrawable extends DrawableWrapper { public static int getCarrierChangeState(int numLevels) { return (STATE_CARRIER_CHANGE << STATE_SHIFT) | (numLevels << NUM_LEVEL_SHIFT); } + + private static int getIconRes() { + if (newStatusBarIcons()) { + return R.drawable.ic_mobile_level_list; + } else { + return com.android.internal.R.drawable.ic_signal_cellular; + } + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index 50e2f9cb13f7..bdb58719b1a8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -139,7 +139,6 @@ public abstract class InfoMediaManager extends MediaManager { } } - @Override public void startScan() { mMediaDevices.clear(); startScanOnRouter(); @@ -156,7 +155,6 @@ public abstract class InfoMediaManager extends MediaManager { } } - @Override public abstract void stopScan(); protected abstract void startScanOnRouter(); diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java index dfbf23f426fb..8bebd6ee3448 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java @@ -54,16 +54,6 @@ public abstract class MediaManager { } } - /** - * Start scan connected MediaDevice - */ - public abstract void startScan(); - - /** - * Stop scan MediaDevice - */ - public abstract void stopScan(); - protected MediaDevice findMediaDevice(String id) { for (MediaDevice mediaDevice : mMediaDevices) { if (mediaDevice.getId().equals(id)) { diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java index b9a464752824..69f83a4dfa3c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java @@ -19,6 +19,8 @@ package com.android.settingslib.wifi; import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED; import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.getMaxNetworkSelectionDisableReason; +import static com.android.settingslib.flags.Flags.newStatusBarIcons; + import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; @@ -88,21 +90,49 @@ public class WifiUtils { public static final String KEY_CHOSEN_WIFIENTRY_KEY = "key_chosen_wifientry_key"; public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args"; - static final int[] WIFI_PIE = { - com.android.internal.R.drawable.ic_wifi_signal_0, - com.android.internal.R.drawable.ic_wifi_signal_1, - com.android.internal.R.drawable.ic_wifi_signal_2, - com.android.internal.R.drawable.ic_wifi_signal_3, - com.android.internal.R.drawable.ic_wifi_signal_4 - }; - - static final int[] NO_INTERNET_WIFI_PIE = { - R.drawable.ic_no_internet_wifi_signal_0, - R.drawable.ic_no_internet_wifi_signal_1, - R.drawable.ic_no_internet_wifi_signal_2, - R.drawable.ic_no_internet_wifi_signal_3, - R.drawable.ic_no_internet_wifi_signal_4 - }; + static final int[] WIFI_PIE = getIconsBasedOnFlag(); + + private static int[] getIconsBasedOnFlag() { + if (newStatusBarIcons()) { + return new int[] { + R.drawable.ic_wifi_0, + R.drawable.ic_wifi_1, + R.drawable.ic_wifi_2, + R.drawable.ic_wifi_3, + R.drawable.ic_wifi_4 + }; + } else { + return new int[] { + com.android.internal.R.drawable.ic_wifi_signal_0, + com.android.internal.R.drawable.ic_wifi_signal_1, + com.android.internal.R.drawable.ic_wifi_signal_2, + com.android.internal.R.drawable.ic_wifi_signal_3, + com.android.internal.R.drawable.ic_wifi_signal_4 + }; + } + } + + static final int[] NO_INTERNET_WIFI_PIE = getErrorIconsBasedOnFlag(); + + private static int [] getErrorIconsBasedOnFlag() { + if (newStatusBarIcons()) { + return new int[] { + R.drawable.ic_wifi_0_error, + R.drawable.ic_wifi_1_error, + R.drawable.ic_wifi_2_error, + R.drawable.ic_wifi_3_error, + R.drawable.ic_wifi_4_error + }; + } else { + return new int[] { + R.drawable.ic_no_internet_wifi_signal_0, + R.drawable.ic_no_internet_wifi_signal_1, + R.drawable.ic_no_internet_wifi_signal_2, + R.drawable.ic_no_internet_wifi_signal_3, + R.drawable.ic_no_internet_wifi_signal_4 + }; + } + } public static String buildLoggingSummary(AccessPoint accessPoint, WifiConfiguration config) { final StringBuilder summary = new StringBuilder(); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java index 3b731921f201..46e724d245f5 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java @@ -52,17 +52,7 @@ public class MediaManagerTest { when(mDevice.getId()).thenReturn(TEST_ID); - mMediaManager = new MediaManager(mContext, null) { - @Override - public void startScan() { - - } - - @Override - public void stopScan() { - - } - }; + mMediaManager = new MediaManager(mContext, null) {}; } @Test diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 609f314bb540..078da1c863ce 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -17,6 +17,7 @@ package com.android.systemui.communal.ui.compose import android.appwidget.AppWidgetHostView +import android.graphics.drawable.Icon import android.os.Bundle import android.util.SizeF import android.widget.FrameLayout @@ -26,6 +27,7 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -77,6 +79,8 @@ import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ColorMatrix import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.LayoutCoordinates @@ -85,8 +89,10 @@ import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.positionInWindow import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId @@ -101,6 +107,8 @@ import androidx.compose.ui.window.Popup import androidx.core.view.setPadding import com.android.compose.modifiers.thenIf import com.android.compose.theme.LocalAndroidColorScheme +import com.android.compose.ui.graphics.painter.rememberDrawablePainter +import com.android.internal.R.dimen.system_app_widget_background_radius import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.model.CommunalContentSize import com.android.systemui.communal.ui.compose.Dimensions.CardOutlineWidth @@ -178,7 +186,7 @@ fun CommunalHub( // not display this button. if ( index == null || - communalContent[index].isWidget() || + communalContent[index].isWidgetContent() || communalContent[index] is CommunalContentModel.CtaTileInViewMode ) { isButtonToEditWidgetsShowing = true @@ -330,7 +338,7 @@ private fun BoxScope.CommunalHubLazyGrid( DraggableItem( dragDropState = dragDropState, selected = selected, - enabled = list[index] is CommunalContentModel.Widget, + enabled = list[index].isWidgetContent(), index = index, ) { isDragging -> CommunalContent( @@ -539,9 +547,11 @@ private fun CommunalContent( widgetConfigurator: WidgetConfigurator? = null, ) { when (model) { - is CommunalContentModel.Widget -> + is CommunalContentModel.WidgetContent.Widget -> WidgetContent(viewModel, model, size, selected, widgetConfigurator, modifier) is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(modifier) + is CommunalContentModel.WidgetContent.DisabledWidget -> + DisabledWidgetPlaceholder(model, modifier) is CommunalContentModel.CtaTileInViewMode -> CtaTileInViewModeContent(viewModel, modifier) is CommunalContentModel.CtaTileInEditMode -> CtaTileInEditModeContent(modifier, onOpenWidgetPicker) @@ -672,7 +682,7 @@ private fun CtaTileInEditModeContent( @Composable private fun WidgetContent( viewModel: BaseCommunalViewModel, - model: CommunalContentModel.Widget, + model: CommunalContentModel.WidgetContent.Widget, size: SizeF, selected: Boolean, widgetConfigurator: WidgetConfigurator?, @@ -692,8 +702,9 @@ private fun WidgetContent( }, update = { view -> // Remove the extra padding applied to AppWidgetHostView to allow widgets to - // occupy the entire box. The added padding is now adjusted to leave only sufficient - // space for displaying the outline around the box when the widget is selected. + // occupy the entire box. The added padding is now adjusted to leave only + // sufficient space for displaying the outline around the box when the widget + // is selected. view.setPadding(paddingInPx) }, // For reusing composition in lazy lists. @@ -717,7 +728,7 @@ private fun WidgetContent( @Composable fun WidgetConfigureButton( visible: Boolean, - model: CommunalContentModel.Widget, + model: CommunalContentModel.WidgetContent.Widget, modifier: Modifier = Modifier, widgetConfigurator: WidgetConfigurator, ) { @@ -752,6 +763,38 @@ fun WidgetConfigureButton( } @Composable +fun DisabledWidgetPlaceholder( + model: CommunalContentModel.WidgetContent.DisabledWidget, + modifier: Modifier = Modifier, +) { + val context = LocalContext.current + val appInfo = model.appInfo + val icon: Icon = + if (appInfo == null || appInfo.icon == 0) { + Icon.createWithResource(context, android.R.drawable.sym_def_app_icon) + } else { + Icon.createWithResource(appInfo.packageName, appInfo.icon) + } + + Column( + modifier = + modifier.background( + MaterialTheme.colorScheme.surfaceVariant, + RoundedCornerShape(dimensionResource(system_app_widget_background_radius)) + ), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Image( + painter = rememberDrawablePainter(icon.loadDrawable(context)), + contentDescription = stringResource(R.string.icon_description_for_disabled_widget), + modifier = Modifier.size(48.dp), + colorFilter = ColorFilter.colorMatrix(Colors.DisabledColorFilter), + ) + } +} + +@Composable private fun SmartspaceContent( model: CommunalContentModel.Smartspace, modifier: Modifier = Modifier, @@ -852,7 +895,7 @@ private fun firstIndexAtOffset(gridState: LazyGridState, offset: Offset): Int? = /** Returns the key of item if it's editable at the given index. Only widget is editable. */ private fun keyAtIndexIfEditable(list: List<CommunalContentModel>, index: Int): String? = - if (index in list.indices && list[index].isWidget()) list[index].key else null + if (index in list.indices && list[index].isWidgetContent()) list[index].key else null data class ContentPaddingInPx(val start: Float, val top: Float) { fun toOffset(): Offset = Offset(start, top) @@ -882,5 +925,30 @@ object Dimensions { val IconSize = 48.dp } +private object Colors { + val DisabledColorFilter by lazy { disabledColorMatrix() } + + /** Returns the disabled image filter. Ported over from [DisableImageView]. */ + private fun disabledColorMatrix(): ColorMatrix { + val brightnessMatrix = ColorMatrix() + val brightnessAmount = 0.5f + val brightnessRgb = (255 * brightnessAmount).toInt().toFloat() + // Brightness: C-new = C-old*(1-amount) + amount + val scale = 1f - brightnessAmount + val mat = brightnessMatrix.values + mat[0] = scale + mat[6] = scale + mat[12] = scale + mat[4] = brightnessRgb + mat[9] = brightnessRgb + mat[14] = brightnessRgb + + return ColorMatrix().apply { + setToSaturation(0F) + timesAssign(brightnessMatrix) + } + } +} + /** The resource id of communal hub accessible from UiAutomator. */ private const val COMMUNAL_HUB_TEST_TAG = "communal_hub" diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt index 9b8c9d0ab616..c5dab3347e5f 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt @@ -71,8 +71,8 @@ internal constructor( /** Remove widget from the list and the database. */ fun onRemove(indexToRemove: Int) { - if (list[indexToRemove] is CommunalContentModel.Widget) { - val widget = list[indexToRemove] as CommunalContentModel.Widget + if (list[indexToRemove].isWidgetContent()) { + val widget = list[indexToRemove] as CommunalContentModel.WidgetContent list.apply { removeAt(indexToRemove) } onDeleteWidget(widget.appWidgetId) } @@ -100,7 +100,7 @@ internal constructor( val widgetIdToPriorityMap: Map<Int, Int> = list .mapIndexedNotNull { index, item -> - if (item is CommunalContentModel.Widget) { + if (item is CommunalContentModel.WidgetContent) { item.appWidgetId to list.size - index } else { null @@ -115,5 +115,5 @@ internal constructor( } /** Returns true if the item at given index is editable. */ - fun isItemEditable(index: Int) = list[index] is CommunalContentModel.Widget + fun isItemEditable(index: Int) = list[index].isWidgetContent() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index cd296524c17c..6e3573b64f9a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -27,6 +27,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository import com.android.systemui.communal.data.repository.FakeCommunalRepository @@ -62,6 +63,7 @@ import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -831,6 +833,95 @@ class CommunalInteractorTest : SysuiTestCase() { } } + @Test + fun widgetContent_containsDisabledWidgets_whenCategoryNotAllowed() = + testScope.runTest { + // Communal available, and tutorial completed. + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setKeyguardOccluded(false) + tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + userRepository.setSelectedUserInfo(mainUser) + + val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK) + userRepository.setUserInfos(userInfos) + userTracker.set( + userInfos = userInfos, + selectedUserIndex = 0, + ) + runCurrent() + + // Widgets available. + val widget1 = + createWidgetWithCategory(1, AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN) + val widget2 = + createWidgetWithCategory(2, AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) + val widget3 = + createWidgetWithCategory(3, AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) + val widgets = listOf(widget1, widget2, widget3) + widgetRepository.setCommunalWidgets(widgets) + + val widgetContent by collectLastValue(underTest.widgetContent) + kosmos.fakeSettings.putIntForUser( + CommunalSettingsRepositoryImpl.GLANCEABLE_HUB_CONTENT_SETTING, + AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD, + mainUser.id + ) + runCurrent() + + // Only the keyguard widget is enabled. + assertThat(widgetContent).hasSize(3) + assertThat(widgetContent!!.get(0)) + .isInstanceOf(CommunalContentModel.WidgetContent.DisabledWidget::class.java) + assertThat(widgetContent!!.get(1)) + .isInstanceOf(CommunalContentModel.WidgetContent.Widget::class.java) + assertThat(widgetContent!!.get(2)) + .isInstanceOf(CommunalContentModel.WidgetContent.DisabledWidget::class.java) + } + + @Test + fun widgetContent_allEnabled_whenCategoryAllowed() = + testScope.runTest { + // Communal available, and tutorial completed. + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setKeyguardOccluded(false) + tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + userRepository.setSelectedUserInfo(mainUser) + + val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK) + userRepository.setUserInfos(userInfos) + userTracker.set( + userInfos = userInfos, + selectedUserIndex = 0, + ) + runCurrent() + + // Widgets available. + val widget1 = + createWidgetWithCategory(1, AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN) + val widget2 = + createWidgetWithCategory(2, AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) + val widget3 = + createWidgetWithCategory(3, AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) + val widgets = listOf(widget1, widget2, widget3) + widgetRepository.setCommunalWidgets(widgets) + + val widgetContent by collectLastValue(underTest.widgetContent) + kosmos.fakeSettings.putIntForUser( + CommunalSettingsRepositoryImpl.GLANCEABLE_HUB_CONTENT_SETTING, + AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD or + AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN, + mainUser.id + ) + runCurrent() + + // All widgets are enabled. + assertThat(widgetContent).hasSize(3) + widgetContent!!.forEach { model -> + assertThat(model) + .isInstanceOf(CommunalContentModel.WidgetContent.Widget::class.java) + } + } + private fun smartspaceTimer(id: String, timestamp: Long = 0L): SmartspaceTarget { val timer = mock(SmartspaceTarget::class.java) whenever(timer.smartspaceTargetId).thenReturn(id) @@ -848,6 +939,17 @@ class CommunalInteractorTest : SysuiTestCase() { whenever(this.providerInfo).thenReturn(providerInfo) } + private fun createWidgetWithCategory( + appWidgetId: Int, + category: Int + ): CommunalWidgetContentModel = + mock<CommunalWidgetContentModel> { + whenever(this.appWidgetId).thenReturn(appWidgetId) + val providerInfo = mock<AppWidgetProviderInfo>().apply { widgetCategory = category } + whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id)) + whenever(this.providerInfo).thenReturn(providerInfo) + } + private companion object { val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN) val USER_INFO_WORK = UserInfo(10, "work", UserInfo.FLAG_PROFILE) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt index 5ee88cb92fa0..8e2e94716660 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt @@ -135,9 +135,9 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { // Only Widgets and CTA tile are shown. assertThat(communalContent?.size).isEqualTo(3) assertThat(communalContent?.get(0)) - .isInstanceOf(CommunalContentModel.Widget::class.java) + .isInstanceOf(CommunalContentModel.WidgetContent::class.java) assertThat(communalContent?.get(1)) - .isInstanceOf(CommunalContentModel.Widget::class.java) + .isInstanceOf(CommunalContentModel.WidgetContent::class.java) assertThat(communalContent?.get(2)) .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java) } @@ -181,9 +181,9 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { // Widgets and CTA tile are shown. assertThat(communalContent?.size).isEqualTo(3) assertThat(communalContent?.get(0)) - .isInstanceOf(CommunalContentModel.Widget::class.java) + .isInstanceOf(CommunalContentModel.WidgetContent::class.java) assertThat(communalContent?.get(1)) - .isInstanceOf(CommunalContentModel.Widget::class.java) + .isInstanceOf(CommunalContentModel.WidgetContent::class.java) assertThat(communalContent?.get(2)) .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java) @@ -192,7 +192,8 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { // Only one widget and CTA tile remain. assertThat(communalContent?.size).isEqualTo(2) val item = communalContent?.get(0) - val appWidgetId = if (item is CommunalContentModel.Widget) item.appWidgetId else null + val appWidgetId = + if (item is CommunalContentModel.WidgetContent) item.appWidgetId else null assertThat(appWidgetId).isEqualTo(widgets.get(1).appWidgetId) assertThat(communalContent?.get(1)) .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt index 1e523dd2a9cc..563aad1920f7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt @@ -184,9 +184,9 @@ class CommunalViewModelTest : SysuiTestCase() { .isInstanceOf(CommunalContentModel.Smartspace::class.java) assertThat(communalContent?.get(1)).isInstanceOf(CommunalContentModel.Umo::class.java) assertThat(communalContent?.get(2)) - .isInstanceOf(CommunalContentModel.Widget::class.java) + .isInstanceOf(CommunalContentModel.WidgetContent::class.java) assertThat(communalContent?.get(3)) - .isInstanceOf(CommunalContentModel.Widget::class.java) + .isInstanceOf(CommunalContentModel.WidgetContent::class.java) assertThat(communalContent?.get(4)) .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt index 3484025f8d80..cd4db2fbf55f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt @@ -79,34 +79,66 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { } @Test - fun dozeAmountTransitionTest() = runTest { - val dozeAmountSteps by collectValues(underTest.dozeAmountTransition) + fun dozeAmountTransitionTest_AodToFromLockscreen() = + testScope.runTest { + val dozeAmountSteps by collectValues(underTest.dozeAmountTransition) - val steps = mutableListOf<TransitionStep>() + val steps = mutableListOf<TransitionStep>() - steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)) - steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING)) - steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED)) - steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)) - steps.add(TransitionStep(LOCKSCREEN, AOD, 0.8f, RUNNING)) - steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING)) - steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 0.8f, RUNNING)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED)) - steps.forEach { - repository.sendTransitionStep(it) - runCurrent() + steps.forEach { + repository.sendTransitionStep(it) + runCurrent() + } + + assertThat(dozeAmountSteps.subList(0, 3)) + .isEqualTo( + listOf( + steps[0].copy(value = 1f - steps[0].value), + steps[1].copy(value = 1f - steps[1].value), + steps[2].copy(value = 1f - steps[2].value), + ) + ) + assertThat(dozeAmountSteps.subList(3, 7)).isEqualTo(steps.subList(3, 7)) } - assertThat(dozeAmountSteps.subList(0, 3)) - .isEqualTo( - listOf( - steps[0].copy(value = 1f - steps[0].value), - steps[1].copy(value = 1f - steps[1].value), - steps[2].copy(value = 1f - steps[2].value), + @Test + fun dozeAmountTransitionTest_AodToFromGone() = + testScope.runTest { + val dozeAmountSteps by collectValues(underTest.dozeAmountTransition) + + val steps = mutableListOf<TransitionStep>() + + steps.add(TransitionStep(AOD, GONE, 0f, STARTED)) + steps.add(TransitionStep(AOD, GONE, 0.3f, RUNNING)) + steps.add(TransitionStep(AOD, GONE, 1f, FINISHED)) + steps.add(TransitionStep(GONE, AOD, 0f, STARTED)) + steps.add(TransitionStep(GONE, AOD, 0.1f, RUNNING)) + steps.add(TransitionStep(GONE, AOD, 0.3f, RUNNING)) + steps.add(TransitionStep(GONE, AOD, 1f, FINISHED)) + + steps.forEach { + repository.sendTransitionStep(it) + runCurrent() + } + + assertThat(dozeAmountSteps.subList(0, 3)) + .isEqualTo( + listOf( + steps[0].copy(value = 1f - steps[0].value), + steps[1].copy(value = 1f - steps[1].value), + steps[2].copy(value = 1f - steps[2].value), + ) ) - ) - assertThat(dozeAmountSteps.subList(3, 7)).isEqualTo(steps.subList(3, 7)) - } + assertThat(dozeAmountSteps.subList(3, 7)).isEqualTo(steps.subList(3, 7)) + } @Test fun finishedKeyguardStateTests() = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt index 4c972e9195e9..4c972e9195e9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt index db8fbf604430..db8fbf604430 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt index ad2ae8b41af9..ad2ae8b41af9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt index 4a10d80430e9..4a10d80430e9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt index 0b80ff8d6ca4..d4dd2ac78e2a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt @@ -36,7 +36,6 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) -@android.platform.test.annotations.EnabledOnRavenwood class LockscreenContentViewModelTest : SysuiTestCase() { private val kosmos: Kosmos = testKosmos() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt index e139466c8096..bef951554b50 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt @@ -36,10 +36,10 @@ import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.testKosmos import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat -import kotlin.test.Test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.Test import org.junit.runner.RunWith @ExperimentalCoroutinesApi diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModelTest.kt index 28f5eba28763..86b3f33b7555 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModelTest.kt @@ -102,6 +102,24 @@ class LockscreenToDozingTransitionViewModelTest : SysuiTestCase() { values.forEach { assertThat(it).isEqualTo(0f) } } + + @Test + fun lockscreenAlphaFadesOutAndFinishesVisible() = + testScope.runTest { + val alpha by collectValues(underTest.lockscreenAlpha) + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DOZING, + testScope, + ) + + assertThat(alpha[0]).isEqualTo(1f) + // Halfway through, it will have faded out + assertThat(alpha[1]).isEqualTo(0f) + // FINISHED alpha should be visible, to support pulsing + assertThat(alpha[2]).isEqualTo(1f) + } + @Test fun deviceEntryBackgroundViewDisappear() = testScope.runTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt index e7aaddd94695..e7aaddd94695 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt index 7a564aca00bb..43ab93a18118 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt @@ -33,10 +33,10 @@ import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.testKosmos import com.google.common.collect.Range import com.google.common.truth.Truth -import kotlin.test.Test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.Test import org.junit.runner.RunWith @ExperimentalCoroutinesApi diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt index 1912987cc447..1912987cc447 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt index c55c27c3b516..c55c27c3b516 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt index 0796af065790..0796af065790 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index fff0a316cbf4..667f516317be 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -615,6 +615,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { private fun TestScope.emulatePendingTransitionProgress( expectedVisible: Boolean = true, ) { + val isVisible by collectLastValue(sceneContainerViewModel.isVisible) assertWithMessage("The FakeSceneDataSource has to be paused for this to do anything.") .that(fakeSceneDataSource.isPaused) .isTrue() @@ -651,7 +652,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { runCurrent() assertWithMessage("Visibility mismatch after scene transition from $from to $to!") - .that(sceneContainerViewModel.isVisible.value) + .that(isVisible) .isEqualTo(expectedVisible) assertThat(sceneContainerViewModel.currentScene.value).isEqualTo(to) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index dd3eb6845789..db94c39e1cb1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.scene.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -31,6 +33,7 @@ import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runCurrent @@ -275,4 +278,18 @@ class SceneInteractorTest : SysuiTestCase() { underTest.setVisible(true, "reason") assertThat(isVisible).isTrue() } + + @Test + fun isVisible_duringRemoteUserInteraction_forcedVisible() = + testScope.runTest { + underTest.setVisible(false, "reason") + val isVisible by collectLastValue(underTest.isVisible) + assertThat(isVisible).isFalse() + underTest.onRemoteUserInteractionStarted("reason") + assertThat(isVisible).isTrue() + + underTest.onUserInteractionFinished() + + assertThat(isVisible).isFalse() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt index ffbdafe338e7..27ae8b60009c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt @@ -14,10 +14,9 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.scene.ui.viewmodel +import android.view.MotionEvent import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -35,9 +34,9 @@ import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -50,7 +49,7 @@ class SceneContainerViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope by lazy { kosmos.testScope } - private val interactor by lazy { kosmos.sceneInteractor } + private val sceneInteractor by lazy { kosmos.sceneInteractor } private val fakeSceneDataSource = kosmos.fakeSceneDataSource private val sceneContainerConfig = kosmos.sceneContainerConfig private val falsingManager = kosmos.fakeFalsingManager @@ -62,7 +61,7 @@ class SceneContainerViewModelTest : SysuiTestCase() { kosmos.fakeSceneContainerFlags.enabled = true underTest = SceneContainerViewModel( - sceneInteractor = interactor, + sceneInteractor = sceneInteractor, falsingInteractor = kosmos.falsingInteractor, powerInteractor = kosmos.powerInteractor, ) @@ -74,10 +73,10 @@ class SceneContainerViewModelTest : SysuiTestCase() { val isVisible by collectLastValue(underTest.isVisible) assertThat(isVisible).isTrue() - interactor.setVisible(false, "reason") + sceneInteractor.setVisible(false, "reason") assertThat(isVisible).isFalse() - interactor.setVisible(true, "reason") + sceneInteractor.setVisible(true, "reason") assertThat(isVisible).isTrue() } @@ -199,4 +198,20 @@ class SceneContainerViewModelTest : SysuiTestCase() { underTest.onMotionEvent(mock()) assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue() } + + @Test + fun remoteUserInteraction_keepsContainerVisible() = + testScope.runTest { + sceneInteractor.setVisible(false, "reason") + val isVisible by collectLastValue(underTest.isVisible) + assertThat(isVisible).isFalse() + sceneInteractor.onRemoteUserInteractionStarted("reason") + assertThat(isVisible).isTrue() + + underTest.onMotionEvent( + mock { whenever(actionMasked).thenReturn(MotionEvent.ACTION_UP) } + ) + + assertThat(isVisible).isFalse() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index f7756d82a956..7f5a658587f3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -47,8 +47,6 @@ import com.android.systemui.res.R import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.mockLargeScreenHeaderHelper import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor -import com.android.systemui.statusbar.policy.SplitShadeStateController -import com.android.systemui.statusbar.policy.splitShadeStateController import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever @@ -68,7 +66,6 @@ import org.mockito.Mockito.mock @RunWith(AndroidJUnit4::class) class SharedNotificationContainerViewModelTest : SysuiTestCase() { val aodBurnInViewModel = mock(AodBurnInViewModel::class.java) - val splitShadeStateController = mock(SplitShadeStateController::class.java) lateinit var translationYFlow: MutableStateFlow<Float> val kosmos = @@ -81,7 +78,6 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { init { kosmos.aodBurnInViewModel = aodBurnInViewModel - kosmos.splitShadeStateController = splitShadeStateController } val testScope = kosmos.testScope val configurationRepository = kosmos.fakeConfigurationRepository @@ -98,7 +94,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Before fun setUp() { - whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())).thenReturn(false) + overrideResource(R.bool.config_use_split_notification_shade, false) translationYFlow = MutableStateFlow(0f) whenever(aodBurnInViewModel.translationY(any())).thenReturn(translationYFlow) underTest = kosmos.sharedNotificationContainerViewModel @@ -107,8 +103,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun validateMarginStartInSplitShade() = testScope.runTest { - whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) - .thenReturn(true) + overrideResource(R.bool.config_use_split_notification_shade, true) overrideResource(R.dimen.notification_panel_margin_horizontal, 20) val dimens by collectLastValue(underTest.configurationBasedDimensions) @@ -121,8 +116,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun validateMarginStart() = testScope.runTest { - whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) - .thenReturn(false) + overrideResource(R.bool.config_use_split_notification_shade, false) overrideResource(R.dimen.notification_panel_margin_horizontal, 20) val dimens by collectLastValue(underTest.configurationBasedDimensions) @@ -137,8 +131,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { testScope.runTest { mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5) - whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) - .thenReturn(true) + overrideResource(R.bool.config_use_split_notification_shade, true) overrideResource(R.bool.config_use_large_screen_shade_header, true) overrideResource(R.dimen.large_screen_shade_header_height, 10) overrideResource(R.dimen.keyguard_split_shade_top_margin, 50) @@ -156,8 +149,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { testScope.runTest { mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5) - whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) - .thenReturn(true) + overrideResource(R.bool.config_use_split_notification_shade, true) overrideResource(R.bool.config_use_large_screen_shade_header, true) overrideResource(R.dimen.large_screen_shade_header_height, 10) overrideResource(R.dimen.keyguard_split_shade_top_margin, 50) @@ -172,8 +164,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun validatePaddingTop() = testScope.runTest { - whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) - .thenReturn(false) + overrideResource(R.bool.config_use_split_notification_shade, false) overrideResource(R.dimen.large_screen_shade_header_height, 10) overrideResource(R.dimen.keyguard_split_shade_top_margin, 50) @@ -431,8 +422,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { val bounds by collectLastValue(underTest.bounds) // When not in split shade - whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) - .thenReturn(false) + overrideResource(R.bool.config_use_split_notification_shade, false) configurationRepository.onAnyConfigurationChange() runCurrent() @@ -454,8 +444,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { // When in split shade whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5) - whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) - .thenReturn(true) + overrideResource(R.bool.config_use_split_notification_shade, true) overrideResource(R.bool.config_use_large_screen_shade_header, true) overrideResource(R.dimen.large_screen_shade_header_height, 10) overrideResource(R.dimen.keyguard_split_shade_top_margin, 50) @@ -483,8 +472,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { // When in split shade whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5) - whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) - .thenReturn(true) + overrideResource(R.bool.config_use_split_notification_shade, true) overrideResource(R.bool.config_use_large_screen_shade_header, true) overrideResource(R.dimen.large_screen_shade_header_height, 10) overrideResource(R.dimen.keyguard_split_shade_top_margin, 50) @@ -543,8 +531,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { showLockscreen() - whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) - .thenReturn(false) + overrideResource(R.bool.config_use_split_notification_shade, false) configurationRepository.onAnyConfigurationChange() keyguardInteractor.setNotificationContainerBounds( NotificationContainerBounds(top = 1f, bottom = 2f) @@ -567,8 +554,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { showLockscreen() - whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) - .thenReturn(false) + overrideResource(R.bool.config_use_split_notification_shade, false) configurationRepository.onAnyConfigurationChange() keyguardInteractor.setNotificationContainerBounds( NotificationContainerBounds(top = 1f, bottom = 2f) @@ -604,8 +590,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { // Show lockscreen with shade expanded showLockscreenWithShadeExpanded() - whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) - .thenReturn(false) + overrideResource(R.bool.config_use_split_notification_shade, false) configurationRepository.onAnyConfigurationChange() keyguardInteractor.setNotificationContainerBounds( NotificationContainerBounds(top = 1f, bottom = 2f) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java index 6456669c09b6..6390e82321f0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java @@ -71,8 +71,6 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { @Rule public MockitoRule rule = MockitoJUnit.rule(); - private static final String TEST_PACKAGE_NAME = "BaseHeadsUpManagerTest"; - static final int TEST_TOUCH_ACCEPTANCE_TIME = 200; static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000; @@ -80,8 +78,6 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer())); @Mock private AccessibilityManagerWrapper mAccessibilityMgr; - private static final int TEST_UID = 0; - protected static final int TEST_MINIMUM_DISPLAY_TIME = 400; protected static final int TEST_AUTO_DISMISS_TIME = 600; protected static final int TEST_STICKY_AUTO_DISMISS_TIME = 800; diff --git a/packages/SystemUI/res/drawable/battery_unified_attr_charging.xml b/packages/SystemUI/res/drawable/battery_unified_attr_charging.xml new file mode 100644 index 000000000000..8e3b89b9a7eb --- /dev/null +++ b/packages/SystemUI/res/drawable/battery_unified_attr_charging.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="8dp" + android:height="10dp" + android:viewportWidth="16.0" + android:viewportHeight="20.0"> + <path + android:pathData="M4,20L5,13H0L9,0H11L10,8H16L6,20H4Z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SystemUI/res/drawable/battery_unified_attr_defend.xml b/packages/SystemUI/res/drawable/battery_unified_attr_defend.xml new file mode 100644 index 000000000000..e7beee2bc7c0 --- /dev/null +++ b/packages/SystemUI/res/drawable/battery_unified_attr_defend.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="8dp" + android:height="9dp" + android:viewportWidth="8.0" + android:viewportHeight="9.0"> + <path + android:pathData="M3.698,9C2.629,8.765 1.746,8.181 1.048,7.247C0.349,6.306 0,5.266 0,4.126V1.422L3.698,0L7.397,1.422V4.126C7.397,5.266 7.048,6.306 6.349,7.247C5.651,8.181 4.767,8.765 3.698,9ZM3.698,7.846C4.439,7.596 5.052,7.129 5.537,6.445C6.029,5.754 6.274,4.981 6.274,4.126V2.191L3.698,1.197L1.122,2.191V4.126C1.122,4.981 1.365,5.754 1.849,6.445C2.341,7.129 2.957,7.596 3.698,7.846ZM3.698,7.183C3.1,6.99 2.605,6.616 2.213,6.061C1.828,5.505 1.635,4.888 1.635,4.211V2.651L3.698,1.86L5.761,2.651V4.211C5.761,4.888 5.565,5.505 5.173,6.061C4.789,6.616 4.297,6.99 3.698,7.183Z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SystemUI/res/drawable/battery_unified_attr_powersave.xml b/packages/SystemUI/res/drawable/battery_unified_attr_powersave.xml new file mode 100644 index 000000000000..a2c0cbae3e8a --- /dev/null +++ b/packages/SystemUI/res/drawable/battery_unified_attr_powersave.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 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. + --> + +<!-- This drawable is inset for now until the unified battery attrs can get their own paddings --> +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:insetRight="0.5dp" + > + <vector + android:width="8dp" + android:height="8dp" + android:viewportWidth="8.0" + android:viewportHeight="8.0" + > + <path + android:pathData="M3.242,4.758H0V3.257H3.242V0H4.758V3.257H8V4.758H4.758V8.015H3.242V4.758Z" + android:fillColor="#000"/> + </vector> +</inset> diff --git a/packages/SystemUI/res/drawable/battery_unified_frame.xml b/packages/SystemUI/res/drawable/battery_unified_frame.xml new file mode 100644 index 000000000000..016b88b5cca1 --- /dev/null +++ b/packages/SystemUI/res/drawable/battery_unified_frame.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 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. + --> + +<!-- Unified battery frame for BatteryLayersDrawable.kt --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="14dp" + android:viewportWidth="24.0" + android:viewportHeight="14.0"> + <!-- body --> + <path + android:pathData="@string/battery_unified_frame_path_string" + android:strokeColor="#000" + android:strokeWidth="1.5" + /> + <!-- cap --> + <path + android:pathData="M0,4C0,3.448 0.448,3 1,3H1.5V11H1C0.448,11 0,10.552 0,10V4Z" + android:fillColor="#000"/> +</vector> diff --git a/packages/SystemUI/res/drawable/battery_unified_frame_bg.xml b/packages/SystemUI/res/drawable/battery_unified_frame_bg.xml new file mode 100644 index 000000000000..8e04f109fade --- /dev/null +++ b/packages/SystemUI/res/drawable/battery_unified_frame_bg.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<!-- Vector description of the battery gutter: the background of the fill --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="14dp" + android:viewportWidth="24.0" + android:viewportHeight="14.0"> + <path + android:pathData="@string/battery_unified_frame_path_string" + android:fillColor="#fff" /> +</vector> diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml index 1365a11d7a56..22d156da7580 100644 --- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml +++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml @@ -263,8 +263,9 @@ android:layout_gravity="center_vertical|start"> <ImageView android:id="@+id/wifi_connected_icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scaleType="fitCenter" android:layout_gravity="center"/> </FrameLayout> diff --git a/packages/SystemUI/res/layout/internet_list_item.xml b/packages/SystemUI/res/layout/internet_list_item.xml index f6a213662a18..0da0f2b1e9b1 100644 --- a/packages/SystemUI/res/layout/internet_list_item.xml +++ b/packages/SystemUI/res/layout/internet_list_item.xml @@ -35,8 +35,9 @@ android:layout_gravity="center_vertical|start"> <ImageView android:id="@+id/wifi_icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scaleType="fitCenter" android:layout_gravity="center"/> </FrameLayout> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 32b1cadd1c6c..b7eff38aa015 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -164,6 +164,9 @@ so the width of the icon should be 13.0sp * (12.0 / 20.0) --> <dimen name="status_bar_battery_icon_width">7.8sp</dimen> + <dimen name="status_bar_battery_unified_icon_width">24sp</dimen> + <dimen name="status_bar_battery_unified_icon_height">14sp</dimen> + <!-- The battery icon is 13sp tall, but the other system icons are 15sp tall (see @*android:dimen/status_bar_system_icon_size) with some top and bottom padding embedded in the drawables themselves. So, the battery icon may need an extra 1dp of spacing so that its @@ -199,6 +202,7 @@ <!-- Size of the view displaying the mobile signal icon in the status bar. This value should match the core/status_bar_system_icon_size and change to sp unit --> <dimen name="status_bar_mobile_signal_size">15sp</dimen> + <dimen name="status_bar_mobile_signal_size_updated">14sp</dimen> <!-- Size of the view displaying the mobile signal icon in the status bar. This value should match the viewport height of mobile signal drawables such as ic_lte_mobiledata --> <dimen name="status_bar_mobile_type_size">16sp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index ea0e3092a781..346bdfc8b2d8 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -34,6 +34,10 @@ remaining [CHAR LIMIT=none]--> <string name="battery_low_percent_format"><xliff:g id="percentage">%s</xliff:g> remaining</string> + <!-- SVG path description for the battery frame of the unified battery drawable + (BatteryLayersDrawable.kt). Drawn on a 24x14 canvas. Not suitable outside in any other context --> + <string name="battery_unified_frame_path_string" translatable="false">M2.75,3C2.75,1.757 3.757,0.75 5,0.75H20C21.795,0.75 23.25,2.205 23.25,4V10C23.25,11.795 21.795,13.25 20,13.25H5C3.757,13.25 2.75,12.243 2.75,11V3Z </string> + <!-- A message that appears when the battery remaining estimate is low in a dialog. This is appended to the subtitle of the low battery alert. "percentage" is the percentage of battery remaining. "time" is the amount of time remaining before the phone runs out of battery [CHAR LIMIT=none]--> @@ -1114,6 +1118,8 @@ <string name="popup_on_dismiss_cta_tile_text">Long press to customize widgets</string> <!-- Text for the button to configure widgets after long press. [CHAR LIMIT=50] --> <string name="button_to_configure_widgets_text">Customize widgets</string> + <!-- Description for the App icon of disabled widget. [CHAR LIMIT=NONE] --> + <string name="icon_description_for_disabled_widget">App icon for disabled widget</string> <!-- Label for the button which configures widgets [CHAR LIMIT=NONE] --> <string name="edit_widget">Edit widget</string> <!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] --> diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 05e07a788892..169a4e0f3501 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -413,7 +413,6 @@ constructor( listenForDozing(this) if (migrateClocksToBlueprint()) { listenForDozeAmountTransition(this) - listenForAnyStateToAodTransition(this) } else { listenForDozeAmount(this) } @@ -522,19 +521,6 @@ constructor( } } - /** - * When keyguard is displayed again after being gone, the clock must be reset to full dozing. - */ - @VisibleForTesting - internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job { - return scope.launch { - keyguardTransitionInteractor.transitionStepsToState(AOD) - .filter { it.transitionState == TransitionState.STARTED } - .filter { it.from != LOCKSCREEN } - .collect { handleDoze(1f) } - } - } - @VisibleForTesting internal fun listenForDozing(scope: CoroutineScope): Job { return scope.launch { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 38c2829e27f6..b7667a8669f4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -34,6 +34,7 @@ import static android.hardware.biometrics.BiometricSourceType.FACE; import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; import static android.os.BatteryManager.CHARGING_POLICY_DEFAULT; +import static android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; @@ -437,7 +438,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } }; - private final OnSubscriptionsChangedListener mSubscriptionListener = + @VisibleForTesting + final OnSubscriptionsChangedListener mSubscriptionListener = new OnSubscriptionsChangedListener() { @Override public void onSubscriptionsChanged() { @@ -586,16 +588,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private void handleSimSubscriptionInfoChanged() { Assert.isMainThread(); mLogger.v("onSubscriptionInfoChanged()"); - List<SubscriptionInfo> sil = mSubscriptionManager - .getCompleteActiveSubscriptionInfoList(); - if (sil != null) { - for (SubscriptionInfo subInfo : sil) { + List<SubscriptionInfo> subscriptionInfos = getSubscriptionInfo(true /* forceReload */); + if (!subscriptionInfos.isEmpty()) { + for (SubscriptionInfo subInfo : subscriptionInfos) { mLogger.logSubInfo(subInfo); } } else { mLogger.v("onSubscriptionInfoChanged: list is null"); } - List<SubscriptionInfo> subscriptionInfos = getSubscriptionInfo(true /* forceReload */); // Hack level over 9000: Because the subscription id is not yet valid when we see the // first update in handleSimStateChange, we need to force refresh all SIM states @@ -658,18 +658,18 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab /** * @return List of SubscriptionInfo records, maybe empty but never null. + * + * Note that this method will filter out any subscription which is PROFILE_CLASS_PROVISIONING */ public List<SubscriptionInfo> getSubscriptionInfo(boolean forceReload) { List<SubscriptionInfo> sil = mSubscriptionInfo; if (sil == null || forceReload) { - sil = mSubscriptionManager.getCompleteActiveSubscriptionInfoList(); - } - if (sil == null) { - // getCompleteActiveSubscriptionInfoList was null callers expect an empty list. - mSubscriptionInfo = new ArrayList<>(); - } else { - mSubscriptionInfo = sil; + mSubscriptionInfo = mSubscriptionManager.getCompleteActiveSubscriptionInfoList() + .stream() + .filter(subInfo -> subInfo.getProfileClass() != PROFILE_CLASS_PROVISIONING) + .toList(); } + return new ArrayList<>(mSubscriptionInfo); } diff --git a/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt b/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt index 22d0bc9e4b5c..af810ea1bc2e 100644 --- a/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt @@ -20,6 +20,7 @@ import android.animation.ArgbEvaluator import android.content.Context import android.view.ContextThemeWrapper import com.android.settingslib.Utils +import com.android.settingslib.flags.Flags.newStatusBarIcons import com.android.systemui.res.R /** @@ -53,14 +54,26 @@ class DualToneHandler(context: Context) { Utils.getThemeAttr(context, R.attr.darkIconTheme)) val dualToneLightTheme = ContextThemeWrapper(context, Utils.getThemeAttr(context, R.attr.lightIconTheme)) - darkColor = Color( - Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.singleToneColor), + if (newStatusBarIcons()) { + darkColor = Color( + android.graphics.Color.BLACK, Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.iconBackgroundColor), Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.fillColor)) - lightColor = Color( - Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.singleToneColor), + + lightColor = Color( + android.graphics.Color.WHITE, Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.iconBackgroundColor), Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.fillColor)) + } else { + darkColor = Color( + Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.singleToneColor), + Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.iconBackgroundColor), + Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.fillColor)) + lightColor = Color( + Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.singleToneColor), + Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.iconBackgroundColor), + Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.fillColor)) + } } private fun getColorForDarkIntensity(darkIntensity: Float, lightColor: Int, darkColor: Int) = diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java index b1a153aa86aa..31698a35c811 100644 --- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java @@ -17,6 +17,7 @@ package com.android.systemui.battery; import static android.provider.Settings.System.SHOW_BATTERY_PERCENT; +import static com.android.settingslib.flags.Flags.newStatusBarIcons; import static com.android.systemui.DejankUtils.whitelistIpcs; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -25,6 +26,7 @@ import android.animation.LayoutTransition; import android.animation.ObjectAnimator; import android.annotation.IntDef; import android.annotation.IntRange; +import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -47,6 +49,9 @@ import androidx.annotation.VisibleForTesting; import com.android.app.animation.Interpolators; import com.android.systemui.DualToneHandler; +import com.android.systemui.battery.unified.BatteryColors; +import com.android.systemui.battery.unified.BatteryDrawableState; +import com.android.systemui.battery.unified.BatteryLayersDrawable; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; import com.android.systemui.res.R; @@ -78,6 +83,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { private boolean mShowPercentAvailable; private String mEstimateText = null; private boolean mPluggedIn; + private boolean mPowerSaveEnabled; private boolean mIsBatteryDefender; private boolean mIsIncompatibleCharging; private boolean mDisplayShieldEnabled; @@ -91,6 +97,12 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { private BatteryEstimateFetcher mBatteryEstimateFetcher; + // for Flags.newStatusBarIcons. The unified battery icon can show percent inside + @Nullable private BatteryLayersDrawable mUnifiedBattery; + private BatteryColors mUnifiedBatteryColors = BatteryColors.LIGHT_THEME_COLORS; + private BatteryDrawableState mUnifiedBatteryState = + BatteryDrawableState.Companion.getDefaultInitialState(); + public BatteryMeterView(Context context, AttributeSet attrs) { this(context, attrs, 0); } @@ -106,6 +118,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor, context.getColor(com.android.settingslib.R.color.meter_background_color)); mPercentageStyleId = atts.getResourceId(R.styleable.BatteryMeterView_textAppearance, 0); + mDrawable = new AccessorizedBatteryDrawable(context, frameColor); atts.recycle(); @@ -115,13 +128,26 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { setupLayoutTransition(); mBatteryIconView = new ImageView(context); - mBatteryIconView.setImageDrawable(mDrawable); - final MarginLayoutParams mlp = new MarginLayoutParams( - getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_width), - getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_height)); - mlp.setMargins(0, 0, 0, - getResources().getDimensionPixelOffset(R.dimen.battery_margin_bottom)); - addView(mBatteryIconView, mlp); + if (newStatusBarIcons()) { + mUnifiedBattery = BatteryLayersDrawable.Companion + .newBatteryDrawable(context, mUnifiedBatteryState); + mBatteryIconView.setImageDrawable(mUnifiedBattery); + + final MarginLayoutParams mlp = new MarginLayoutParams( + getResources().getDimensionPixelSize( + R.dimen.status_bar_battery_unified_icon_width), + getResources().getDimensionPixelSize( + R.dimen.status_bar_battery_unified_icon_height)); + addView(mBatteryIconView, mlp); + } else { + mBatteryIconView.setImageDrawable(mDrawable); + final MarginLayoutParams mlp = new MarginLayoutParams( + getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_width), + getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_height)); + mlp.setMargins(0, 0, 0, + getResources().getDimensionPixelOffset(R.dimen.battery_margin_bottom)); + addView(mBatteryIconView, mlp); + } updateShowPercent(); mDualToneHandler = new DualToneHandler(context); @@ -132,6 +158,14 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { setClipToPadding(false); } + + private void setBatteryDrawableState(BatteryDrawableState newState) { + if (!newStatusBarIcons()) return; + + mUnifiedBatteryState = newState; + mUnifiedBattery.setBatteryState(mUnifiedBatteryState); + } + private void setupLayoutTransition() { LayoutTransition transition = new LayoutTransition(); transition.setDuration(200); @@ -200,25 +234,94 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { * @param pluggedIn whether the device is plugged in or not */ public void onBatteryLevelChanged(@IntRange(from = 0, to = 100) int level, boolean pluggedIn) { + boolean wasCharging = isCharging(); mPluggedIn = pluggedIn; mLevel = level; - mDrawable.setCharging(isCharging()); + boolean isCharging = isCharging(); + mDrawable.setCharging(isCharging); mDrawable.setBatteryLevel(level); updatePercentText(); + + if (newStatusBarIcons()) { + Drawable attr = mUnifiedBatteryState.getAttribution(); + if (isCharging != wasCharging) { + attr = getBatteryAttribution(isCharging); + } + + BatteryDrawableState newState = + new BatteryDrawableState( + level, + mUnifiedBatteryState.getShowPercent(), + level <= 20, + attr + ); + + setBatteryDrawableState(newState); + } + } + + // Potentially reloads any attribution. Should not be called if the state hasn't changed + private Drawable getBatteryAttribution(boolean isCharging) { + if (!newStatusBarIcons()) return null; + + int resId = 0; + if (mPowerSaveEnabled) { + resId = R.drawable.battery_unified_attr_powersave; + } else if (mIsBatteryDefender && mDisplayShieldEnabled) { + resId = R.drawable.battery_unified_attr_defend; + } else if (isCharging) { + resId = R.drawable.battery_unified_attr_charging; + } + + Drawable attr = null; + if (resId > 0) { + attr = mContext.getDrawable(resId); + } + + return attr; } void onPowerSaveChanged(boolean isPowerSave) { - mDrawable.setPowerSaveEnabled(isPowerSave); + if (isPowerSave == mPowerSaveEnabled) { + return; + } + mPowerSaveEnabled = isPowerSave; + if (!newStatusBarIcons()) { + mDrawable.setPowerSaveEnabled(isPowerSave); + } else { + setBatteryDrawableState( + new BatteryDrawableState( + mUnifiedBatteryState.getLevel(), + mUnifiedBatteryState.getShowPercent(), + mUnifiedBatteryState.getShowErrorState(), + getBatteryAttribution(isCharging()) + ) + ); + } } void onIsBatteryDefenderChanged(boolean isBatteryDefender) { boolean valueChanged = mIsBatteryDefender != isBatteryDefender; mIsBatteryDefender = isBatteryDefender; - if (valueChanged) { - updateContentDescription(); + + if (!valueChanged) { + return; + } + + updateContentDescription(); + if (!newStatusBarIcons()) { // The battery drawable is a different size depending on whether it's currently // overheated or not, so we need to re-scale the view when overheated changes. scaleBatteryMeterViews(); + } else { + setBatteryDrawableState( + new BatteryDrawableState( + mUnifiedBatteryState.getLevel(), + mUnifiedBatteryState.getShowPercent(), + mUnifiedBatteryState.getShowErrorState(), + getBatteryAttribution(isCharging()) + ) + ); } } @@ -226,7 +329,18 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { boolean valueChanged = mIsIncompatibleCharging != isIncompatibleCharging; mIsIncompatibleCharging = isIncompatibleCharging; if (valueChanged) { - mDrawable.setCharging(isCharging()); + if (newStatusBarIcons()) { + setBatteryDrawableState( + new BatteryDrawableState( + mUnifiedBatteryState.getLevel(), + mUnifiedBatteryState.getShowPercent(), + mUnifiedBatteryState.getShowErrorState(), + getBatteryAttribution(isCharging()) + ) + ); + } else { + mDrawable.setCharging(isCharging()); + } updateContentDescription(); } } @@ -260,6 +374,38 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { } void updatePercentText() { + if (!newStatusBarIcons()) { + updatePercentTextLegacy(); + return; + } + + // The unified battery can show the percent inside, so we only need to handle + // the estimated time remaining case + if (mShowPercentMode == MODE_ESTIMATE + && mBatteryEstimateFetcher != null + && !isCharging() + ) { + mBatteryEstimateFetcher.fetchBatteryTimeRemainingEstimate( + (String estimate) -> { + if (mBatteryPercentView == null) { + mBatteryPercentView = loadPercentView(); + } + if (estimate != null && mShowPercentMode == MODE_ESTIMATE) { + mEstimateText = estimate; + mBatteryPercentView.setText(estimate); + updateContentDescription(); + } else { + mEstimateText = null; + mBatteryPercentView.setText(null); + updateContentDescription(); + } + }); + } else { + updateContentDescription(); + } + } + + void updatePercentTextLegacy() { if (mBatteryStateUnknown) { return; } @@ -334,6 +480,45 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { } void updateShowPercent() { + if (!newStatusBarIcons()) { + updateShowPercentLegacy(); + return; + } + + if (mUnifiedBattery == null) { + return; + } + + // TODO(b/140051051) + final boolean systemSetting = 0 != whitelistIpcs(() -> Settings.System + .getIntForUser(getContext().getContentResolver(), + SHOW_BATTERY_PERCENT, getContext().getResources().getBoolean( + com.android.internal.R.bool.config_defaultBatteryPercentageSetting) + ? 1 : 0, UserHandle.USER_CURRENT)); + + boolean shouldShow = + (mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF) + || mShowPercentMode == MODE_ON; + shouldShow = shouldShow && !mBatteryStateUnknown; + + setBatteryDrawableState( + new BatteryDrawableState( + mUnifiedBatteryState.getLevel(), + shouldShow, + mUnifiedBatteryState.getShowErrorState(), + mUnifiedBatteryState.getAttribution() + ) + ); + + // The legacy impl used the percent view for the estimate and the percent text. The modern + // version only uses it for estimate. It can be safely removed here + if (mShowPercentMode != MODE_ESTIMATE) { + removeView(mBatteryPercentView); + mBatteryPercentView = null; + } + } + + private void updateShowPercentLegacy() { final boolean showing = mBatteryPercentView != null; // TODO(b/140051051) final boolean systemSetting = 0 != whitelistIpcs(() -> Settings.System @@ -395,10 +580,39 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { updateShowPercent(); } + void scaleBatteryMeterViews() { + if (!newStatusBarIcons()) { + scaleBatteryMeterViewsLegacy(); + return; + } + + // For simplicity's sake, copy the general pattern in the legacy method and use the new + // resources, excluding what we don't need + Resources res = getContext().getResources(); + TypedValue typedValue = new TypedValue(); + + res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true); + float iconScaleFactor = typedValue.getFloat(); + + float mainBatteryHeight = + res.getDimensionPixelSize( + R.dimen.status_bar_battery_unified_icon_height) * iconScaleFactor; + float mainBatteryWidth = + res.getDimensionPixelSize( + R.dimen.status_bar_battery_unified_icon_width) * iconScaleFactor; + + LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams( + Math.round(mainBatteryWidth), + Math.round(mainBatteryHeight)); + + mBatteryIconView.setLayoutParams(scaledLayoutParams); + mBatteryIconView.invalidateDrawable(mUnifiedBattery); + } + /** * Looks up the scale factor for status bar icons and scales the battery view by that amount. */ - void scaleBatteryMeterViews() { + void scaleBatteryMeterViewsLegacy() { Resources res = getContext().getResources(); TypedValue typedValue = new TypedValue(); @@ -445,6 +659,32 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { @Override public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) { if (mIsStaticColor) return; + + if (!newStatusBarIcons()) { + onDarkChangedLegacy(areas, darkIntensity, tint); + return; + } + + if (mUnifiedBattery == null) { + return; + } + + if (DarkIconDispatcher.isInAreas(areas, this)) { + if (darkIntensity < 0.5) { + mUnifiedBatteryColors = BatteryColors.DARK_THEME_COLORS; + } else { + mUnifiedBatteryColors = BatteryColors.LIGHT_THEME_COLORS; + } + + mUnifiedBattery.setColors(mUnifiedBatteryColors); + } else { + // Same behavior as the legacy code when not isInArea + mUnifiedBatteryColors = BatteryColors.DARK_THEME_COLORS; + mUnifiedBattery.setColors(mUnifiedBatteryColors); + } + } + + private void onDarkChangedLegacy(ArrayList<Rect> areas, float darkIntensity, int tint) { float intensity = DarkIconDispatcher.isInAreas(areas, this) ? darkIntensity : 0; int nonAdaptedSingleToneColor = mDualToneHandler.getSingleColor(intensity); int nonAdaptedForegroundColor = mDualToneHandler.getFillColor(intensity); @@ -478,7 +718,16 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { } } - private boolean isCharging() { + /** For newStatusBarIcons(), we use a BatteryColors object to declare the theme */ + public void setUnifiedBatteryColors(BatteryColors colors) { + if (!newStatusBarIcons()) return; + + mUnifiedBatteryColors = colors; + mUnifiedBattery.setColors(mUnifiedBatteryColors); + } + + @VisibleForTesting + boolean isCharging() { return mPluggedIn && !mIsIncompatibleCharging; } @@ -505,6 +754,16 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { return mBatteryPercentView.getText(); } + @VisibleForTesting + TextView getBatteryPercentView() { + return mBatteryPercentView; + } + + @VisibleForTesting + BatteryDrawableState getUnifiedBatteryState() { + return mUnifiedBatteryState; + } + /** An interface that will fetch the estimated time remaining for the user's battery. */ public interface BatteryEstimateFetcher { void fetchBatteryTimeRemainingEstimate( diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt new file mode 100644 index 000000000000..1b8495ace243 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2024 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.battery.unified + +import android.graphics.Canvas +import android.graphics.ColorFilter +import android.graphics.PixelFormat +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.graphics.drawable.DrawableWrapper +import android.view.Gravity +import kotlin.math.min +import kotlin.math.roundToInt + +/** + * A battery attribution is defined as a drawable that can display either alongside the percent text + * or solely in the center of the battery frame. + * + * Attributions are given an explicit canvas of 18x8, or 6x6 depending on the display mode (centered + * or right-aligned). The size is configured in [BatteryLayersDrawable] by changing this drawable + * wrapper's bounds, and optionally setting the [gravity] + */ +@Suppress("RtlHardcoded") +class BatteryAttributionDrawable(dr: Drawable?) : DrawableWrapper(dr) { + /** One of [CENTER, LEFT]. Note that RTL is handled in the parent */ + var gravity = Gravity.CENTER + set(value) { + field = value + updateBoundsInner() + } + + // Must be called if bounds change, gravity changes, or the wrapped drawable changes + private fun updateBoundsInner() { + val dr = drawable ?: return + + val hScale = bounds.width().toFloat() / dr.intrinsicWidth.toFloat() + val vScale = bounds.height().toFloat() / dr.intrinsicHeight.toFloat() + val scale = min(hScale, vScale) + + val dw = scale * dr.intrinsicWidth + val dh = scale * dr.intrinsicHeight + + if (gravity == Gravity.CENTER) { + val padLeft = (bounds.width() - dw) / 2 + val padTop = (bounds.height() - dh) / 2 + dr.setBounds( + (bounds.left + padLeft).roundToInt(), + (bounds.top + padTop).roundToInt(), + (bounds.left + padLeft + dw).roundToInt(), + (bounds.top + padTop + dh).roundToInt() + ) + } else if (gravity == Gravity.LEFT) { + dr.setBounds( + bounds.left, + bounds.top, + (bounds.left + dw).roundToInt(), + (bounds.top + dh).roundToInt() + ) + } + } + + override fun setDrawable(dr: Drawable?) { + super.setDrawable(dr) + updateBoundsInner() + } + + override fun onBoundsChange(bounds: Rect) { + updateBoundsInner() + } + + /** + * DrawableWrapper allows for a null constructor, but this method assumes that the drawable is + * non-null. It is called by LayerDrawable on init, so we have to handle null here specifically + */ + override fun getChangingConfigurations(): Int = drawable?.changingConfigurations ?: 0 + + override fun draw(canvas: Canvas) { + drawable?.draw(canvas) + } + + // Deprecated, but needed for Drawable implementation + override fun getOpacity() = PixelFormat.OPAQUE + + // We don't use this + override fun setAlpha(alpha: Int) {} + + override fun setColorFilter(colorFilter: ColorFilter?) {} +} diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt new file mode 100644 index 000000000000..b5a93b6635c6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2024 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.battery.unified + +import android.graphics.Color +import android.graphics.drawable.Drawable + +/** + * Encapsulates all drawing information needed by BatteryMeterDrawable to render properly. Rendered + * state will be equivalent to the most recent state passed in. + */ +data class BatteryDrawableState( + /** [0-100] description of the battery level */ + val level: Int, + /** Whether or not to render the percent as a foreground text layer */ + val showPercent: Boolean, + /** + * In an error state, the drawable will use the error colors and removes the third layer. If + * [showPercent] is false, then the fill will be rendered in the foreground error color. Else + * the fill is not rendered. + */ + val showErrorState: Boolean, + + /** + * An attribution is a drawable that shows either alongside the percent, or centered in the + * foreground of the overall drawable. + * + * When space sharing with the percent text, the default rect is 6x6, positioned directly next + * to the percent and left-aligned. + * + * When the attribution is the only foreground layer, then we use a 16x8 canvas and center this + * drawable. + * + * In both cases, we use a FIT_CENTER style scaling. Note that for now the attributions will + * have to configure their own padding inside of their vector definitions. Future versions + * should abstract the side- and center- canvases and allow attributions to be defined with + * separate designs for each case. + */ + val attribution: Drawable? +) { + fun hasForegroundContent() = showPercent || attribution != null + + companion object { + val DefaultInitialState = + BatteryDrawableState( + level = 50, + showPercent = false, + showErrorState = false, + attribution = null, + ) + } +} + +sealed interface BatteryColors { + /** The color for the frame and any foreground attributions for the battery */ + val fg: Int + /** + * Default color for the frame background. Configured to be a transparent white or black that + * matches the current mode (white for light theme, black for dark theme) and provides extra + * contrast for the drawable + */ + val bg: Int + + /** Color for the level fill when there is an attribution on top */ + val fill: Int + /** + * When there is no attribution, [fillOnlyColor] describes an opaque color with more contrast + */ + val fillOnly: Int + + /** Error colors are used for low battery states typically */ + val errorForeground: Int + val errorBackground: Int + + /** Currently unused */ + val warnBackground: Int + + /** Color scheme appropriate for light mode (dark icons) */ + data object LightThemeColors : BatteryColors { + override val fg = Color.BLACK + // 22% alpha white + override val bg: Int = Color.valueOf(1f, 1f, 1f, 0.22f).toArgb() + + // 18% alpha black + override val fill = Color.valueOf(0f, 0f, 0f, 0.18f).toArgb() + // GM Gray 500 + override val fillOnly = Color.parseColor("#9AA0A6") + + // GM Red 600 + override val errorForeground = Color.parseColor("#D93025") + // GM Red 100 + override val errorBackground = Color.parseColor("#FAD2CF") + + // GM Yellow 500 + override val warnBackground = Color.parseColor("#FBBC04") + } + + /** Color scheme appropriate for dark mode (light icons) */ + data object DarkThemeColors : BatteryColors { + override val fg = Color.WHITE + // 18% alpha black + override val bg: Int = Color.valueOf(0f, 0f, 0f, 0.18f).toArgb() + + // 22% alpha white + override val fill = Color.valueOf(1f, 1f, 1f, 0.22f).toArgb() + // GM Gray 600 + override val fillOnly = Color.parseColor("#80868B") + + // GM Red 600 + override val errorForeground = Color.parseColor("#D93025") + // GM Red 200 + override val errorBackground = Color.parseColor("#F6AEA9") + // GM Yellow + override val warnBackground = Color.parseColor("#FBBC04") + } + + companion object { + /** For use from java */ + @JvmField val LIGHT_THEME_COLORS = LightThemeColors + + @JvmField val DARK_THEME_COLORS = DarkThemeColors + } +} diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt new file mode 100644 index 000000000000..6d3206767d2b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2024 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.battery.unified + +import android.graphics.BlendMode +import android.graphics.Canvas +import android.graphics.ColorFilter +import android.graphics.Matrix +import android.graphics.Paint +import android.graphics.Path +import android.graphics.PixelFormat +import android.graphics.Rect +import android.graphics.RectF +import android.graphics.drawable.Drawable +import com.android.systemui.battery.unified.BatteryLayersDrawable.Companion.Metrics +import kotlin.math.floor +import kotlin.math.roundToInt + +/** + * Draws a right-to-left fill inside of the given [framePath]. This fill is designed to exactly fill + * the usable space inside of [framePath], given that the stroke width of the path is 1.5, and we + * want an extra 0.25 (canvas units) of a gap between the fill and the stroke + */ +class BatteryFillDrawable(private val framePath: Path) : Drawable() { + private var hScale = 1f + private val scaleMatrix = Matrix().also { it.setScale(1f, 1f) } + private val scaledPath = Path() + private val scaledFillRect = RectF() + private var scaledLeftOffset = 0f + private var scaledRightInset = 0f + + // Drawable.level cannot be overloaded + var batteryLevel = 0 + set(value) { + field = value + invalidateSelf() + } + + var fillColor: Int = 0 + set(value) { + field = value + fillPaint.color = value + invalidateSelf() + } + + private val clearPaint = + Paint(Paint.ANTI_ALIAS_FLAG).also { p -> + p.style = Paint.Style.STROKE + p.strokeWidth = 5f + p.blendMode = BlendMode.CLEAR + } + + private val fillPaint = + Paint(Paint.ANTI_ALIAS_FLAG).also { p -> + p.style = Paint.Style.FILL + p.color = fillColor + } + + override fun onBoundsChange(bounds: Rect) { + super.onBoundsChange(bounds) + + hScale = bounds.right / Metrics.ViewportWidth + + if (bounds.isEmpty) { + scaleMatrix.setScale(1f, 1f) + } else { + scaleMatrix.setScale( + (bounds.right / Metrics.ViewportWidth), + (bounds.bottom / Metrics.ViewportHeight) + ) + } + + updateScale() + } + + private fun updateScale() { + framePath.transform(/* matrix = */ scaleMatrix, /* dst = */ scaledPath) + scaleMatrix.mapRect(/* dst = */ scaledFillRect, /* src = */ FillRect) + + scaledLeftOffset = LeftFillOffset * hScale + scaledRightInset = RightFillInset * hScale + } + + override fun draw(canvas: Canvas) { + if (batteryLevel == 0) { + return + } + + // saveLayer is needed here so we don't clip the other layers of our drawable + canvas.saveLayer(null, null) + + // We need to use 3 draw commands: + // 1. Clip to the current level + // 2. Clip anything outside of the path + // 3. render the fill as a rect the correct size to fit the inner space + // 4. Clip out the padding between the frame and the fill + + val fillLeft: Int = + if (batteryLevel == 100) { + 0 + } else { + val fillFraction = batteryLevel / 100f + floor(scaledFillRect.width() * (1 - fillFraction)).roundToInt() + } + + // Clip to the fill level + canvas.clipOutRect( + scaledLeftOffset, + bounds.top.toFloat(), + scaledLeftOffset + fillLeft, + bounds.height().toFloat() + ) + // Clip everything outside of the path + canvas.clipPath(scaledPath) + + // Draw the fill + canvas.drawRect(scaledFillRect, fillPaint) + + // Clear around the fill + canvas.drawPath(scaledPath, clearPaint) + + // Finally, restore the layer + canvas.restore() + } + + override fun setColorFilter(colorFilter: ColorFilter?) { + clearPaint.setColorFilter(colorFilter) + fillPaint.setColorFilter(colorFilter) + } + + // unused + override fun getOpacity(): Int = PixelFormat.OPAQUE + + // unused + override fun setAlpha(alpha: Int) {} + + companion object { + // 3.75f = + // 2.75 (left-most edge of the frame path) + // + 0.75 (1/2 of the stroke width) + // + 0.25 (padding between stroke and fill edge) + private const val LeftFillOffset = 3.75f + + // 1.75, calculated the same way, but from the right edge (without the battery cap), which + // consumes 2 units of width. + private const val RightFillInset = 1.75f + + /** Scale this to the viewport so we fill correctly! */ + private val FillRect = + RectF( + LeftFillOffset, + 0f, + Metrics.ViewportWidth - RightFillInset, + Metrics.ViewportHeight + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt new file mode 100644 index 000000000000..199dd1f18a42 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2024 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.battery.unified + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Matrix +import android.graphics.Rect +import android.graphics.RectF +import android.graphics.Typeface +import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable +import android.util.PathParser +import android.view.Gravity +import com.android.systemui.res.R +import kotlin.math.roundToInt + +/** + * Custom [Drawable] that manages a list of other drawables which, together, achieve an appropriate + * view for [BatteryDrawableState]. + * + * The main elements managed by this drawable are: + * + * 1. A battery frame background, which may show a solid fill color + * 2. The battery frame itself + * 3. A custom [BatteryFillDrawable], which renders a fill level, appropriately scale and + * clipped to the battery percent + * 4. Percent text + * 5. An attribution + * + * Layers (1) and (2) are loaded directly from xml, as they are static assets. Layer (3) contains a + * custom [Drawable.draw] implementation and uses the same path as the battery shape to achieve an + * appropriate fill shape. + * + * The text and attribution layers have the following behaviors: + * + * - When text-only or attribute-only, the foreground layer is centered and the maximum size + * - When sharing space between the attribute and the text: + * - The internal space is divided into 12x10 and 6x6 rectangles + * - The attribution is aligned left + * - The percent text is scaled based on the number of characters (1,2, or 3) in the string + * + * When [BatteryDrawableState.showErrorState] is true, we will only show either the percent text OR + * the battery fill, in order to maximize contrast when using the error colors. + */ +@Suppress("RtlHardcoded") +class BatteryLayersDrawable( + private val frameBg: Drawable, + private val frame: Drawable, + private val fill: BatteryFillDrawable, + private val textOnly: BatteryPercentTextOnlyDrawable, + private val spaceSharingText: BatterySpaceSharingPercentTextDrawable, + private val attribution: BatteryAttributionDrawable, + batteryState: BatteryDrawableState, +) : LayerDrawable(arrayOf(frameBg, frame, fill, textOnly, spaceSharingText, attribution)) { + + private val scaleMatrix = Matrix().also { it.setScale(1f, 1f) } + private val scaledAttrFullCanvas = RectF(Metrics.AttrFullCanvas) + private val scaledAttrRightCanvas = RectF(Metrics.AttrRightCanvas) + + var batteryState = batteryState + set(value) { + if (field != value) { + // Update before we set the backing field so we can diff + handleUpdateState(field, value) + field = value + invalidateSelf() + } + } + + var colors: BatteryColors = BatteryColors.LightThemeColors + set(value) { + field = value + updateColors(batteryState.showErrorState, value) + } + + private fun handleUpdateState(old: BatteryDrawableState, new: BatteryDrawableState) { + if (new.showErrorState != old.showErrorState) { + updateColors(new.showErrorState, colors) + } + + if (new.level != old.level) { + fill.batteryLevel = new.level + textOnly.batteryLevel = new.level + spaceSharingText.batteryLevel = new.level + } + + if (new.attribution != null && new.attribution != attribution.drawable) { + attribution.drawable = new.attribution + updateColors(new.showErrorState, colors) + } + + if (new.hasForegroundContent() != old.hasForegroundContent()) { + setFillColor(new.hasForegroundContent(), new.showErrorState, colors) + } + } + + /** In error states, we don't draw fill unless there is no foreground content (e.g., percent) */ + private fun updateColors(showErrorState: Boolean, colorInfo: BatteryColors) { + frameBg.setTint(if (showErrorState) colorInfo.errorBackground else colorInfo.bg) + frame.setTint(colorInfo.fg) + attribution.setTint(if (showErrorState) colorInfo.errorForeground else colorInfo.fg) + textOnly.setTint(if (showErrorState) colorInfo.errorForeground else colorInfo.fg) + spaceSharingText.setTint(if (showErrorState) colorInfo.errorForeground else colorInfo.fg) + setFillColor(batteryState.hasForegroundContent(), showErrorState, colorInfo) + } + + /** + * If there is a foreground layer, then we draw the fill with the low opacity + * [BatteryColors.fill] color. Otherwise, if there is no other foreground layer, we will use + * either the error or fillOnly colors for more contrast + */ + private fun setFillColor( + hasFg: Boolean, + error: Boolean, + colorInfo: BatteryColors, + ) { + if (hasFg) { + fill.fillColor = colorInfo.fill + } else { + fill.fillColor = if (error) colorInfo.errorForeground else colorInfo.fillOnly + } + } + + override fun onBoundsChange(bounds: Rect) { + super.onBoundsChange(bounds) + + scaleMatrix.setScale( + bounds.width() / Metrics.ViewportWidth, + bounds.height() / Metrics.ViewportHeight + ) + + // Scale the attribution bounds + scaleMatrix.mapRect(scaledAttrFullCanvas, Metrics.AttrFullCanvas) + scaleMatrix.mapRect(scaledAttrRightCanvas, Metrics.AttrRightCanvas) + } + + override fun draw(canvas: Canvas) { + // 1. Draw the frame bg + frameBg.draw(canvas) + // 2. Then the frame itself + frame.draw(canvas) + + // 3. Fill it the appropriate amount if non-error state or error + no attribute + if (!batteryState.showErrorState || !batteryState.hasForegroundContent()) { + fill.draw(canvas) + } + // 4. Decide what goes inside + if (batteryState.showPercent && batteryState.attribution != null) { + // 4a. percent & attribution. Implies space-sharing + + // Configure the attribute to draw in a smaller bounding box and align left + attribution.gravity = Gravity.LEFT + attribution.setBounds( + scaledAttrRightCanvas.left.roundToInt(), + scaledAttrRightCanvas.top.roundToInt(), + scaledAttrRightCanvas.right.roundToInt(), + scaledAttrRightCanvas.bottom.roundToInt(), + ) + attribution.draw(canvas) + + spaceSharingText.draw(canvas) + } else if (batteryState.showPercent) { + // 4b. Percent only + textOnly.draw(canvas) + } else if (batteryState.attribution != null) { + // 4c. Attribution only + attribution.gravity = Gravity.CENTER + attribution.setBounds( + scaledAttrFullCanvas.left.roundToInt(), + scaledAttrFullCanvas.top.roundToInt(), + scaledAttrFullCanvas.right.roundToInt(), + scaledAttrFullCanvas.bottom.roundToInt(), + ) + attribution.draw(canvas) + } + } + + /** + * This drawable relies on [BatteryColors] to encode all alpha in their values, so we ignore + * externally-set alpha + */ + override fun setAlpha(alpha: Int) {} + + interface M { + val ViewportWidth: Float + val ViewportHeight: Float + + // Bounds, oriented in the above viewport, where we will fit-center and center-align + // an attribution that is the sole foreground element + val AttrFullCanvas: RectF + // Bounds, oriented in the above viewport, where we will fit-center and left-align + // an attribution that is sharing space with the percent text of the drawable + val AttrRightCanvas: RectF + } + + companion object { + private val PercentFont = Typeface.create("google-sans", Typeface.BOLD) + + /** + * Think of this like the `android:<attr>` values in a drawable.xml file. [Metrics] defines + * relevant canvas and size information for us to layout this cluster of drawables + */ + val Metrics = + object : M { + override val ViewportWidth: Float = 24f + override val ViewportHeight: Float = 14f + + /** + * Bounds, oriented in the above viewport, where we will fit-center and center-align + * an attribution that is the sole foreground element + * + * 18x8 point size + */ + override val AttrFullCanvas: RectF = RectF(4f, 3f, 22f, 11f) + /** + * Bounds, oriented in the above viewport, where we will fit-center and left-align + * an attribution that is sharing space with the percent text of the drawable + * + * 6x6 point size + */ + override val AttrRightCanvas: RectF = RectF(16f, 4f, 22f, 10f) + } + + /** + * Create all of the layers needed by [BatteryLayersDrawable]. This class relies on the + * following resources to exist in order to properly render: + * - R.drawable.battery_unified_frame_bg + * - R.drawable.battery_unified_frame + * - R.string.battery_unified_frame_path_string + * - GoogleSans bold font + * + * See [BatteryDrawableState] for how to set the properties of the resulting class + */ + fun newBatteryDrawable( + context: Context, + initialState: BatteryDrawableState = BatteryDrawableState.DefaultInitialState, + ): BatteryLayersDrawable { + val framePath = + PathParser.createPathFromPathData( + context.getString(R.string.battery_unified_frame_path_string) + ) + + val frameBg = + context.getDrawable(R.drawable.battery_unified_frame_bg) + ?: throw IllegalStateException("Missing battery_unified_frame_bg.xml") + val frame = + context.getDrawable(R.drawable.battery_unified_frame) + ?: throw IllegalStateException("Missing battery_unified_frame.xml") + val fill = BatteryFillDrawable(framePath) + val textOnly = BatteryPercentTextOnlyDrawable(PercentFont) + val spaceSharingText = BatterySpaceSharingPercentTextDrawable(PercentFont) + val attribution = BatteryAttributionDrawable(null) + + return BatteryLayersDrawable( + frameBg = frameBg, + frame = frame, + fill = fill, + textOnly = textOnly, + spaceSharingText = spaceSharingText, + attribution = attribution, + batteryState = initialState, + ) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt new file mode 100644 index 000000000000..123d6ba57900 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2024 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.battery.unified + +import android.graphics.Canvas +import android.graphics.ColorFilter +import android.graphics.Paint +import android.graphics.PixelFormat +import android.graphics.Rect +import android.graphics.Typeface +import android.graphics.drawable.Drawable +import com.android.systemui.battery.unified.BatteryLayersDrawable.Companion.Metrics + +/** + * (Names are hard) this drawable calculates the percent text for inside of the + * [BatteryLayersDrawable], assuming that there is no other attribution in the foreground. In this + * case, we can use the maximum font size and center the text in the full render area inside of the + * frame. After accounting for the stroke width and the insets from there, our rendering area is + * 18x10 points. + * + * See [BatterySpaceSharingPercentTextDrawable] (names are still hard) for the space-sharing + * approach. + * + * Note that these drawing metrics are only tested to work with google-sans BOLD + */ +class BatteryPercentTextOnlyDrawable(font: Typeface) : Drawable() { + private var hScale = 1f + private var vScale = 1f + + // range 0-100 + var batteryLevel: Int = 100 + set(value) { + field = value + percentText = "$value" + invalidateSelf() + } + + private var percentText = "$batteryLevel" + + private val textPaint = + Paint().also { p -> + p.textSize = 10f + p.typeface = font + } + + override fun onBoundsChange(bounds: Rect) { + super.onBoundsChange(bounds) + + vScale = bounds.bottom / Metrics.ViewportHeight + hScale = bounds.right / Metrics.ViewportWidth + + updateScale() + } + + private fun updateScale() { + textPaint.textSize = TextSize * vScale + } + + override fun draw(canvas: Canvas) { + val totalAvailableHeight = CanvasHeight * vScale + + // Distribute the vertical whitespace around the text. This is a simplified version of + // the equation ((C - T) / 2) + T - V, where C == canvas height, T == text height, and V + // is the vertical nudge. + val offsetY = (totalAvailableHeight + textPaint.textSize) / 2 - (VerticalNudge * vScale) + + val totalAvailableWidth = CanvasWidth * hScale + val textWidth = textPaint.measureText(percentText) + val offsetX = (totalAvailableWidth - textWidth) / 2 + + // Draw the text centered in the available area + canvas.drawText( + percentText, + (ViewportInsetLeft * hScale) + offsetX, + (ViewportInsetTop * vScale) + offsetY, + textPaint + ) + } + + override fun setTint(tintColor: Int) { + textPaint.color = tintColor + super.setTint(tintColor) + } + + override fun getOpacity() = PixelFormat.OPAQUE + + override fun setAlpha(alpha: Int) {} + + override fun setColorFilter(colorFilter: ColorFilter?) {} + + companion object { + // Based on the 24x14 canvas, we can render in an 18x10 canvas, inset like so: + const val ViewportInsetLeft = 4f + const val ViewportInsetRight = 2f + const val ViewportInsetTop = 2f + const val CanvasHeight = 10f + const val CanvasWidth = 18f + + // raise the text up by a smidgen so that it is more centered. Experimentally determined + const val VerticalNudge = 1.5f + + // Experimentally-determined value + const val TextSize = 10f + } +} diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt new file mode 100644 index 000000000000..0c418b9caa7d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2024 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.battery.unified + +import android.graphics.Canvas +import android.graphics.ColorFilter +import android.graphics.Paint +import android.graphics.PixelFormat +import android.graphics.Rect +import android.graphics.Typeface +import android.graphics.drawable.Drawable +import com.android.systemui.battery.unified.BatteryLayersDrawable.Companion.Metrics + +/** + * A variant of [BatteryPercentTextOnlyDrawable] with the following differences: + * 1. It is defined on a canvas of 12x10 (shortened by 6 points horizontally) + * 2. Because of this, we scale the font according to the number of characters + * + * Note that these drawing metrics are only tested to work with google-sans BOLD + */ +class BatterySpaceSharingPercentTextDrawable(font: Typeface) : Drawable() { + private var verticalNudge = 0f + private var hScale = 1f + private var vScale = 1f + + // range 0-100 + var batteryLevel: Int = 88 + set(value) { + field = value + percentText = "$value" + invalidateSelf() + } + + private var percentText = "$batteryLevel" + set(value) { + field = value + numberOfCharacters = percentText.length + } + + private var numberOfCharacters = percentText.length + set(value) { + if (field != value) { + field = value + updateFontSize() + } + } + + private val textPaint = + Paint().also { p -> + p.textSize = 10f + p.typeface = font + } + + private fun updateFontSize() { + // These values are determined experimentally + when (numberOfCharacters) { + 3 -> { + verticalNudge = 1f + textPaint.textSize = 6f * hScale + } + // 1, 2 + else -> { + verticalNudge = 1.25f + textPaint.textSize = 9f * hScale + } + } + } + + private fun updateScale() { + updateFontSize() + } + + override fun onBoundsChange(bounds: Rect) { + super.onBoundsChange(bounds) + + hScale = bounds.right / Metrics.ViewportWidth + vScale = bounds.bottom / Metrics.ViewportHeight + + updateScale() + } + + override fun draw(canvas: Canvas) { + val totalAvailableHeight = CanvasHeight * vScale + + // Distribute the vertical whitespace around the text. This is a simplified version of + // the equation ((C - T) / 2) + T - V, where C == canvas height, T == text height, and V + // is the vertical nudge. + val offsetY = (totalAvailableHeight + textPaint.textSize) / 2 - (verticalNudge * vScale) + + val totalAvailableWidth = CanvasWidth * hScale + val textWidth = textPaint.measureText(percentText) + val offsetX = (totalAvailableWidth - textWidth) / 2 + + canvas.drawText( + percentText, + (ViewportInsetLeft * hScale) + offsetX, + (ViewportInsetTop * vScale) + offsetY, + textPaint + ) + } + + override fun setTint(tintColor: Int) { + textPaint.color = tintColor + super.setTint(tintColor) + } + + override fun getOpacity() = PixelFormat.OPAQUE + + override fun setAlpha(p0: Int) {} + + override fun setColorFilter(colorFilter: ColorFilter?) { + textPaint.colorFilter = colorFilter + } + + companion object { + private const val ViewportInsetLeft = 4f + private const val ViewportInsetTop = 2f + + private const val CanvasWidth = 12f + private const val CanvasHeight = 10f + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 5d525413a919..151e1eeaefc5 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -24,6 +24,7 @@ import com.android.systemui.communal.data.repository.CommunalPrefsRepository import com.android.systemui.communal.data.repository.CommunalRepository import com.android.systemui.communal.data.repository.CommunalWidgetRepository import com.android.systemui.communal.domain.model.CommunalContentModel +import com.android.systemui.communal.domain.model.CommunalContentModel.WidgetContent import com.android.systemui.communal.shared.model.CommunalContentSize import com.android.systemui.communal.shared.model.CommunalContentSize.FULL import com.android.systemui.communal.shared.model.CommunalContentSize.HALF @@ -278,14 +279,25 @@ constructor( } /** A list of widget content to be displayed in the communal hub. */ - val widgetContent: Flow<List<CommunalContentModel.Widget>> = - widgetRepository.communalWidgets.map { widgets -> - filterWidgetsByExistingUsers(widgets).map Widget@{ widget -> - return@Widget CommunalContentModel.Widget( - appWidgetId = widget.appWidgetId, - providerInfo = widget.providerInfo, - appWidgetHost = appWidgetHost, - ) + val widgetContent: Flow<List<WidgetContent>> = + combine( + widgetRepository.communalWidgets.map { filterWidgetsByExistingUsers(it) }, + communalSettingsInteractor.communalWidgetCategories + ) { widgets, allowedCategories -> + widgets.map { widget -> + if (widget.providerInfo.widgetCategory and allowedCategories != 0) { + // At least one category this widget specified is allowed, so show it + WidgetContent.Widget( + appWidgetId = widget.appWidgetId, + providerInfo = widget.providerInfo, + appWidgetHost = appWidgetHost, + ) + } else { + WidgetContent.DisabledWidget( + appWidgetId = widget.appWidgetId, + providerInfo = widget.providerInfo, + ) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt index ae019a187bae..c64f666ebf10 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt @@ -18,6 +18,7 @@ package com.android.systemui.communal.domain.model import android.appwidget.AppWidgetProviderInfo import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE +import android.content.pm.ApplicationInfo import android.widget.RemoteViews import com.android.systemui.communal.shared.model.CommunalContentSize import com.android.systemui.communal.widgets.CommunalAppWidgetHost @@ -42,20 +43,37 @@ sealed interface CommunalContentModel { val createdTimestampMillis: Long } - data class Widget( - val appWidgetId: Int, - val providerInfo: AppWidgetProviderInfo, - val appWidgetHost: CommunalAppWidgetHost, - ) : CommunalContentModel { - override val key = KEY.widget(appWidgetId) - // Widget size is always half. - override val size = CommunalContentSize.HALF + sealed interface WidgetContent : CommunalContentModel { + val appWidgetId: Int + val providerInfo: AppWidgetProviderInfo + + data class Widget( + override val appWidgetId: Int, + override val providerInfo: AppWidgetProviderInfo, + val appWidgetHost: CommunalAppWidgetHost, + ) : WidgetContent { + override val key = KEY.widget(appWidgetId) + // Widget size is always half. + override val size = CommunalContentSize.HALF + + /** Whether this widget can be reconfigured after it has already been added. */ + val reconfigurable: Boolean + get() = + (providerInfo.widgetFeatures and WIDGET_FEATURE_RECONFIGURABLE != 0) && + providerInfo.configure != null + } + + data class DisabledWidget( + override val appWidgetId: Int, + override val providerInfo: AppWidgetProviderInfo + ) : WidgetContent { + override val key = KEY.disabledWidget(appWidgetId) + // Widget size is always half. + override val size = CommunalContentSize.HALF - /** Whether this widget can be reconfigured after it has already been added. */ - val reconfigurable: Boolean - get() = - (providerInfo.widgetFeatures and WIDGET_FEATURE_RECONFIGURABLE != 0) && - providerInfo.configure != null + val appInfo: ApplicationInfo? + get() = providerInfo.providerInfo?.applicationInfo + } } /** A placeholder item representing a new widget being added */ @@ -111,6 +129,10 @@ sealed interface CommunalContentModel { return "widget_$id" } + fun disabledWidget(id: Int): String { + return "disabled_widget_$id" + } + fun widgetPlaceholder(): String { return "widget_placeholder_${UUID.randomUUID()}" } @@ -129,5 +151,5 @@ sealed interface CommunalContentModel { } } - fun isWidget() = this is Widget + fun isWidgetContent() = this is WidgetContent } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt index e8a84449566d..7f8103e63684 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt @@ -18,7 +18,7 @@ package com.android.systemui.controls.ui import android.app.Activity import android.app.ActivityOptions -import android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED +import android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED import android.app.Dialog import android.app.PendingIntent import android.content.ComponentName diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt index 9fc36923b04d..6aed944ac809 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt @@ -31,8 +31,8 @@ import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.flow.sample +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @SysUISingleton @@ -62,7 +62,6 @@ constructor( listenForTransitionToCamera(scope, keyguardInteractor) } - @FlowPreview private fun listenForAlternateBouncerToLockscreenHubAodOrDozing() { scope.launch { keyguardInteractor.alternateBouncerShowing @@ -71,7 +70,7 @@ constructor( // happening prematurely. // This should eventually be removed in favor of // [KeyguardTransitionInteractor#startDismissKeyguardTransition] - .sample(150L) + .onEach { delay(150L) } .sampleCombine( keyguardInteractor.primaryBouncerShowing, startedKeyguardTransitionStep, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index d1fd7195d8cc..719edd7e8535 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -45,7 +45,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest -import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.shareIn /** Encapsulates business-logic related to the keyguard transitions. */ @@ -177,10 +176,16 @@ constructor( * Lockscreen (0f). */ val dozeAmountTransition: Flow<TransitionStep> = - merge( - aodToLockscreenTransition.map { step -> step.copy(value = 1f - step.value) }, - lockscreenToAodTransition, - ) + repository.transitions + .filter { step -> step.from == AOD || step.to == AOD } + .map { step -> + if (step.from == AOD) { + step.copy(value = 1 - step.value) + } else { + step + } + } + .shareIn(scope, SharingStarted.Eagerly, replay = 1) /** The last [TransitionStep] with a [TransitionState] of STARTED */ val startedKeyguardTransitionStep: Flow<TransitionStep> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt index 8a3b57ba027f..5741b9485287 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt @@ -29,7 +29,6 @@ import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combineTransform -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.onStart /** Models UI state for the alpha of the AOD (always-on display). */ @@ -46,24 +45,23 @@ constructor( /** The alpha level for the entire lockscreen while in AOD. */ val alpha: Flow<Float> = combineTransform( - keyguardTransitionInteractor.transitions, - goneToAodTransitionViewModel.enterFromTopAnimationAlpha.onStart { emit(0f) }, - goneToDozingTransitionViewModel.lockscreenAlpha.onStart { emit(0f) }, - keyguardInteractor.keyguardAlpha.onStart { emit(1f) }, - ) { step, goneToAodAlpha, goneToDozingAlpha, keyguardAlpha -> - if (step.to == GONE) { - // When transitioning to GONE, only emit a value when complete as other - // transitions may be controlling the alpha fade - if (step.value == 1f) { - emit(0f) - } - } else if (step.from == GONE && step.to == AOD) { - emit(goneToAodAlpha) - } else if (step.from == GONE && step.to == DOZING) { - emit(goneToDozingAlpha) - } else if (!migrateClocksToBlueprint()) { - emit(keyguardAlpha) + keyguardTransitionInteractor.transitions, + goneToAodTransitionViewModel.enterFromTopAnimationAlpha.onStart { emit(0f) }, + goneToDozingTransitionViewModel.lockscreenAlpha.onStart { emit(0f) }, + keyguardInteractor.keyguardAlpha.onStart { emit(1f) }, + ) { step, goneToAodAlpha, goneToDozingAlpha, keyguardAlpha -> + if (step.to == GONE) { + // When transitioning to GONE, only emit a value when complete as other + // transitions may be controlling the alpha fade + if (step.value == 1f) { + emit(0f) } + } else if (step.from == GONE && step.to == AOD) { + emit(goneToAodAlpha) + } else if (step.from == GONE && step.to == DOZING) { + emit(goneToDozingAlpha) + } else if (!migrateClocksToBlueprint()) { + emit(keyguardAlpha) } - .distinctUntilChanged() + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt index 8665aabc3ef7..7be390a4526f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt @@ -28,10 +28,6 @@ import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.BurnInModel -import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER -import com.android.systemui.keyguard.shared.model.KeyguardState.AOD -import com.android.systemui.keyguard.shared.model.KeyguardState.GONE -import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING import com.android.systemui.keyguard.shared.model.TransitionState.STARTED @@ -47,7 +43,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart /** @@ -127,17 +122,9 @@ constructor( params: BurnInParameters, ): Flow<BurnInModel> { return combine( - merge( - keyguardTransitionInteractor.transition(GONE, AOD).map { it.value }, - keyguardTransitionInteractor.transition(AOD, PRIMARY_BOUNCER).map { - 1f - it.value - }, - keyguardTransitionInteractor.transition(ALTERNATE_BOUNCER, AOD).map { - it.value - }, - keyguardTransitionInteractor.dozeAmountTransition.map { it.value }, - ) - .map { dozeAmount -> Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount) }, + keyguardTransitionInteractor.dozeAmountTransition.map { + Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it.value) + }, burnInInteractor.keyguardBurnIn, ) { interpolated, burnIn -> val useScaleOnly = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index 921eb66cd3cc..bdcaf0951c5b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -74,6 +74,10 @@ constructor( private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel, private val glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel, + private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel, + private val goneToDozingTransitionViewModel: GoneToDozingTransitionViewModel, + private val lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel, + private val lockscreenToDozingTransitionViewModel: LockscreenToDozingTransitionViewModel, private val lockscreenToDreamingTransitionViewModel: LockscreenToDreamingTransitionViewModel, private val lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel, @@ -133,17 +137,24 @@ constructor( fun alpha(viewState: ViewStateAccessor): Flow<Float> { return combine( communalInteractor.isIdleOnCommunal, - keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) }, + keyguardTransitionInteractor + .transitionValue(GONE) + .map { it == 1f } + .onStart { emit(false) } + .distinctUntilChanged(), // The transitions are mutually exclusive, so they are safe to merge to get the last // value emitted by any of them. Do not add flows that cannot make this guarantee. merge( - aodAlphaViewModel.alpha, alphaOnShadeExpansion, keyguardInteractor.dismissAlpha.filterNotNull(), alternateBouncerToGoneTransitionViewModel.lockscreenAlpha, aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState), dozingToLockscreenTransitionViewModel.lockscreenAlpha, glanceableHubToLockscreenTransitionViewModel.keyguardAlpha, + goneToAodTransitionViewModel.enterFromTopAnimationAlpha, + goneToDozingTransitionViewModel.lockscreenAlpha, + lockscreenToAodTransitionViewModel.lockscreenAlpha(viewState), + lockscreenToDozingTransitionViewModel.lockscreenAlpha, lockscreenToDreamingTransitionViewModel.lockscreenAlpha, lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha, lockscreenToGoneTransitionViewModel.lockscreenAlpha(viewState), @@ -156,8 +167,8 @@ constructor( primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha, ) .onStart { emit(1f) } - ) { isIdleOnCommunal, goneValue, alpha -> - if (isIdleOnCommunal || goneValue == 1f) { + ) { isIdleOnCommunal, gone, alpha -> + if (isIdleOnCommunal || gone) { // Keyguard should not show while the communal hub is fully visible. This check // is added since at the moment, closing the notification shade will cause the // keyguard alpha to be set back to 1. Also ensure keyguard is never visible diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt index 7bf51a7d3d54..1f9f3043dfdf 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor @@ -67,6 +68,15 @@ constructor( onCancel = { 1f }, ) + fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> { + var startAlpha = 1f + return transitionAnimation.sharedFlow( + duration = 500.milliseconds, + onStart = { startAlpha = viewState.alpha() }, + onStep = { MathUtils.lerp(startAlpha, 1f, it) }, + ) + } + override val deviceEntryParentViewAlpha: Flow<Float> = deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { isUdfpsEnrolledAndEnabled -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt index b60c52b1c3df..c836f01e2ee9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt @@ -44,6 +44,14 @@ constructor( to = KeyguardState.DOZING, ) + val lockscreenAlpha: Flow<Float> = + transitionAnimation.sharedFlow( + duration = 250.milliseconds, + onStep = { 1 - it }, + onFinish = { 1f }, + onCancel = { 1f }, + ) + val shortcutsAlpha: Flow<Float> = transitionAnimation.sharedFlow( duration = 250.milliseconds, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt index 6fc22ea60a9e..865c49e1d817 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt @@ -17,6 +17,7 @@ package com.android.systemui.media.controls.domain.pipeline import android.annotation.SuppressLint +import android.app.ActivityOptions import android.app.BroadcastOptions import android.app.Notification import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME @@ -1272,7 +1273,7 @@ class MediaDataManager( val options = BroadcastOptions.makeBasic() options.setInteractive(true) options.setPendingIntentBackgroundActivityStartMode( - BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED ) intent.send(options.toBundle()) true diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt index a811065fdc65..e1741c73d453 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt @@ -18,7 +18,7 @@ package com.android.systemui.mediaprojection.appselector.view import android.app.ActivityOptions import android.app.ActivityOptions.LaunchCookie -import android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED +import android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED import android.app.IActivityTaskManager import android.graphics.Rect import android.view.LayoutInflater diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 000f3c09d84c..5f8b5ddc9de6 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -221,7 +221,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis // If scene framework is enabled, set the scene container window to // visible and let the touch "slip" into that window. if (mSceneContainerFlags.isEnabled()) { - mSceneInteractor.get().setVisible(true, "swipe down on launcher"); + mSceneInteractor.get().onRemoteUserInteractionStarted("launcher swipe"); } else { mShadeViewControllerLazy.get().startInputFocusTransfer(); } diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt index a3021946713f..e60dff183148 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt @@ -49,6 +49,13 @@ constructor( private val _isVisible = MutableStateFlow(true) val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow() + /** + * Whether there's an ongoing remotely-initiated user interaction. + * + * For more information see the logic in `SceneInteractor` that mutates this. + */ + val isRemoteUserInteractionOngoing = MutableStateFlow(false) + private val defaultTransitionState = ObservableTransitionState.Idle(config.initialSceneKey) private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null) val transitionState: StateFlow<ObservableTransitionState> = diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt index 0add4443fa7a..6b7c672fbfe0 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt @@ -31,6 +31,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map @@ -121,7 +122,21 @@ constructor( ) /** Whether the scene container is visible. */ - val isVisible: StateFlow<Boolean> = repository.isVisible + val isVisible: StateFlow<Boolean> = + combine( + repository.isVisible, + repository.isRemoteUserInteractionOngoing, + ) { isVisible, isRemoteUserInteractionOngoing -> + isVisibleInternal( + raw = isVisible, + isRemoteUserInteractionOngoing = isRemoteUserInteractionOngoing, + ) + } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = isVisibleInternal() + ) /** * Returns the keys of all scenes in the container. @@ -164,7 +179,14 @@ constructor( repository.changeScene(toScene, transitionKey) } - /** Sets the visibility of the container. */ + /** + * Sets the visibility of the container. + * + * Please do not call this from outside of the scene framework. If you are trying to force the + * visibility to visible or invisible, prefer making changes to the existing caller of this + * method or to upstream state used to calculate [isVisible]; for an example of the latter, + * please see [onRemoteUserInteractionStarted] and [onUserInteractionFinished]. + */ fun setVisible(isVisible: Boolean, loggingReason: String) { val wasVisible = repository.isVisible.value if (wasVisible == isVisible) { @@ -180,6 +202,31 @@ constructor( } /** + * Notifies that a remote user interaction has begun. + * + * This is a user interaction that originates outside of the UI of the scene container and + * possibly outside of the System UI process itself. + * + * As an example, consider the dragging that can happen in the launcher that expands the shade. + * This is a user interaction that begins remotely (as it starts in the launcher process) and is + * then rerouted by window manager to System UI. While the user interaction definitely continues + * within the System UI process and code, it also originates remotely. + */ + fun onRemoteUserInteractionStarted(loggingReason: String) { + logger.logRemoteUserInteractionStarted(loggingReason) + repository.isRemoteUserInteractionOngoing.value = true + } + + /** + * Notifies that the current user interaction (internally or remotely started, see + * [onRemoteUserInteractionStarted]) has finished. + */ + fun onUserInteractionFinished() { + logger.logUserInteractionFinished() + repository.isRemoteUserInteractionOngoing.value = false + } + + /** * Binds the given flow so the system remembers it. * * Note that you must call is with `null` when the UI is done or risk a memory leak. @@ -187,4 +234,11 @@ constructor( fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) { repository.setTransitionState(transitionState) } + + private fun isVisibleInternal( + raw: Boolean = repository.isVisible.value, + isRemoteUserInteractionOngoing: Boolean = repository.isRemoteUserInteractionOngoing.value, + ): Boolean { + return raw || isRemoteUserInteractionOngoing + } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt index d59fcff34796..cbf7b3e7a971 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt @@ -95,6 +95,26 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: ) } + fun logRemoteUserInteractionStarted( + reason: String, + ) { + logBuffer.log( + tag = TAG, + level = LogLevel.INFO, + messageInitializer = { str1 = reason }, + messagePrinter = { "remote user interaction started, reason: $str3" }, + ) + } + + fun logUserInteractionFinished() { + logBuffer.log( + tag = TAG, + level = LogLevel.INFO, + messageInitializer = {}, + messagePrinter = { "user interaction finished" }, + ) + } + companion object { private const val TAG = "SceneFramework" } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt index 4cd3baa7a52e..91861aa5c29a 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt @@ -68,6 +68,12 @@ constructor( fun onMotionEvent(event: MotionEvent) { powerInteractor.onUserTouch() falsingInteractor.onTouchEvent(event) + if ( + event.actionMasked == MotionEvent.ACTION_UP || + event.actionMasked == MotionEvent.ACTION_CANCEL + ) { + sceneInteractor.onUserInteractionFinished() + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt index fb5339df7212..1f9853b17a28 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt @@ -16,6 +16,8 @@ package com.android.systemui.screenshot +import android.app.ActivityOptions +import android.app.ExitTransitionCoordinator import android.content.Context import android.content.Intent import android.os.Bundle @@ -23,6 +25,7 @@ import android.os.Process.myUserHandle import android.os.RemoteException import android.os.UserHandle import android.util.Log +import android.util.Pair import android.view.IRemoteAnimationFinishedCallback import android.view.IRemoteAnimationRunner import android.view.RemoteAnimationAdapter @@ -64,18 +67,18 @@ constructor( */ fun launchIntentAsync( intent: Intent, - options: Bundle?, + transition: Pair<ActivityOptions, ExitTransitionCoordinator>?, user: UserHandle, overrideTransition: Boolean, ) { applicationScope.launch("$TAG#launchIntentAsync") { - launchIntent(intent, options, user, overrideTransition) + launchIntent(intent, transition, user, overrideTransition) } } suspend fun launchIntent( intent: Intent, - options: Bundle?, + transition: Pair<ActivityOptions, ExitTransitionCoordinator>?, user: UserHandle, overrideTransition: Boolean, ) { @@ -87,11 +90,14 @@ constructor( } else { dismissKeyguard() } + transition?.second?.startExit() if (user == myUserHandle()) { - withContext(mainDispatcher) { context.startActivity(intent, options) } + withContext(mainDispatcher) { + context.startActivity(intent, transition?.first?.toBundle()) + } } else { - launchCrossProfileIntent(user, intent, options) + launchCrossProfileIntent(user, intent, transition?.first?.toBundle()) } if (overrideTransition) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java index 0588fe2bba82..30f5e8b50bf4 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java @@ -18,6 +18,7 @@ package com.android.systemui.screenshot; import static java.util.Objects.requireNonNull; +import android.app.ActivityOptions; import android.app.BroadcastOptions; import android.app.PendingIntent; import android.content.Context; @@ -100,7 +101,7 @@ public class OverlayActionChip extends FrameLayout { BroadcastOptions options = BroadcastOptions.makeBasic(); options.setInteractive(true); options.setPendingIntentBackgroundActivityStartMode( - BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); intent.send(options.toBundle()); finisher.run(); } catch (PendingIntent.CanceledException e) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index 31086d85f747..bbf7ed529220 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -16,40 +16,29 @@ package com.android.systemui.screenshot; -import static com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS; import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK; import static com.android.systemui.screenshot.LogConfig.DEBUG_STORAGE; import static com.android.systemui.screenshot.LogConfig.logTag; import static com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType; -import android.app.ActivityTaskManager; import android.app.Notification; import android.app.PendingIntent; import android.content.ClipData; import android.content.ClipDescription; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.UserInfo; -import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.drawable.Icon; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Process; -import android.os.RemoteException; import android.os.UserHandle; -import android.os.UserManager; import android.provider.DeviceConfig; -import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; -import com.android.systemui.res.R; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition; import com.google.common.util.concurrent.ListenableFuture; @@ -60,7 +49,6 @@ import java.util.List; import java.util.Random; import java.util.UUID; import java.util.concurrent.CompletableFuture; -import java.util.function.Supplier; /** * An AsyncTask that saves an image to the media store in the background. @@ -81,7 +69,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider; private String mScreenshotId; private final Random mRandom = new Random(); - private final Supplier<ActionTransition> mSharedElementTransition; private final ImageExporter mImageExporter; private long mImageTime; @@ -91,7 +78,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { ImageExporter exporter, ScreenshotSmartActions screenshotSmartActions, ScreenshotController.SaveImageInBackgroundData data, - Supplier<ActionTransition> sharedElementTransition, ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider ) { @@ -100,7 +86,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mScreenshotSmartActions = screenshotSmartActions; mImageData = new ScreenshotController.SavedImageData(); mQuickShareData = new ScreenshotController.QuickShareData(); - mSharedElementTransition = sharedElementTransition; mImageExporter = exporter; // Prepare all the output metadata @@ -176,12 +161,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mImageData.uri = uri; mImageData.owner = mParams.owner; mImageData.smartActions = smartActions; - mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri, - smartActionsEnabled); - mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri, - smartActionsEnabled); - mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri, - smartActionsEnabled); mImageData.quickShareAction = createQuickShareAction( mQuickShareData.quickShareAction, mScreenshotId, uri, mImageTime, image, mParams.owner); @@ -234,164 +213,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mParams.clearImage(); } - /** - * Assumes that the action intent is sent immediately after being supplied. - */ - @VisibleForTesting - Supplier<ActionTransition> createShareAction(Context context, Resources r, Uri uri, - boolean smartActionsEnabled) { - return () -> { - ActionTransition transition = mSharedElementTransition.get(); - - // Note: Both the share and edit actions are proxied through ActionProxyReceiver in - // order to do some common work like dismissing the keyguard and sending - // closeSystemWindows - - // Create a share intent, this will always go through the chooser activity first - // which should not trigger auto-enter PiP - Intent sharingIntent = new Intent(Intent.ACTION_SEND); - sharingIntent.setDataAndType(uri, "image/png"); - sharingIntent.putExtra(Intent.EXTRA_STREAM, uri); - // Include URI in ClipData also, so that grantPermission picks it up. - // We don't use setData here because some apps interpret this as "to:". - ClipData clipdata = new ClipData(new ClipDescription("content", - new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}), - new ClipData.Item(uri)); - sharingIntent.setClipData(clipdata); - sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getSubjectString(mImageTime)); - sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - - - // Make sure pending intents for the system user are still unique across users - // by setting the (otherwise unused) request code to the current user id. - int requestCode = context.getUserId(); - - Intent sharingChooserIntent = Intent.createChooser(sharingIntent, null) - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK) - .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - - - // cancel current pending intent (if any) since clipData isn't used for matching - PendingIntent pendingIntent = PendingIntent.getActivityAsUser( - context, 0, sharingChooserIntent, - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, - transition.bundle, UserHandle.CURRENT); - - // Create a share action for the notification - PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode, - new Intent(context, ActionProxyReceiver.class) - .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent) - .putExtra(ScreenshotController.EXTRA_DISALLOW_ENTER_PIP, true) - .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId) - .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, - smartActionsEnabled) - .setAction(Intent.ACTION_SEND) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, - UserHandle.SYSTEM); - - Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder( - Icon.createWithResource(r, R.drawable.ic_screenshot_share), - r.getString(com.android.internal.R.string.share), shareAction); - - transition.action = shareActionBuilder.build(); - return transition; - }; - } - - @VisibleForTesting - Supplier<ActionTransition> createEditAction(Context context, Resources r, Uri uri, - boolean smartActionsEnabled) { - return () -> { - ActionTransition transition = mSharedElementTransition.get(); - // Note: Both the share and edit actions are proxied through ActionProxyReceiver in - // order to do some common work like dismissing the keyguard and sending - // closeSystemWindows - - // Create an edit intent, if a specific package is provided as the editor, then - // launch that directly - String editorPackage = context.getString(R.string.config_screenshotEditor); - Intent editIntent = new Intent(Intent.ACTION_EDIT); - if (!TextUtils.isEmpty(editorPackage)) { - editIntent.setComponent(ComponentName.unflattenFromString(editorPackage)); - } - editIntent.setDataAndType(uri, "image/png"); - editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - - PendingIntent pendingIntent = PendingIntent.getActivityAsUser( - context, 0, editIntent, PendingIntent.FLAG_IMMUTABLE, - transition.bundle, UserHandle.CURRENT); - - // Make sure pending intents for the system user are still unique across users - // by setting the (otherwise unused) request code to the current user id. - int requestCode = mContext.getUserId(); - - // Create an edit action - PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode, - new Intent(context, ActionProxyReceiver.class) - .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent) - .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId) - .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, - smartActionsEnabled) - .putExtra(ScreenshotController.EXTRA_OVERRIDE_TRANSITION, true) - .setAction(Intent.ACTION_EDIT) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, - UserHandle.SYSTEM); - Notification.Action.Builder editActionBuilder = new Notification.Action.Builder( - Icon.createWithResource(r, R.drawable.ic_screenshot_edit), - r.getString(com.android.internal.R.string.screenshot_edit), editAction); - - transition.action = editActionBuilder.build(); - return transition; - }; - } - - @VisibleForTesting - Notification.Action createDeleteAction(Context context, Resources r, Uri uri, - boolean smartActionsEnabled) { - // Make sure pending intents for the system user are still unique across users - // by setting the (otherwise unused) request code to the current user id. - int requestCode = mContext.getUserId(); - - // Create a delete action for the notification - PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode, - new Intent(context, DeleteScreenshotReceiver.class) - .putExtra(ScreenshotController.SCREENSHOT_URI_ID, uri.toString()) - .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId) - .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, - smartActionsEnabled) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), - PendingIntent.FLAG_CANCEL_CURRENT - | PendingIntent.FLAG_ONE_SHOT - | PendingIntent.FLAG_IMMUTABLE); - Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder( - Icon.createWithResource(r, R.drawable.ic_screenshot_delete), - r.getString(com.android.internal.R.string.delete), deleteAction); - - return deleteActionBuilder.build(); - } - - private UserHandle getUserHandleOfForegroundApplication(Context context) { - UserManager manager = UserManager.get(context); - int result; - // This logic matches - // com.android.systemui.statusbar.phone.PhoneStatusBarPolicy#updateManagedProfile - try { - result = ActivityTaskManager.getService().getLastResumedActivityUserId(); - } catch (RemoteException e) { - if (DEBUG_ACTIONS) { - Log.d(TAG, "Failed to get UserHandle of foreground app: ", e); - } - result = context.getUserId(); - } - UserInfo userInfo = manager.getUserInfo(result); - return userInfo.getUserHandle(); - } - private List<Notification.Action> buildSmartActions( List<Notification.Action> actions, Context context) { List<Notification.Action> broadcastActions = new ArrayList<>(); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 21a08a9a4980..ee3e7ba9e8b3 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -39,7 +39,6 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.ExitTransitionCoordinator; -import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks; import android.app.ICompatCameraControlCallback; import android.app.Notification; import android.app.assist.AssistContent; @@ -54,7 +53,6 @@ import android.graphics.Insets; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.net.Uri; -import android.os.Bundle; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; @@ -87,13 +85,12 @@ import com.android.internal.app.ChooserActivity; import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.PhoneWindow; import com.android.settingslib.applications.InterestingConfigChanges; -import com.android.systemui.res.R; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.clipboardoverlay.ClipboardOverlayController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; -import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition; +import com.android.systemui.res.R; import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback; import com.android.systemui.util.Assert; @@ -111,7 +108,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.function.Consumer; -import java.util.function.Supplier; import javax.inject.Provider; @@ -171,31 +167,16 @@ public class ScreenshotController { */ static class SavedImageData { public Uri uri; - public Supplier<ActionTransition> shareTransition; - public Supplier<ActionTransition> editTransition; - public Notification.Action deleteAction; public List<Notification.Action> smartActions; public Notification.Action quickShareAction; public UserHandle owner; public String subject; // Title for sharing /** - * POD for shared element transition. - */ - static class ActionTransition { - public Bundle bundle; - public Notification.Action action; - public Runnable onCancelRunnable; - } - - /** * Used to reset the return data on error */ public void reset() { uri = null; - shareTransition = null; - editTransition = null; - deleteAction = null; smartActions = null; quickShareAction = null; subject = null; @@ -469,8 +450,9 @@ public class ScreenshotController { if (!shouldShowUi()) { saveScreenshotInWorkerThread( - screenshot.getUserHandle(), finisher, this::logSuccessOnActionsReady, - (ignored) -> {}); + screenshot.getUserHandle(), finisher, this::logSuccessOnActionsReady, + (ignored) -> { + }); return; } @@ -489,7 +471,7 @@ public class ScreenshotController { if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) { if (screenshot.getScreenBounds() != null && aspectRatiosMatch(screenshot.getBitmap(), screenshot.getInsets(), - screenshot.getScreenBounds())) { + screenshot.getScreenBounds())) { showFlash = false; } else { showFlash = true; @@ -643,6 +625,12 @@ public class ScreenshotController { } @Override + public void onAction(Intent intent, UserHandle owner, boolean overrideTransition) { + mActionExecutor.launchIntentAsync( + intent, createWindowTransition(), owner, overrideTransition); + } + + @Override public void onDismiss() { finishDismiss(); } @@ -652,7 +640,7 @@ public class ScreenshotController { // TODO(159460485): Remove this when focus is handled properly in the system setWindowFocusable(false); } - }, mActionExecutor, mFlags); + }, mFlags); mScreenshotView.setDefaultDisplay(mDisplayId); mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis()); @@ -964,6 +952,35 @@ public class ScreenshotController { mScreenshotAnimation.start(); } + /** + * Supplies the necessary bits for the shared element transition to share sheet. + * Note that once called, the action intent to share must be sent immediately after. + */ + private Pair<ActivityOptions, ExitTransitionCoordinator> createWindowTransition() { + ExitTransitionCoordinator.ExitTransitionCallbacks callbacks = + new ExitTransitionCoordinator.ExitTransitionCallbacks() { + @Override + public boolean isReturnTransitionAllowed() { + return false; + } + + @Override + public void hideSharedElements() { + finishDismiss(); + } + + @Override + public void onFinish() { + } + }; + Pair<ActivityOptions, ExitTransitionCoordinator> transition = + ActivityOptions.startSharedElementAnimation(mWindow, callbacks, null, + Pair.create(mScreenshotView.getScreenshotPreview(), + ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)); + + return transition; + } + /** Reset screenshot view and then call onCompleteRunnable */ private void finishDismiss() { Log.d(TAG, "finishDismiss"); @@ -1011,7 +1028,7 @@ public class ScreenshotController { } mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mFlags, mImageExporter, - mScreenshotSmartActions, data, getActionTransitionSupplier(), + mScreenshotSmartActions, data, mScreenshotNotificationSmartActionsProvider); mSaveInBgTask.execute(); } @@ -1078,26 +1095,6 @@ public class ScreenshotController { } /** - * Supplies the necessary bits for the shared element transition to share sheet. - * Note that once supplied, the action intent to share must be sent immediately after. - */ - private Supplier<ActionTransition> getActionTransitionSupplier() { - return () -> { - Pair<ActivityOptions, ExitTransitionCoordinator> transition = - ActivityOptions.startSharedElementAnimation( - mWindow, new ScreenshotExitTransitionCallbacksSupplier(true).get(), - null, Pair.create(mScreenshotView.getScreenshotPreview(), - ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)); - transition.second.startExit(); - - ActionTransition supply = new ActionTransition(); - supply.bundle = transition.first.toBundle(); - supply.onCancelRunnable = () -> ActivityOptions.stopSharedElementAnimation(mWindow); - return supply; - }; - } - - /** * Logs success/failure of the screenshot saving task, and shows an error if it failed. */ private void logSuccessOnActionsReady(ScreenshotController.SavedImageData imageData) { @@ -1186,36 +1183,6 @@ public class ScreenshotController { return matchWithinTolerance; } - private class ScreenshotExitTransitionCallbacksSupplier implements - Supplier<ExitTransitionCallbacks> { - final boolean mDismissOnHideSharedElements; - - ScreenshotExitTransitionCallbacksSupplier(boolean dismissOnHideSharedElements) { - mDismissOnHideSharedElements = dismissOnHideSharedElements; - } - - @Override - public ExitTransitionCallbacks get() { - return new ExitTransitionCallbacks() { - @Override - public boolean isReturnTransitionAllowed() { - return false; - } - - @Override - public void hideSharedElements() { - if (mDismissOnHideSharedElements) { - finishDismiss(); - } - } - - @Override - public void onFinish() { - } - }; - } - } - /** Injectable factory to create screenshot controller instances for a specific display. */ @AssistedFactory public interface Factory { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index 31c928406aac..be30a1576b21 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -57,6 +57,7 @@ import android.graphics.drawable.LayerDrawable; import android.os.Bundle; import android.os.Looper; import android.os.RemoteException; +import android.os.UserHandle; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; @@ -86,9 +87,9 @@ import androidx.constraintlayout.widget.ConstraintLayout; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEventLogger; -import com.android.systemui.res.R; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.res.R; import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.shared.system.InputMonitorCompat; import com.android.systemui.shared.system.QuickStepContract; @@ -104,6 +105,8 @@ public class ScreenshotView extends FrameLayout implements interface ScreenshotViewCallback { void onUserInteraction(); + void onAction(Intent intent, UserHandle owner, boolean overrideTransition); + void onDismiss(); /** DOWN motion event was observed outside of the touchable areas of this view. */ @@ -166,7 +169,6 @@ public class ScreenshotView extends FrameLayout implements private final InteractionJankMonitor mInteractionJankMonitor; private long mDefaultTimeoutOfTimeoutHandler; - private ActionIntentExecutor mActionExecutor; private FeatureFlags mFlags; private final Bundle mInteractiveBroadcastOption; @@ -430,11 +432,9 @@ public class ScreenshotView extends FrameLayout implements * Note: must be called before any other (non-constructor) method or null pointer exceptions * may occur. */ - void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks, - ActionIntentExecutor actionExecutor, FeatureFlags flags) { + void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks, FeatureFlags flags) { mUiEventLogger = uiEventLogger; mCallbacks = callbacks; - mActionExecutor = actionExecutor; mFlags = flags; } @@ -800,24 +800,21 @@ public class ScreenshotView extends FrameLayout implements shareIntent = ActionIntentCreator.INSTANCE.createShareWithSubject( imageData.uri, imageData.subject); } - mActionExecutor.launchIntentAsync(shareIntent, - imageData.shareTransition.get().bundle, - imageData.owner, false); + mCallbacks.onAction(shareIntent, imageData.owner, false); + }); mEditChip.setOnClickListener(v -> { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED, 0, mPackageName); prepareSharedTransition(); - mActionExecutor.launchIntentAsync( + mCallbacks.onAction( ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext), - imageData.editTransition.get().bundle, imageData.owner, true); }); mScreenshotPreview.setOnClickListener(v -> { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED, 0, mPackageName); prepareSharedTransition(); - mActionExecutor.launchIntentAsync( + mCallbacks.onAction( ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext), - imageData.editTransition.get().bundle, imageData.owner, true); }); if (mQuickShareChip != null) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 7b330b0f3803..fd3c480a334f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -3074,7 +3074,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private void onClosingFinished() { - mOpenCloseListener.onClosingFinished(); + if (mOpenCloseListener != null) { + mOpenCloseListener.onClosingFinished(); + } setClosingWithAlphaFadeout(false); mMediaHierarchyManager.closeGuts(); } @@ -4705,7 +4707,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump if (mSplitShadeEnabled && !isKeyguardShowing()) { mQsController.setExpandImmediate(true); } - mOpenCloseListener.onOpenStarted(); + if (mOpenCloseListener != null) { + mOpenCloseListener.onOpenStarted(); + } } if (state == STATE_CLOSED) { mQsController.setExpandImmediate(false); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt index 835225009110..a58ce4162ddc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt @@ -22,7 +22,9 @@ import android.util.AttributeSet import android.view.View import android.widget.FrameLayout import android.widget.LinearLayout +import com.android.settingslib.flags.Flags.newStatusBarIcons import com.android.systemui.battery.BatteryMeterView +import com.android.systemui.battery.unified.BatteryColors import com.android.systemui.res.R import com.android.systemui.statusbar.events.BackgroundAnimatableView @@ -39,8 +41,12 @@ class BatteryStatusChip @JvmOverloads constructor(context: Context, attrs: Attri roundedContainer = requireViewById(R.id.rounded_container) batteryMeterView = requireViewById(R.id.battery_meter_view) batteryMeterView.setStaticColor(true) - val primaryColor = context.resources.getColor(android.R.color.black, context.theme) - batteryMeterView.updateColors(primaryColor, primaryColor, primaryColor) + if (newStatusBarIcons()) { + batteryMeterView.setUnifiedBatteryColors(BatteryColors.LightThemeColors) + } else { + val primaryColor = context.resources.getColor(android.R.color.black, context.theme) + batteryMeterView.updateColors(primaryColor, primaryColor, primaryColor) + } updateResources() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java index e582a01ea88b..dbcda418496e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.connectivity; +import static com.android.settingslib.flags.Flags.newStatusBarIcons; + import com.android.settingslib.AccessibilityContentDescriptions; import com.android.settingslib.R; import com.android.settingslib.SignalIcon.IconGroup; @@ -23,21 +25,52 @@ import com.android.settingslib.SignalIcon.IconGroup; /** */ public class WifiIcons { - public static final int[] WIFI_FULL_ICONS = { - com.android.internal.R.drawable.ic_wifi_signal_0, - com.android.internal.R.drawable.ic_wifi_signal_1, - com.android.internal.R.drawable.ic_wifi_signal_2, - com.android.internal.R.drawable.ic_wifi_signal_3, - com.android.internal.R.drawable.ic_wifi_signal_4 - }; + public static final int[] WIFI_FULL_ICONS = getIconsBasedOnFlag(); - public static final int[] WIFI_NO_INTERNET_ICONS = { - R.drawable.ic_no_internet_wifi_signal_0, - R.drawable.ic_no_internet_wifi_signal_1, - R.drawable.ic_no_internet_wifi_signal_2, - R.drawable.ic_no_internet_wifi_signal_3, - R.drawable.ic_no_internet_wifi_signal_4 - }; + /** + * Check the aconfig flag to decide on which icons to use. Can be removed once the flag is gone + */ + private static int[] getIconsBasedOnFlag() { + if (newStatusBarIcons()) { + return new int[] { + R.drawable.ic_wifi_0, + R.drawable.ic_wifi_1, + R.drawable.ic_wifi_2, + R.drawable.ic_wifi_3, + R.drawable.ic_wifi_4 + }; + } else { + return new int[] { + com.android.internal.R.drawable.ic_wifi_signal_0, + com.android.internal.R.drawable.ic_wifi_signal_1, + com.android.internal.R.drawable.ic_wifi_signal_2, + com.android.internal.R.drawable.ic_wifi_signal_3, + com.android.internal.R.drawable.ic_wifi_signal_4 + }; + } + } + + public static final int[] WIFI_NO_INTERNET_ICONS = getErrorIconsBasedOnFlag(); + + private static int [] getErrorIconsBasedOnFlag() { + if (newStatusBarIcons()) { + return new int[] { + R.drawable.ic_wifi_0_error, + R.drawable.ic_wifi_1_error, + R.drawable.ic_wifi_2_error, + R.drawable.ic_wifi_3_error, + R.drawable.ic_wifi_4_error + }; + } else { + return new int[] { + R.drawable.ic_no_internet_wifi_signal_0, + R.drawable.ic_no_internet_wifi_signal_1, + R.drawable.ic_no_internet_wifi_signal_2, + R.drawable.ic_no_internet_wifi_signal_3, + R.drawable.ic_no_internet_wifi_signal_4 + }; + } + } public static final int[][] QS_WIFI_SIGNAL_STRENGTH = { WIFI_NO_INTERNET_ICONS, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java index 6f992ac815cb..d513f8da06e5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java @@ -15,10 +15,12 @@ package com.android.systemui.statusbar.phone; import static com.android.systemui.plugins.DarkIconDispatcher.getTint; +import static com.android.settingslib.flags.Flags.newStatusBarIcons; import android.animation.ArgbEvaluator; import android.content.Context; import android.content.res.ColorStateList; +import android.graphics.Color; import android.graphics.Rect; import android.util.ArrayMap; import android.widget.ImageView; @@ -66,10 +68,16 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher, Context context, LightBarTransitionsController.Factory lightBarTransitionsControllerFactory, DumpManager dumpManager) { - mDarkModeIconColorSingleTone = context.getColor( - com.android.settingslib.R.color.dark_mode_icon_color_single_tone); - mLightModeIconColorSingleTone = context.getColor( - com.android.settingslib.R.color.light_mode_icon_color_single_tone); + + if (newStatusBarIcons()) { + mDarkModeIconColorSingleTone = Color.BLACK; + mLightModeIconColorSingleTone = Color.WHITE; + } else { + mDarkModeIconColorSingleTone = context.getColor( + com.android.settingslib.R.color.dark_mode_icon_color_single_tone); + mLightModeIconColorSingleTone = context.getColor( + com.android.settingslib.R.color.light_mode_icon_color_single_tone); + } mTransitionsController = lightBarTransitionsControllerFactory.create(this); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt index a608be39f7f7..8f3b0e788dae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository +import android.telephony.CellSignalStrength import android.telephony.SubscriptionInfo import android.telephony.TelephonyManager import com.android.systemui.log.table.TableLogBuffer @@ -149,6 +150,6 @@ interface MobileConnectionRepository { companion object { /** The default number of levels to use for [numberOfLevels]. */ - const val DEFAULT_NUM_LEVELS = 4 + val DEFAULT_NUM_LEVELS = CellSignalStrength.getNumSignalStrengthLevels() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt index 6de7a00b5a10..3cb138bd75a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt @@ -25,6 +25,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionS import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_CARRIER_ID import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_CARRIER_NETWORK_CHANGE @@ -223,6 +224,9 @@ class DemoMobileConnectionRepository( _carrierId.value = event.carrierId ?: INVALID_SUBSCRIPTION_ID + numberOfLevels.value = + if (event.inflateStrength) DEFAULT_NUM_LEVELS + 1 else DEFAULT_NUM_LEVELS + cdmaRoaming.value = event.roaming _isRoaming.value = event.roaming // TODO(b/261029387): not yet supported diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt index 11a61a9fd319..dbfc576496b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt @@ -71,7 +71,7 @@ constructor( val dataType = getString("datatype")?.toDataType() val slot = getString("slot")?.toInt() val carrierId = getString("carrierid")?.toInt() - val inflateStrength = getString("inflate")?.toBoolean() + val inflateStrength = getString("inflate").toBoolean() val activity = getString("activity")?.toActivity() val carrierNetworkChange = getString("carriernetworkchange") == "show" val roaming = getString("roam") == "show" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt index 4836abe73f86..42171d0dc2b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt @@ -31,7 +31,7 @@ sealed interface FakeNetworkEventModel { // Null means the default (chosen by the repository) val subId: Int?, val carrierId: Int?, - val inflateStrength: Boolean?, + val inflateStrength: Boolean = false, @DataActivityType val activity: Int?, val carrierNetworkChange: Boolean, val roaming: Boolean, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt index 5e38715fefa2..adcf736bba01 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt @@ -295,12 +295,7 @@ class MobileIconInteractorImpl( } .stateIn(scope, SharingStarted.WhileSubscribed(), 0) - private val numberOfLevels: StateFlow<Int> = - connectionRepository.numberOfLevels.stateIn( - scope, - SharingStarted.WhileSubscribed(), - connectionRepository.numberOfLevels.value, - ) + private val numberOfLevels: StateFlow<Int> = connectionRepository.numberOfLevels override val isDataConnected: StateFlow<Boolean> = connectionRepository.dataConnectionState diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt index 900a92052153..fd5ab135a1ad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt @@ -19,6 +19,8 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.view import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater +import android.widget.ImageView +import com.android.settingslib.flags.Flags.newStatusBarIcons import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarIconView.getVisibleStateString import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger @@ -59,6 +61,18 @@ class ModernStatusBarMobileView( .inflate(R.layout.status_bar_mobile_signal_group_new, null) as ModernStatusBarMobileView) .also { + // Flag-specific configuration + if (newStatusBarIcons()) { + // New icon (with no embedded whitespace) is slightly shorter + // (but actually taller) + val iconView = it.requireViewById<ImageView>(R.id.mobile_signal) + val lp = iconView.layoutParams + lp.height = + context.resources.getDimensionPixelSize( + R.dimen.status_bar_mobile_signal_size_updated + ) + } + it.subId = viewModel.subscriptionId it.initView(slot) { MobileIconBinder.bind(view = it, viewModel = viewModel, logger = logger) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt index 4cd348412f63..ba55aadace0a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt @@ -20,6 +20,9 @@ import android.annotation.SuppressLint import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.ImageView +import com.android.settingslib.flags.Flags.newStatusBarIcons import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView @@ -57,7 +60,18 @@ class ModernStatusBarWifiView( ): ModernStatusBarWifiView { return (LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null) as ModernStatusBarWifiView) - .also { it.initView(slot) { WifiViewBinder.bind(it, wifiViewModel) } } + .also { + // Flag-specific configuration + if (newStatusBarIcons()) { + // The newer asset does not embed whitespace around it, and is therefore + // rectangular. Use wrap_content for the width in this case + val iconView = it.requireViewById<ImageView>(R.id.wifi_signal) + val lp = iconView.layoutParams + lp.width = ViewGroup.LayoutParams.WRAP_CONTENT + } + + it.initView(slot) { WifiViewBinder.bind(it, wifiViewModel) } + } } } } 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 f498520f219b..1a399341f12c 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java @@ -20,6 +20,7 @@ import static com.android.systemui.wallet.ui.WalletCardCarousel.CARD_ANIM_ALPHA_ import static com.android.systemui.wallet.ui.WalletCardCarousel.CARD_ANIM_ALPHA_DURATION; import android.annotation.Nullable; +import android.app.ActivityOptions; import android.app.BroadcastOptions; import android.app.PendingIntent; import android.content.Context; @@ -40,8 +41,8 @@ import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.Utils; -import com.android.systemui.res.R; import com.android.systemui.classifier.FalsingCollector; +import com.android.systemui.res.R; import java.util.List; @@ -308,7 +309,7 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard BroadcastOptions options = BroadcastOptions.makeBasic(); options.setInteractive(true); options.setPendingIntentBackgroundActivityStartMode( - BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); walletCard.getPendingIntent().send(options.toBundle()); } catch (PendingIntent.CanceledException e) { Log.w(TAG, "Error sending pending intent for wallet card."); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 30269664a559..0f8a81399be8 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -336,48 +336,6 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test - fun listenForTransitionToAodFromGone_updatesClockDozeAmountToOne() = - runBlocking(IMMEDIATE) { - val transitionStep = MutableStateFlow(TransitionStep()) - whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD)) - .thenReturn(transitionStep) - - val job = underTest.listenForAnyStateToAodTransition(this) - transitionStep.value = - TransitionStep( - from = KeyguardState.GONE, - to = KeyguardState.AOD, - transitionState = TransitionState.STARTED, - ) - yield() - - verify(animations, times(2)).doze(1f) - - job.cancel() - } - - @Test - fun listenForTransitionToAodFromLockscreen_neverUpdatesClockDozeAmount() = - runBlocking(IMMEDIATE) { - val transitionStep = MutableStateFlow(TransitionStep()) - whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD)) - .thenReturn(transitionStep) - - val job = underTest.listenForAnyStateToAodTransition(this) - transitionStep.value = - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, - transitionState = TransitionState.STARTED, - ) - yield() - - verify(animations, never()).doze(1f) - - job.cancel() - } - - @Test fun unregisterListeners_validate() = runBlocking(IMMEDIATE) { underTest.unregisterListeners() diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 538daee52377..336a97ef09f4 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -26,6 +26,8 @@ import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG; import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL; import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE; import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID; +import static android.telephony.SubscriptionManager.PROFILE_CLASS_DEFAULT; +import static android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; @@ -187,6 +189,11 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { TEST_CARRIER, TEST_CARRIER_2, NAME_SOURCE_CARRIER_ID, 0xFFFFFF, "", DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", true, TEST_GROUP_UUID, TEST_CARRIER_ID, 0); + private static final SubscriptionInfo TEST_SUBSCRIPTION_PROVISIONING = new SubscriptionInfo( + 1, "", 0, + TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_CARRIER_ID, 0xFFFFFF, "", + DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, TEST_GROUP_UUID, + TEST_CARRIER_ID, PROFILE_CLASS_PROVISIONING); private static final int FINGERPRINT_SENSOR_ID = 1; @Mock @@ -1204,7 +1211,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.mSimDatas.get(TEST_SUBSCRIPTION.getSubscriptionId())) .isNotNull(); - when(mSubscriptionManager.getCompleteActiveSubscriptionInfoList()).thenReturn(null); + when(mSubscriptionManager.getCompleteActiveSubscriptionInfoList()) + .thenReturn(new ArrayList<>()); mKeyguardUpdateMonitor.mPhoneStateListener.onActiveDataSubscriptionIdChanged( SubscriptionManager.INVALID_SUBSCRIPTION_ID); mTestableLooper.processAllMessages(); @@ -1216,6 +1224,37 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void testActiveSubscriptionList_filtersProvisioningNetworks() { + List<SubscriptionInfo> list = new ArrayList<>(); + list.add(TEST_SUBSCRIPTION_PROVISIONING); + when(mSubscriptionManager.getCompleteActiveSubscriptionInfoList()).thenReturn(list); + mKeyguardUpdateMonitor.mSubscriptionListener.onSubscriptionsChanged(); + + assertThat(mKeyguardUpdateMonitor.getSubscriptionInfo(true)).isEmpty(); + + SubscriptionInfo.Builder b = new SubscriptionInfo.Builder(TEST_SUBSCRIPTION_PROVISIONING); + b.setProfileClass(PROFILE_CLASS_DEFAULT); + SubscriptionInfo validInfo = b.build(); + + list.clear(); + list.add(validInfo); + mKeyguardUpdateMonitor.mSubscriptionListener.onSubscriptionsChanged(); + + assertThat(mKeyguardUpdateMonitor.getSubscriptionInfo(true)).hasSize(1); + } + + @Test + public void testActiveSubscriptionList_filtersProvisioningNetworks_untilValid() { + List<SubscriptionInfo> list = new ArrayList<>(); + list.add(TEST_SUBSCRIPTION_PROVISIONING); + when(mSubscriptionManager.getCompleteActiveSubscriptionInfoList()).thenReturn(list); + mKeyguardUpdateMonitor.mSubscriptionListener.onSubscriptionsChanged(); + + assertThat(mKeyguardUpdateMonitor.getSubscriptionInfo(true)).isEmpty(); + + } + + @Test public void testIsUserUnlocked() { // mUserManager will report the user as unlocked on @Before assertThat( diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt index 4ab7ab450fae..043dcaa0d919 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt @@ -15,10 +15,13 @@ */ package com.android.systemui.battery +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.widget.ImageView import androidx.test.filters.SmallTest +import com.android.settingslib.flags.Flags.FLAG_NEW_STATUS_BAR_ICONS import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.battery.BatteryMeterView.BatteryEstimateFetcher @@ -141,7 +144,8 @@ class BatteryMeterViewTest : SysuiTestCase() { } @Test - fun changesFromEstimateToPercent_textAndContentDescriptionChanges() { + @DisableFlags(FLAG_NEW_STATUS_BAR_ICONS) + fun changesFromEstimateToPercent_textAndContentDescriptionChanges_flagOff() { mBatteryMeterView.onBatteryLevelChanged(15, false) mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE) mBatteryMeterView.setBatteryEstimateFetcher(Fetcher()) @@ -164,6 +168,31 @@ class BatteryMeterViewTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS) + fun changesFromEstimateToPercent_textAndContentDescriptionChanges_flagOn() { + mBatteryMeterView.onBatteryLevelChanged(15, false) + mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE) + mBatteryMeterView.setBatteryEstimateFetcher(Fetcher()) + + mBatteryMeterView.updatePercentText() + + assertThat(mBatteryMeterView.contentDescription).isEqualTo( + context.getString( + R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE + ) + ) + + // Update the show mode from estimate to percent + mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON) + + assertThat(mBatteryMeterView.batteryPercentView).isNull() + assertThat(mBatteryMeterView.contentDescription).isEqualTo( + context.getString(R.string.accessibility_battery_level, 15) + ) + assertThat(mBatteryMeterView.unifiedBatteryState.showPercent).isTrue() + } + + @Test fun contentDescription_manyUpdates_alwaysUpdated() { // BatteryDefender mBatteryMeterView.onBatteryLevelChanged(90, false) @@ -208,7 +237,8 @@ class BatteryMeterViewTest : SysuiTestCase() { } @Test - fun isBatteryDefenderChanged_true_drawableGetsTrue() { + @DisableFlags(FLAG_NEW_STATUS_BAR_ICONS) + fun isBatteryDefenderChanged_true_drawableGetsTrue_flagOff() { mBatteryMeterView.setDisplayShieldEnabled(true) val drawable = getBatteryDrawable() @@ -218,7 +248,18 @@ class BatteryMeterViewTest : SysuiTestCase() { } @Test - fun isBatteryDefenderChanged_false_drawableGetsFalse() { + @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS) + fun isBatteryDefenderChanged_true_drawableGetsTrue_flagOn() { + mBatteryMeterView.setDisplayShieldEnabled(true) + + mBatteryMeterView.onIsBatteryDefenderChanged(true) + + assertThat(mBatteryMeterView.unifiedBatteryState.attribution).isNotNull() + } + + @Test + @DisableFlags(FLAG_NEW_STATUS_BAR_ICONS) + fun isBatteryDefenderChanged_false_drawableGetsFalse_flagOff() { mBatteryMeterView.setDisplayShieldEnabled(true) val drawable = getBatteryDrawable() @@ -232,7 +273,22 @@ class BatteryMeterViewTest : SysuiTestCase() { } @Test - fun isBatteryDefenderChanged_true_featureflagOff_drawableGetsFalse() { + @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS) + fun isBatteryDefenderChanged_false_drawableGetsFalse_flagOn() { + mBatteryMeterView.setDisplayShieldEnabled(true) + + // Start as true + mBatteryMeterView.onIsBatteryDefenderChanged(true) + + // Update to false + mBatteryMeterView.onIsBatteryDefenderChanged(false) + + assertThat(mBatteryMeterView.unifiedBatteryState.attribution).isNull() + } + + @Test + @DisableFlags(FLAG_NEW_STATUS_BAR_ICONS) + fun isBatteryDefenderChanged_true_featureflagOff_drawableGetsFalse_flagOff() { mBatteryMeterView.setDisplayShieldEnabled(false) val drawable = getBatteryDrawable() @@ -242,17 +298,41 @@ class BatteryMeterViewTest : SysuiTestCase() { } @Test - fun isIncompatibleChargingChanged_true_drawableGetsChargingFalse() { + @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS) + fun isBatteryDefenderChanged_true_featureflagOff_drawableGetsFalse_flagOn() { + mBatteryMeterView.setDisplayShieldEnabled(false) + + mBatteryMeterView.onIsBatteryDefenderChanged(true) + + assertThat(mBatteryMeterView.unifiedBatteryState.attribution).isNull() + } + + @Test + @DisableFlags(FLAG_NEW_STATUS_BAR_ICONS) + fun isIncompatibleChargingChanged_true_drawableGetsChargingFalse_flagOff() { mBatteryMeterView.onBatteryLevelChanged(45, true) val drawable = getBatteryDrawable() mBatteryMeterView.onIsIncompatibleChargingChanged(true) assertThat(drawable.getCharging()).isFalse() + assertThat(mBatteryMeterView.isCharging).isFalse() } @Test - fun isIncompatibleChargingChanged_false_drawableGetsChargingTrue() { + @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS) + fun isIncompatibleChargingChanged_true_drawableGetsChargingFalse_flagOn() { + mBatteryMeterView.onBatteryLevelChanged(45, true) + + mBatteryMeterView.onIsIncompatibleChargingChanged(true) + + assertThat(mBatteryMeterView.unifiedBatteryState.attribution).isNull() + assertThat(mBatteryMeterView.isCharging).isFalse() + } + + @Test + @DisableFlags(FLAG_NEW_STATUS_BAR_ICONS) + fun isIncompatibleChargingChanged_false_drawableGetsChargingTrue_flagOff() { mBatteryMeterView.onBatteryLevelChanged(45, true) val drawable = getBatteryDrawable() @@ -261,6 +341,17 @@ class BatteryMeterViewTest : SysuiTestCase() { assertThat(drawable.getCharging()).isTrue() } + @Test + @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS) + fun isIncompatibleChargingChanged_false_drawableGetsChargingTrue_flagOn() { + mBatteryMeterView.onBatteryLevelChanged(45, true) + + mBatteryMeterView.onIsIncompatibleChargingChanged(false) + + assertThat(mBatteryMeterView.isCharging).isTrue() + assertThat(mBatteryMeterView.unifiedBatteryState.attribution).isNotNull() + } + private fun getBatteryDrawable(): AccessorizedBatteryDrawable { return (mBatteryMeterView.getChildAt(0) as ImageView) .drawable as AccessorizedBatteryDrawable diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt index fbb77cdc3049..25dd9fedba7c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt @@ -34,7 +34,6 @@ import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import java.util.concurrent.CompletableFuture -import java.util.function.Supplier import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Before @@ -46,8 +45,6 @@ class SaveImageInBackgroundTaskTest : SysuiTestCase() { private val smartActions = mock<ScreenshotSmartActions>() private val smartActionsProvider = mock<ScreenshotNotificationSmartActionsProvider>() private val saveImageData = SaveImageInBackgroundData() - private val sharedTransitionSupplier = - mock<Supplier<ScreenshotController.SavedImageData.ActionTransition>>() private val testScreenshotId: String = "testScreenshotId" private val testBitmap = mock<Bitmap>() private val testUser = UserHandle.getUserHandleForUid(0) @@ -88,7 +85,6 @@ class SaveImageInBackgroundTaskTest : SysuiTestCase() { imageExporter, smartActions, saveImageData, - sharedTransitionSupplier, smartActionsProvider, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java index 85c8ba7e77b3..2a9aca7f5a35 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java @@ -20,8 +20,6 @@ import static com.android.systemui.screenshot.ScreenshotNotificationSmartActions import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.doThrow; @@ -32,19 +30,15 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Notification; -import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; -import android.os.Bundle; import android.os.Handler; -import android.os.Looper; import android.os.UserHandle; import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition; import org.junit.Before; import org.junit.Test; @@ -84,7 +78,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { ScreenshotNotificationSmartActionsProvider smartActionsProvider = mock( ScreenshotNotificationSmartActionsProvider.class); when(smartActionsProvider.getActions(any(), any(), any(), any(), any(), any())) - .thenThrow(RuntimeException.class); + .thenThrow(RuntimeException.class); CompletableFuture<List<Notification.Action>> smartActionsFuture = mScreenshotSmartActions.getSmartActionsFuture( "", Uri.parse("content://authority/data"), bitmap, smartActionsProvider, @@ -166,89 +160,4 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS); assertEquals(smartActions.size(), 0); } - - // Tests for share action extras - @Test - public void testShareActionExtras() { - if (Looper.myLooper() == null) { - Looper.prepare(); - } - - ScreenshotController.SaveImageInBackgroundData - data = new ScreenshotController.SaveImageInBackgroundData(); - data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); - data.finisher = null; - data.mActionsReadyListener = null; - SaveImageInBackgroundTask task = - new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data, - ActionTransition::new, mSmartActionsProvider); - - Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(), - Uri.parse("Screenshot_123.png"), true).get().action; - - Intent intent = shareAction.actionIntent.getIntent(); - assertNotNull(intent); - Bundle bundle = intent.getExtras(); - assertTrue(bundle.containsKey(ScreenshotController.EXTRA_ID)); - assertTrue(bundle.containsKey(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED)); - assertEquals(ScreenshotController.ACTION_TYPE_SHARE, shareAction.title); - assertEquals(Intent.ACTION_SEND, intent.getAction()); - } - - // Tests for edit action extras - @Test - public void testEditActionExtras() { - if (Looper.myLooper() == null) { - Looper.prepare(); - } - - ScreenshotController.SaveImageInBackgroundData - data = new ScreenshotController.SaveImageInBackgroundData(); - data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); - data.finisher = null; - data.mActionsReadyListener = null; - SaveImageInBackgroundTask task = - new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data, - ActionTransition::new, mSmartActionsProvider); - - Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(), - Uri.parse("Screenshot_123.png"), true).get().action; - - Intent intent = editAction.actionIntent.getIntent(); - assertNotNull(intent); - Bundle bundle = intent.getExtras(); - assertTrue(bundle.containsKey(ScreenshotController.EXTRA_ID)); - assertTrue(bundle.containsKey(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED)); - assertEquals(ScreenshotController.ACTION_TYPE_EDIT, editAction.title); - assertEquals(Intent.ACTION_EDIT, intent.getAction()); - } - - // Tests for share action extras - @Test - public void testDeleteActionExtras() { - if (Looper.myLooper() == null) { - Looper.prepare(); - } - - ScreenshotController.SaveImageInBackgroundData - data = new ScreenshotController.SaveImageInBackgroundData(); - data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); - data.finisher = null; - data.mActionsReadyListener = null; - SaveImageInBackgroundTask task = - new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data, - ActionTransition::new, mSmartActionsProvider); - - Notification.Action deleteAction = task.createDeleteAction(mContext, - mContext.getResources(), - Uri.parse("Screenshot_123.png"), true); - - Intent intent = deleteAction.actionIntent.getIntent(); - assertNotNull(intent); - Bundle bundle = intent.getExtras(); - assertTrue(bundle.containsKey(ScreenshotController.EXTRA_ID)); - assertTrue(bundle.containsKey(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED)); - assertEquals(deleteAction.title, ScreenshotController.ACTION_TYPE_DELETE); - assertNull(intent.getAction()); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt index b958f35c0e53..a41bc0d87615 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt @@ -616,7 +616,7 @@ fun validMobileEvent( dataType: SignalIcon.MobileIconGroup? = THREE_G, subId: Int? = 1, carrierId: Int? = UNKNOWN_CARRIER_ID, - inflateStrength: Boolean? = false, + inflateStrength: Boolean = false, activity: Int? = null, carrierNetworkChange: Boolean = false, roaming: Boolean = false, diff --git a/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt index f6ddfccfa532..185deea31747 100644 --- a/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt +++ b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt @@ -20,5 +20,6 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.util.mockito.mock -var Kosmos.applicationContext: Context by Kosmos.Fixture { testCase.context } +var Kosmos.applicationContext: Context by + Kosmos.Fixture { testCase.context.apply { ensureTestableResources() } } val Kosmos.mockedContext: Context by Kosmos.Fixture { mock<Context>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt index ecf66a297f0d..8ca53e6591c0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt @@ -42,6 +42,10 @@ val Kosmos.keyguardRootViewModel by Fixture { aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel, dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel, glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel, + goneToAodTransitionViewModel = goneToAodTransitionViewModel, + goneToDozingTransitionViewModel = goneToDozingTransitionViewModel, + lockscreenToAodTransitionViewModel = lockscreenToAodTransitionViewModel, + lockscreenToDozingTransitionViewModel = lockscreenToDozingTransitionViewModel, lockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel, lockscreenToGlanceableHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel, lockscreenToGoneTransitionViewModel = lockscreenToGoneTransitionViewModel, diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java index d0eb59d83f5a..1dab40ea5876 100644 --- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java +++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java @@ -25,12 +25,12 @@ import static android.content.ComponentName.createRelative; import static android.content.pm.PackageManager.FEATURE_WATCH; import static com.android.server.companion.CompanionDeviceManagerService.DEBUG; -import static com.android.server.companion.MetricUtils.logCreateAssociation; -import static com.android.server.companion.PackageUtils.enforceUsesCompanionDeviceFeature; -import static com.android.server.companion.PermissionsUtils.enforcePermissionsForAssociation; -import static com.android.server.companion.RolesUtils.addRoleHolderForAssociation; -import static com.android.server.companion.RolesUtils.isRoleHolder; -import static com.android.server.companion.Utils.prepareForIpc; +import static com.android.server.companion.utils.MetricUtils.logCreateAssociation; +import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature; +import static com.android.server.companion.utils.PermissionsUtils.enforcePermissionForCreatingAssociation; +import static com.android.server.companion.utils.RolesUtils.addRoleHolderForAssociation; +import static com.android.server.companion.utils.RolesUtils.isRoleHolder; +import static com.android.server.companion.utils.Utils.prepareForIpc; import static java.util.Objects.requireNonNull; @@ -59,6 +59,7 @@ import android.os.UserHandle; import android.util.Slog; import com.android.internal.R; +import com.android.server.companion.utils.PackageUtils; import java.util.List; @@ -167,7 +168,7 @@ class AssociationRequestsProcessor { } // 1. Enforce permissions and other requirements. - enforcePermissionsForAssociation(mContext, request, packageUid); + enforcePermissionForCreatingAssociation(mContext, request, packageUid); enforceUsesCompanionDeviceFeature(mContext, userId, packageName); // 2a. Check if association can be created without launching UI (i.e. CDM needs NEITHER @@ -257,7 +258,7 @@ class AssociationRequestsProcessor { // 1. Need to check permissions again in case something changed, since we first received // this request. try { - enforcePermissionsForAssociation(mContext, request, packageUid); + enforcePermissionForCreatingAssociation(mContext, request, packageUid); } catch (SecurityException e) { // Since, at this point the caller is our own UI, we need to catch the exception on // forward it back to the application via the callback. @@ -316,6 +317,9 @@ class AssociationRequestsProcessor { // If it is null, then the operation will succeed without granting any role. addRoleHolderForAssociation(mService.getContext(), association, success -> { if (success) { + Slog.i(TAG, "Added " + association.getDeviceProfile() + " role to userId=" + + association.getUserId() + ", packageName=" + + association.getPackageName()); addAssociationToStore(association); sendCallbackAndFinish(association, callback, resultReceiver); } else { diff --git a/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java b/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java index de6382e316df..10963ea37e8e 100644 --- a/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java +++ b/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java @@ -20,8 +20,8 @@ import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIB import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION; import static com.android.internal.util.CollectionUtils.any; -import static com.android.server.companion.MetricUtils.logRemoveAssociation; -import static com.android.server.companion.RolesUtils.removeRoleHolderForAssociation; +import static com.android.server.companion.utils.MetricUtils.logRemoveAssociation; +import static com.android.server.companion.utils.RolesUtils.removeRoleHolderForAssociation; import static com.android.server.companion.CompanionDeviceManagerService.PerUserAssociationSet; import android.annotation.NonNull; @@ -203,7 +203,8 @@ public class AssociationRevokeProcessor { return false; } - removeRoleHolderForAssociation(mContext, association); + removeRoleHolderForAssociation(mContext, association.getUserId(), + association.getPackageName(), association.getDeviceProfile()); return true; } diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java index af0777c74605..559ebbc290f6 100644 --- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java +++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java @@ -38,6 +38,9 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.infra.PerUser; import com.android.server.companion.presence.CompanionDevicePresenceMonitor; +import com.android.server.companion.presence.ObservableUuid; +import com.android.server.companion.presence.ObservableUuidStore; +import com.android.server.companion.utils.PackageUtils; import java.io.PrintWriter; import java.util.ArrayList; @@ -61,7 +64,7 @@ import java.util.Map; * <ul> * <li> {@link #bindCompanionApplication(int, String, boolean)} * <li> {@link #unbindCompanionApplication(int, String)} - * <li> {@link #notifyCompanionApplicationDevicePresenceEvent(AssociationInfo, int)} + * <li> {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)} * <li> {@link #isCompanionApplicationBound(int, String)} * <li> {@link #isRebindingCompanionApplicationScheduled(int, String)} * </ul> @@ -251,7 +254,13 @@ public class CompanionApplicationController { serviceConnector.connect(); } - void notifyCompanionApplicationDeviceAppeared(AssociationInfo association) { + /** + * Notify the app that the device appeared. + * + * @deprecated use {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)} instead + */ + @Deprecated + public void notifyCompanionApplicationDeviceAppeared(AssociationInfo association) { final int userId = association.getUserId(); final String packageName = association.getPackageName(); @@ -273,7 +282,13 @@ public class CompanionApplicationController { primaryServiceConnector.postOnDeviceAppeared(association); } - void notifyCompanionApplicationDeviceDisappeared(AssociationInfo association) { + /** + * Notify the app that the device disappeared. + * + * @deprecated use {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)} instead + */ + @Deprecated + public void notifyCompanionApplicationDeviceDisappeared(AssociationInfo association) { final int userId = association.getUserId(); final String packageName = association.getPackageName(); @@ -295,7 +310,10 @@ public class CompanionApplicationController { primaryServiceConnector.postOnDeviceDisappeared(association); } - void notifyCompanionApplicationDevicePresenceEvent(AssociationInfo association, int event) { + /** + * Notify the app that the device appeared. + */ + public void notifyCompanionDevicePresenceEvent(AssociationInfo association, int event) { final int userId = association.getUserId(); final String packageName = association.getPackageName(); final CompanionDeviceServiceConnector primaryServiceConnector = @@ -318,7 +336,10 @@ public class CompanionApplicationController { primaryServiceConnector.postOnDevicePresenceEvent(devicePresenceEvent); } - void notifyApplicationDevicePresenceEvent(ObservableUuid uuid, int event) { + /** + * Notify the app that the device disappeared. + */ + public void notifyUuidDevicePresenceEvent(ObservableUuid uuid, int event) { final int userId = uuid.getUserId(); final ParcelUuid parcelUuid = uuid.getUuid(); final String packageName = uuid.getPackageName(); diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 09c77939eb7b..a478a3d84161 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -38,15 +38,15 @@ import static com.android.internal.util.CollectionUtils.any; import static com.android.internal.util.Preconditions.checkState; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import static com.android.server.companion.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED; -import static com.android.server.companion.PackageUtils.isRestrictedSettingsAllowed; -import static com.android.server.companion.PackageUtils.enforceUsesCompanionDeviceFeature; -import static com.android.server.companion.PackageUtils.getPackageInfo; -import static com.android.server.companion.PermissionsUtils.checkCallerCanManageCompanionDevice; -import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageAssociationsForPackage; -import static com.android.server.companion.PermissionsUtils.enforceCallerCanObservingDevicePresenceByUuid; -import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr; -import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId; -import static com.android.server.companion.PermissionsUtils.sanitizeWithCallerChecks; +import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed; +import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature; +import static com.android.server.companion.utils.PackageUtils.getPackageInfo; +import static com.android.server.companion.utils.PermissionsUtils.checkCallerCanManageCompanionDevice; +import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage; +import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanObservingDevicePresenceByUuid; +import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOr; +import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId; +import static com.android.server.companion.utils.PermissionsUtils.sanitizeWithCallerChecks; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.DAYS; @@ -123,6 +123,8 @@ import com.android.server.companion.datatransfer.contextsync.CrossDeviceCall; import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController; import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncControllerCallback; import com.android.server.companion.presence.CompanionDevicePresenceMonitor; +import com.android.server.companion.presence.ObservableUuid; +import com.android.server.companion.presence.ObservableUuidStore; import com.android.server.companion.transport.CompanionTransportManager; import com.android.server.pm.UserManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; @@ -435,7 +437,7 @@ public class CompanionDeviceManagerService extends SystemService { bindApplicationIfNeeded(association); - mCompanionAppController.notifyCompanionApplicationDevicePresenceEvent( + mCompanionAppController.notifyCompanionDevicePresenceEvent( association, event); break; case EVENT_BLE_DISAPPEARED: @@ -446,7 +448,7 @@ public class CompanionDeviceManagerService extends SystemService { return; } if (association.shouldBindWhenPresent()) { - mCompanionAppController.notifyCompanionApplicationDevicePresenceEvent( + mCompanionAppController.notifyCompanionDevicePresenceEvent( association, event); } // Check if there are other devices associated to the app that are present. @@ -475,7 +477,7 @@ public class CompanionDeviceManagerService extends SystemService { Log.i(TAG, "u" + userId + "\\" + packageName + " is already bound"); } - mCompanionAppController.notifyApplicationDevicePresenceEvent(uuid, event); + mCompanionAppController.notifyUuidDevicePresenceEvent(uuid, event); break; case EVENT_BT_DISCONNECTED: @@ -484,7 +486,7 @@ public class CompanionDeviceManagerService extends SystemService { return; } - mCompanionAppController.notifyApplicationDevicePresenceEvent(uuid, event); + mCompanionAppController.notifyUuidDevicePresenceEvent(uuid, event); // Check if there are other devices associated to the app or the UUID to be // observed are present. if (shouldBindPackage(userId, packageName)) return; diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index de4f2b60170f..74b4cabbab67 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -18,7 +18,7 @@ package com.android.server.companion; import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC; -import static com.android.server.companion.PermissionsUtils.sanitizeWithCallerChecks; +import static com.android.server.companion.utils.PermissionsUtils.sanitizeWithCallerChecks; import android.companion.AssociationInfo; import android.companion.ContextSyncMessage; @@ -36,6 +36,7 @@ import com.android.server.companion.datatransfer.SystemDataTransferProcessor; import com.android.server.companion.datatransfer.contextsync.BitmapUtils; import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController; import com.android.server.companion.presence.CompanionDevicePresenceMonitor; +import com.android.server.companion.presence.ObservableUuid; import com.android.server.companion.transport.CompanionTransportManager; import java.io.PrintWriter; diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java index 1ebe65c6aa5f..7527efb7b19a 100644 --- a/services/companion/java/com/android/server/companion/PersistentDataStore.java +++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java @@ -27,11 +27,11 @@ import static com.android.internal.util.XmlUtils.writeLongAttribute; import static com.android.internal.util.XmlUtils.writeStringAttribute; import static com.android.server.companion.CompanionDeviceManagerService.getFirstAssociationIdForUser; import static com.android.server.companion.CompanionDeviceManagerService.getLastAssociationIdForUser; -import static com.android.server.companion.DataStoreUtils.createStorageFileForUser; -import static com.android.server.companion.DataStoreUtils.fileToByteArray; -import static com.android.server.companion.DataStoreUtils.isEndOfTag; -import static com.android.server.companion.DataStoreUtils.isStartOfTag; -import static com.android.server.companion.DataStoreUtils.writeToFileSafely; +import static com.android.server.companion.utils.DataStoreUtils.createStorageFileForUser; +import static com.android.server.companion.utils.DataStoreUtils.fileToByteArray; +import static com.android.server.companion.utils.DataStoreUtils.isEndOfTag; +import static com.android.server.companion.utils.DataStoreUtils.isStartOfTag; +import static com.android.server.companion.utils.DataStoreUtils.writeToFileSafely; import android.annotation.NonNull; import android.annotation.Nullable; @@ -51,6 +51,7 @@ import android.util.Xml; import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; +import com.android.server.companion.utils.DataStoreUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java index 260b21f109d0..74236a402244 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java +++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java @@ -23,7 +23,7 @@ import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PERMISSIO import static android.content.ComponentName.createRelative; import static android.content.pm.PackageManager.FEATURE_WATCH; -import static com.android.server.companion.Utils.prepareForIpc; +import static com.android.server.companion.utils.Utils.prepareForIpc; import android.annotation.NonNull; import android.annotation.Nullable; @@ -54,9 +54,9 @@ import android.util.Slog; import com.android.internal.R; import com.android.server.companion.AssociationStore; import com.android.server.companion.CompanionDeviceManagerService; -import com.android.server.companion.PackageUtils; -import com.android.server.companion.PermissionsUtils; import com.android.server.companion.transport.CompanionTransportManager; +import com.android.server.companion.utils.PackageUtils; +import com.android.server.companion.utils.PermissionsUtils; import java.util.List; import java.util.concurrent.ExecutorService; diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java index c4c80f907b3a..ee816a0c2cc9 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java +++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java @@ -22,11 +22,11 @@ import static com.android.internal.util.XmlUtils.readBooleanAttribute; import static com.android.internal.util.XmlUtils.readIntAttribute; import static com.android.internal.util.XmlUtils.writeBooleanAttribute; import static com.android.internal.util.XmlUtils.writeIntAttribute; -import static com.android.server.companion.DataStoreUtils.createStorageFileForUser; -import static com.android.server.companion.DataStoreUtils.fileToByteArray; -import static com.android.server.companion.DataStoreUtils.isEndOfTag; -import static com.android.server.companion.DataStoreUtils.isStartOfTag; -import static com.android.server.companion.DataStoreUtils.writeToFileSafely; +import static com.android.server.companion.utils.DataStoreUtils.createStorageFileForUser; +import static com.android.server.companion.utils.DataStoreUtils.fileToByteArray; +import static com.android.server.companion.utils.DataStoreUtils.isEndOfTag; +import static com.android.server.companion.utils.DataStoreUtils.isStartOfTag; +import static com.android.server.companion.utils.DataStoreUtils.writeToFileSafely; import android.annotation.NonNull; import android.annotation.Nullable; 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 7e3079073c90..679243496e95 100644 --- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java +++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java @@ -34,7 +34,7 @@ import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_MATCH_LOST; import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER; import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG; -import static com.android.server.companion.presence.Utils.btDeviceToString; +import static com.android.server.companion.utils.Utils.btDeviceToString; import static java.util.Objects.requireNonNull; diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java index c514f3ef29d0..0287f6258c06 100644 --- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java +++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java @@ -20,7 +20,7 @@ import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED; import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED; import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG; -import static com.android.server.companion.presence.Utils.btDeviceToString; +import static com.android.server.companion.utils.Utils.btDeviceToString; import android.annotation.NonNull; import android.annotation.SuppressLint; @@ -40,8 +40,6 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import com.android.server.companion.AssociationStore; -import com.android.server.companion.ObservableUuid; -import com.android.server.companion.ObservableUuidStore; import java.util.Arrays; import java.util.Collections; diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java index 54a4692d964d..caca48d3aa02 100644 --- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java +++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java @@ -43,8 +43,6 @@ import android.util.Slog; import android.util.SparseArray; import com.android.server.companion.AssociationStore; -import com.android.server.companion.ObservableUuid; -import com.android.server.companion.ObservableUuidStore; import java.io.PrintWriter; import java.util.HashSet; diff --git a/services/companion/java/com/android/server/companion/ObservableUuid.java b/services/companion/java/com/android/server/companion/presence/ObservableUuid.java index 6ab3188c8fd2..9cfa2705cb2f 100644 --- a/services/companion/java/com/android/server/companion/ObservableUuid.java +++ b/services/companion/java/com/android/server/companion/presence/ObservableUuid.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.companion; +package com.android.server.companion.presence; import android.annotation.NonNull; import android.annotation.UserIdInt; diff --git a/services/companion/java/com/android/server/companion/ObservableUuidStore.java b/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java index 94be22afd9e2..ee8b1065b42c 100644 --- a/services/companion/java/com/android/server/companion/ObservableUuidStore.java +++ b/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.companion; +package com.android.server.companion.presence; import static com.android.internal.util.XmlUtils.readIntAttribute; import static com.android.internal.util.XmlUtils.readLongAttribute; @@ -22,10 +22,10 @@ import static com.android.internal.util.XmlUtils.readStringAttribute; import static com.android.internal.util.XmlUtils.writeIntAttribute; import static com.android.internal.util.XmlUtils.writeLongAttribute; import static com.android.internal.util.XmlUtils.writeStringAttribute; -import static com.android.server.companion.DataStoreUtils.createStorageFileForUser; -import static com.android.server.companion.DataStoreUtils.isEndOfTag; -import static com.android.server.companion.DataStoreUtils.isStartOfTag; -import static com.android.server.companion.DataStoreUtils.writeToFileSafely; +import static com.android.server.companion.utils.DataStoreUtils.createStorageFileForUser; +import static com.android.server.companion.utils.DataStoreUtils.isEndOfTag; +import static com.android.server.companion.utils.DataStoreUtils.isStartOfTag; +import static com.android.server.companion.utils.DataStoreUtils.writeToFileSafely; import android.annotation.NonNull; import android.annotation.Nullable; @@ -57,6 +57,9 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +/** + * This store manages the cache and disk data for observable uuids. + */ public class ObservableUuidStore { private static final String TAG = "CDM_ObservableUuidStore"; private static final String FILE_NAME = "observing_uuids_presence.xml"; @@ -84,9 +87,9 @@ public class ObservableUuidStore { } /** - * Remove the observable uuid from the disk. + * Remove the observable uuid. */ - void removeObservableUuid(@UserIdInt int userId, ParcelUuid uuid, String packageName) { + public void removeObservableUuid(@UserIdInt int userId, ParcelUuid uuid, String packageName) { List<ObservableUuid> cachedObservableUuids; synchronized (mLock) { @@ -101,7 +104,10 @@ public class ObservableUuidStore { mExecutor.execute(() -> writeObservableUuidToStore(userId, cachedObservableUuids)); } - void writeObservableUuid(@UserIdInt int userId, ObservableUuid uuid) { + /** + * Write the observable uuid. + */ + public void writeObservableUuid(@UserIdInt int userId, ObservableUuid uuid) { Slog.i(TAG, "Writing uuid=" + uuid.getUuid() + " to store."); List<ObservableUuid> cachedObservableUuids; diff --git a/services/companion/java/com/android/server/companion/presence/Utils.java b/services/companion/java/com/android/server/companion/presence/Utils.java deleted file mode 100644 index 583b443c8cb7..000000000000 --- a/services/companion/java/com/android/server/companion/presence/Utils.java +++ /dev/null @@ -1,49 +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.server.companion.presence; - -import android.annotation.NonNull; -import android.bluetooth.BluetoothDevice; - -/** Utilities for working with Bluetooth and BLE devices. */ -class Utils { - - /** - * @return short String representation of {@link BluetoothDevice}. - */ - static String btDeviceToString(@NonNull BluetoothDevice btDevice) { - final StringBuilder sb = new StringBuilder(btDevice.getAddress()); - - sb.append(" [name="); - final String name = btDevice.getName(); - if (name != null) { - sb.append('\'').append(name).append('\''); - } else { - sb.append("null"); - } - - final String alias = btDevice.getAlias(); - if (alias != null) { - sb.append(", alias='").append(alias).append("'"); - } - - return sb.append(']').toString(); - } - - private Utils() { - } -} diff --git a/services/companion/java/com/android/server/companion/DataStoreUtils.java b/services/companion/java/com/android/server/companion/utils/DataStoreUtils.java index 04ce1f673124..c75b1a57206e 100644 --- a/services/companion/java/com/android/server/companion/DataStoreUtils.java +++ b/services/companion/java/com/android/server/companion/utils/DataStoreUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.companion; +package com.android.server.companion.utils; import static org.xmlpull.v1.XmlPullParser.END_TAG; import static org.xmlpull.v1.XmlPullParser.START_TAG; @@ -80,7 +80,7 @@ public final class DataStoreUtils { /** * Writing to file could fail, for example, if the user has been recently removed and so was - * their DE (/data/system_de/<user-id>/) directory. + * their DE (/data/system_de/[user-id]/) directory. */ public static void writeToFileSafely( @NonNull AtomicFile file, @NonNull ThrowingConsumer<FileOutputStream> consumer) { diff --git a/services/companion/java/com/android/server/companion/MetricUtils.java b/services/companion/java/com/android/server/companion/utils/MetricUtils.java index cf867b67cbca..8ea5c89116eb 100644 --- a/services/companion/java/com/android/server/companion/MetricUtils.java +++ b/services/companion/java/com/android/server/companion/utils/MetricUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.companion; +package com.android.server.companion.utils; import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING; import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION; @@ -41,7 +41,7 @@ import android.util.ArrayMap; import java.util.Map; -final class MetricUtils { +public final class MetricUtils { private static final Map<String, Integer> METRIC_DEVICE_PROFILE; static { @@ -75,13 +75,19 @@ final class MetricUtils { METRIC_DEVICE_PROFILE = unmodifiableMap(map); } - static void logCreateAssociation(String profile) { + /** + * Log association creation + */ + public static void logCreateAssociation(String profile) { write(CDM_ASSOCIATION_ACTION, CDM_ASSOCIATION_ACTION__ACTION__CREATED, METRIC_DEVICE_PROFILE.get(profile)); } - static void logRemoveAssociation(String profile) { + /** + * Log association removal + */ + public static void logRemoveAssociation(String profile) { write(CDM_ASSOCIATION_ACTION, CDM_ASSOCIATION_ACTION__ACTION__REMOVED, METRIC_DEVICE_PROFILE.get(profile)); diff --git a/services/companion/java/com/android/server/companion/PackageUtils.java b/services/companion/java/com/android/server/companion/utils/PackageUtils.java index 3aae1ec99f55..d38590e0a251 100644 --- a/services/companion/java/com/android/server/companion/PackageUtils.java +++ b/services/companion/java/com/android/server/companion/utils/PackageUtils.java @@ -14,16 +14,13 @@ * limitations under the License. */ -package com.android.server.companion; +package com.android.server.companion.utils; import static android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP; import static android.content.pm.PackageManager.GET_CONFIGURATIONS; import static android.content.pm.PackageManager.GET_PERMISSIONS; import static android.os.Binder.getCallingUid; -import static com.android.server.companion.CompanionDeviceManagerService.DEBUG; -import static com.android.server.companion.CompanionDeviceManagerService.TAG; - import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -44,13 +41,11 @@ import android.content.pm.ServiceInfo; import android.content.pm.Signature; import android.os.Binder; import android.os.Process; -import android.util.Log; import android.util.Slog; import com.android.internal.util.ArrayUtils; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -58,16 +53,22 @@ import java.util.Map; import java.util.Set; /** - * Utility methods for working with {@link PackageInfo}-s. + * Utility methods for working with {@link PackageInfo}. */ public final class PackageUtils { + + private static final String TAG = "CDM_PackageUtils"; + private static final Intent COMPANION_SERVICE_INTENT = new Intent(CompanionDeviceService.SERVICE_INTERFACE); private static final String PROPERTY_PRIMARY_TAG = "android.companion.PROPERTY_PRIMARY_COMPANION_DEVICE_SERVICE"; + /** + * Get package info + */ @Nullable - static PackageInfo getPackageInfo(@NonNull Context context, + public static PackageInfo getPackageInfo(@NonNull Context context, @UserIdInt int userId, @NonNull String packageName) { final PackageManager pm = context.getPackageManager(); final PackageInfoFlags flags = PackageInfoFlags.of(GET_PERMISSIONS | GET_CONFIGURATIONS); @@ -81,26 +82,32 @@ public final class PackageUtils { }); } - static void enforceUsesCompanionDeviceFeature(@NonNull Context context, + /** + * Require the app to declare the companion device feature. + */ + public static void enforceUsesCompanionDeviceFeature(@NonNull Context context, @UserIdInt int userId, @NonNull String packageName) { // Allow system server to create CDM associations without FEATURE_COMPANION_DEVICE_SETUP if (getCallingUid() == Process.SYSTEM_UID) { return; } - String requiredFeature = FEATURE_COMPANION_DEVICE_SETUP; + PackageInfo packageInfo = getPackageInfo(context, userId, packageName); + if (packageInfo == null) { + throw new IllegalArgumentException("Package " + packageName + " doesn't exist."); + } - FeatureInfo[] requestedFeatures = getPackageInfo(context, userId, packageName).reqFeatures; + FeatureInfo[] requestedFeatures = packageInfo.reqFeatures; if (requestedFeatures != null) { - for (int i = 0; i < requestedFeatures.length; i++) { - if (requiredFeature.equals(requestedFeatures[i].name)) { + for (FeatureInfo requestedFeature : requestedFeatures) { + if (FEATURE_COMPANION_DEVICE_SETUP.equals(requestedFeature.name)) { return; } } } throw new IllegalStateException("Must declare uses-feature " - + requiredFeature + + FEATURE_COMPANION_DEVICE_SETUP + " in manifest to use this API"); } @@ -109,7 +116,7 @@ public final class PackageUtils { * Services marked as "primary" would always appear at the head of the lists, *before* * all non-primary services. */ - static @NonNull Map<String, List<ComponentName>> getCompanionServicesForUser( + public static @NonNull Map<String, List<ComponentName>> getCompanionServicesForUser( @NonNull Context context, @UserIdInt int userId) { final PackageManager pm = context.getPackageManager(); final List<ResolveInfo> companionServices = pm.queryIntentServicesAsUser( @@ -179,9 +186,7 @@ public final class PackageUtils { final String[] allowlistedPackages = context.getResources() .getStringArray(com.android.internal.R.array.config_companionDevicePackages); if (!ArrayUtils.contains(allowlistedPackages, packageName)) { - if (DEBUG) { - Log.d(TAG, packageName + " is not allowlisted."); - } + Slog.d(TAG, packageName + " is not allowlisted."); return false; } @@ -212,13 +217,6 @@ public final class PackageUtils { if (!requestingPackageSignatureAllowlisted) { Slog.w(TAG, "Certificate mismatch for allowlisted package " + packageName); - if (DEBUG) { - Log.d(TAG, " > allowlisted signatures for " + packageName + ": [" - + String.join(", ", allowlistedSignatureDigestsForRequestingPackage) - + "]"); - Log.d(TAG, " > actual signatures for " + packageName + ": " - + Arrays.toString(requestingPackageSignatureDigests)); - } } return requestingPackageSignatureAllowlisted; diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java index 15bebbae05b1..2cf1f462a7d1 100644 --- a/services/companion/java/com/android/server/companion/PermissionsUtils.java +++ b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.companion; +package com.android.server.companion.utils; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.MANAGE_COMPANION_DEVICES; @@ -75,16 +75,22 @@ public final class PermissionsUtils { DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map); } - static void enforcePermissionsForAssociation(@NonNull Context context, + /** + * Require the app to declare necessary permission for creating association. + */ + public static void enforcePermissionForCreatingAssociation(@NonNull Context context, @NonNull AssociationRequest request, int packageUid) { - enforceRequestDeviceProfilePermissions(context, request.getDeviceProfile(), packageUid); + enforcePermissionForRequestingProfile(context, request.getDeviceProfile(), packageUid); if (request.isSelfManaged()) { - enforceRequestSelfManagedPermission(context, packageUid); + enforcePermissionForRequestingSelfManaged(context, packageUid); } } - static void enforceRequestDeviceProfilePermissions( + /** + * Require the app to declare necessary permission for creating association with profile. + */ + public static void enforcePermissionForRequestingProfile( @NonNull Context context, @Nullable String deviceProfile, int packageUid) { // Device profile can be null. if (deviceProfile == null) return; @@ -101,7 +107,11 @@ public final class PermissionsUtils { } } - static void enforceRequestSelfManagedPermission(@NonNull Context context, int packageUid) { + /** + * Require the app to declare necessary permission for creating self-managed association. + */ + public static void enforcePermissionForRequestingSelfManaged(@NonNull Context context, + int packageUid) { if (context.checkPermission(REQUEST_COMPANION_SELF_MANAGED, getCallingPid(), packageUid) != PERMISSION_GRANTED) { throw new SecurityException("Application does not hold " @@ -109,25 +119,39 @@ public final class PermissionsUtils { } } - static boolean checkCallerCanInteractWithUserId(@NonNull Context context, int userId) { + /** + * Check if the caller can interact with the user. + */ + public static boolean checkCallerCanInteractWithUserId(@NonNull Context context, int userId) { if (getCallingUserId() == userId) return true; return context.checkCallingPermission(INTERACT_ACROSS_USERS) == PERMISSION_GRANTED; } - static void enforceCallerCanInteractWithUserId(@NonNull Context context, int userId) { + /** + * Require the caller to be able to interact with the user. + */ + public static void enforceCallerCanInteractWithUserId(@NonNull Context context, int userId) { if (getCallingUserId() == userId) return; context.enforceCallingPermission(INTERACT_ACROSS_USERS, null); } - static void enforceCallerIsSystemOrCanInteractWithUserId(@NonNull Context context, int userId) { + /** + * Require the caller to be system UID or to be able to interact with the user. + */ + public static void enforceCallerIsSystemOrCanInteractWithUserId(@NonNull Context context, + int userId) { if (getCallingUid() == SYSTEM_UID) return; enforceCallerCanInteractWithUserId(context, userId); } - static boolean checkCallerIsSystemOr(@UserIdInt int userId, @NonNull String packageName) { + /** + * Check if the caller is system UID or the provided user. + */ + public static boolean checkCallerIsSystemOr(@UserIdInt int userId, + @NonNull String packageName) { final int callingUid = getCallingUid(); if (callingUid == SYSTEM_UID) return true; @@ -158,13 +182,19 @@ public final class PermissionsUtils { } } - static boolean checkCallerCanManageCompanionDevice(@NonNull Context context) { + /** + * Check if the caller holds the necessary permission to manage companion devices. + */ + public static boolean checkCallerCanManageCompanionDevice(@NonNull Context context) { if (getCallingUid() == SYSTEM_UID) return true; return context.checkCallingPermission(MANAGE_COMPANION_DEVICES) == PERMISSION_GRANTED; } - static void enforceCallerCanManageAssociationsForPackage(@NonNull Context context, + /** + * Require the caller to be able to manage the associations for the package. + */ + public static void enforceCallerCanManageAssociationsForPackage(@NonNull Context context, @UserIdInt int userId, @NonNull String packageName, @Nullable String actionDescription) { if (checkCallerCanManageAssociationsForPackage(context, userId, packageName)) return; @@ -175,7 +205,10 @@ public final class PermissionsUtils { + " for u" + userId + "/" + packageName); } - static void enforceCallerCanObservingDevicePresenceByUuid(@NonNull Context context) { + /** + * Require the caller to hold necessary permission to observe device presence by UUID. + */ + public static void enforceCallerCanObservingDevicePresenceByUuid(@NonNull Context context) { if (context.checkCallingPermission(REQUEST_OBSERVE_DEVICE_UUID_PRESENCE) != PERMISSION_GRANTED) { throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have " @@ -193,7 +226,7 @@ public final class PermissionsUtils { * </ul> * @return whether the caller is one of the above. */ - static boolean checkCallerCanManageAssociationsForPackage(@NonNull Context context, + public static boolean checkCallerCanManageAssociationsForPackage(@NonNull Context context, @UserIdInt int userId, @NonNull String packageName) { if (checkCallerIsSystemOr(userId, packageName)) return true; diff --git a/services/companion/java/com/android/server/companion/RolesUtils.java b/services/companion/java/com/android/server/companion/utils/RolesUtils.java index af9d2d783100..f798e218e8e0 100644 --- a/services/companion/java/com/android/server/companion/RolesUtils.java +++ b/services/companion/java/com/android/server/companion/utils/RolesUtils.java @@ -14,13 +14,10 @@ * limitations under the License. */ -package com.android.server.companion; +package com.android.server.companion.utils; import static android.app.role.RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP; -import static com.android.server.companion.CompanionDeviceManagerService.DEBUG; -import static com.android.server.companion.CompanionDeviceManagerService.TAG; - import android.annotation.NonNull; import android.annotation.SuppressLint; import android.annotation.UserIdInt; @@ -29,7 +26,6 @@ import android.companion.AssociationInfo; import android.content.Context; import android.os.Binder; import android.os.UserHandle; -import android.util.Log; import android.util.Slog; import java.util.List; @@ -37,9 +33,14 @@ import java.util.function.Consumer; /** Utility methods for accessing {@link RoleManager} APIs. */ @SuppressLint("LongLogTag") -final class RolesUtils { +public final class RolesUtils { + + private static final String TAG = "CDM_RolesUtils"; - static boolean isRoleHolder(@NonNull Context context, @UserIdInt int userId, + /** + * Check if the package holds the role. + */ + public static boolean isRoleHolder(@NonNull Context context, @UserIdInt int userId, @NonNull String packageName, @NonNull String role) { final RoleManager roleManager = context.getSystemService(RoleManager.class); final List<String> roleHolders = roleManager.getRoleHoldersAsUser( @@ -58,13 +59,9 @@ final class RolesUtils { * false if failed. If the association does not have any device profile * specified, then the operation will always be successful as a no-op. */ - static void addRoleHolderForAssociation( + public static void addRoleHolderForAssociation( @NonNull Context context, @NonNull AssociationInfo associationInfo, @NonNull Consumer<Boolean> roleGrantResult) { - if (DEBUG) { - Log.d(TAG, "addRoleHolderForAssociation() associationInfo=" + associationInfo); - } - final String deviceProfile = associationInfo.getDeviceProfile(); if (deviceProfile == null) { // If no device profile is specified, then no-op and resolve callback with success. @@ -83,33 +80,30 @@ final class RolesUtils { roleGrantResult); } - static void removeRoleHolderForAssociation( - @NonNull Context context, @NonNull AssociationInfo associationInfo) { - if (DEBUG) { - Log.d(TAG, "removeRoleHolderForAssociation() associationInfo=" + associationInfo); - } - - final String deviceProfile = associationInfo.getDeviceProfile(); + /** + * Remove the role for the package association. + */ + public static void removeRoleHolderForAssociation( + @NonNull Context context, int userId, String packageName, String deviceProfile) { if (deviceProfile == null) return; final RoleManager roleManager = context.getSystemService(RoleManager.class); - final String packageName = associationInfo.getPackageName(); - final int userId = associationInfo.getUserId(); final UserHandle userHandle = UserHandle.of(userId); - Slog.i(TAG, "Removing CDM role holder, role=" + deviceProfile - + ", package=u" + userId + "\\" + packageName); + Slog.i(TAG, "Removing CDM role=" + deviceProfile + + " for userId=" + userId + ", packageName=" + packageName); final long identity = Binder.clearCallingIdentity(); try { roleManager.removeRoleHolderAsUser(deviceProfile, packageName, - MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(), - success -> { - if (!success) { - Slog.e(TAG, "Failed to remove u" + userId + "\\" + packageName - + " from the list of " + deviceProfile + " holders."); - } - }); + MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(), + success -> { + if (!success) { + Slog.e(TAG, "Failed to remove userId=" + userId + ", packageName=" + + packageName + " from the list of " + deviceProfile + + " holders."); + } + }); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/companion/java/com/android/server/companion/Utils.java b/services/companion/java/com/android/server/companion/utils/Utils.java index b9f61ecd8c4f..8302997a5705 100644 --- a/services/companion/java/com/android/server/companion/Utils.java +++ b/services/companion/java/com/android/server/companion/utils/Utils.java @@ -14,8 +14,10 @@ * limitations under the License. */ -package com.android.server.companion; +package com.android.server.companion.utils; +import android.annotation.NonNull; +import android.bluetooth.BluetoothDevice; import android.os.Parcel; import android.os.ResultReceiver; @@ -43,5 +45,27 @@ public final class Utils { return ipcFriendly; } + /** + * Return a human-readable string for the BluetoothDevice. + */ + public static String btDeviceToString(@NonNull BluetoothDevice btDevice) { + final StringBuilder sb = new StringBuilder(btDevice.getAddress()); + + sb.append(" [name="); + final String name = btDevice.getName(); + if (name != null) { + sb.append('\'').append(name).append('\''); + } else { + sb.append("null"); + } + + final String alias = btDevice.getAlias(); + if (alias != null) { + sb.append(", alias='").append(alias).append("'"); + } + + return sb.append(']').toString(); + } + private Utils() {} } diff --git a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java b/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java index 3312be231516..96fee9296215 100644 --- a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java +++ b/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java @@ -32,8 +32,10 @@ import android.os.SystemClock; import android.util.Log; import android.util.Slog; import android.util.SparseIntArray; +import android.util.SparseLongArray; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FrameworkStatsLog; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockSettingsInternal; import com.android.internal.widget.LockSettingsStateListener; @@ -57,6 +59,8 @@ public class AdaptiveAuthService extends SystemService { private static final int MSG_REPORT_BIOMETRIC_AUTH_ATTEMPT = 2; private static final int AUTH_SUCCESS = 1; private static final int AUTH_FAILURE = 0; + private static final int TYPE_PRIMARY_AUTH = 0; + private static final int TYPE_BIOMETRIC_AUTH = 1; private final LockPatternUtils mLockPatternUtils; private final LockSettingsInternal mLockSettings; @@ -67,6 +71,7 @@ public class AdaptiveAuthService extends SystemService { private final UserManagerInternal mUserManager; @VisibleForTesting final SparseIntArray mFailedAttemptsForUser = new SparseIntArray(); + private final SparseLongArray mLastLockedTimestamp = new SparseLongArray(); public AdaptiveAuthService(Context context) { this(context, new LockPatternUtils(context)); @@ -170,7 +175,7 @@ public class AdaptiveAuthService extends SystemService { Slog.d(TAG, "handleReportPrimaryAuthAttempt: success=" + success + ", userId=" + userId); } - reportAuthAttempt(success, userId); + reportAuthAttempt(TYPE_PRIMARY_AUTH, success, userId); } private void handleReportBiometricAuthAttempt(boolean success, int userId) { @@ -178,13 +183,24 @@ public class AdaptiveAuthService extends SystemService { Slog.d(TAG, "handleReportBiometricAuthAttempt: success=" + success + ", userId=" + userId); } - reportAuthAttempt(success, userId); + reportAuthAttempt(TYPE_BIOMETRIC_AUTH, success, userId); } - private void reportAuthAttempt(boolean success, int userId) { + private void reportAuthAttempt(int authType, boolean success, int userId) { if (success) { // Deleting the entry effectively resets the counter of failed attempts for the user mFailedAttemptsForUser.delete(userId); + + // Collect metrics if the device was locked by adaptive auth before + if (mLastLockedTimestamp.indexOfKey(userId) >= 0) { + final long lastLockedTime = mLastLockedTimestamp.get(userId); + collectTimeElapsedSinceLastLocked( + lastLockedTime, SystemClock.elapsedRealtime(), authType); + + // Remove the entry for the last locked time because a successful auth just happened + // and metrics have been collected + mLastLockedTimestamp.delete(userId); + } return; } @@ -210,6 +226,34 @@ public class AdaptiveAuthService extends SystemService { lockDevice(userId); } + private static void collectTimeElapsedSinceLastLocked(long lastLockedTime, long authTime, + int authType) { + final int unlockType = switch (authType) { + case TYPE_PRIMARY_AUTH -> FrameworkStatsLog + .ADAPTIVE_AUTH_UNLOCK_AFTER_LOCK_REPORTED__UNLOCK_TYPE__PRIMARY_AUTH; + case TYPE_BIOMETRIC_AUTH -> FrameworkStatsLog + .ADAPTIVE_AUTH_UNLOCK_AFTER_LOCK_REPORTED__UNLOCK_TYPE__BIOMETRIC_AUTH; + default -> FrameworkStatsLog + .ADAPTIVE_AUTH_UNLOCK_AFTER_LOCK_REPORTED__UNLOCK_TYPE__UNKNOWN; + }; + + if (DEBUG) { + Slog.d(TAG, "collectTimeElapsedSinceLastLockedForUser: " + + "lastLockedTime=" + lastLockedTime + + ", authTime=" + authTime + + ", unlockType=" + unlockType); + } + + // This usually shouldn't happen, and just check out of an abundance of caution + if (lastLockedTime > authTime) { + return; + } + + // Log to statsd + FrameworkStatsLog.write(FrameworkStatsLog.ADAPTIVE_AUTH_UNLOCK_AFTER_LOCK_REPORTED, + lastLockedTime, authTime, unlockType); + } + /** * Locks the device and requires primary auth or biometric auth for unlocking */ @@ -234,5 +278,9 @@ public class AdaptiveAuthService extends SystemService { // Lock the device mWindowManager.lockNow(); + + // Record the time that the device is locked by adaptive auth to collect metrics when the + // next successful primary or biometric auth happens + mLastLockedTimestamp.put(userId, SystemClock.elapsedRealtime()); } } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index b8f6b3f3a988..7d3af99b74d1 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -372,6 +372,15 @@ public final class ActiveServices { @Overridable public static final long FGS_BOOT_COMPLETED_RESTRICTIONS = 296558535L; + /** + * Disables foreground service background starts in System Alert Window for all types + * unless it already has a System Overlay Window. + */ + @ChangeId + @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM) + @Overridable + public static final long FGS_SAW_RESTRICTIONS = 319471980L; + final ActivityManagerService mAm; // Maximum number of services that we allow to start in the background @@ -4133,7 +4142,7 @@ public final class ActiveServices { || (callerApp.mState.getCurProcState() <= PROCESS_STATE_TOP && c.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)), b.client); - if ((serviceBindingOomAdjPolicy + if (!s.mOomAdjBumpedInExec && (serviceBindingOomAdjPolicy & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT) == 0) { needOomAdj = true; mAm.enqueueOomAdjTargetLocked(s.app); @@ -4277,7 +4286,7 @@ public final class ActiveServices { } serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false, - !Flags.serviceBindingOomAdjPolicy() || b == null || !b.mSkippedOomAdj + !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec ? OOM_ADJ_REASON_EXECUTING_SERVICE : OOM_ADJ_REASON_NONE); } } finally { @@ -4398,7 +4407,6 @@ public final class ActiveServices { + (b != null ? b.apps.size() : 0)); boolean inDestroying = mDestroyingServices.contains(r); - boolean skipOomAdj = false; if (b != null) { if (b.apps.size() > 0 && !inDestroying) { // Applications have already bound since the last @@ -4423,11 +4431,11 @@ public final class ActiveServices { // a new client. b.doRebind = true; } - skipOomAdj = Flags.serviceBindingOomAdjPolicy() && b.mSkippedOomAdj; } serviceDoneExecutingLocked(r, inDestroying, false, false, - skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE); + !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec + ? OOM_ADJ_REASON_UNBIND_SERVICE : OOM_ADJ_REASON_NONE); } } finally { mAm.mInjector.restoreCallingIdentity(origId); @@ -4905,9 +4913,8 @@ public final class ActiveServices { * Bump the given service record into executing state. * @param oomAdjReason The caller requests it to perform the oomAdjUpdate not {@link * ActivityManagerInternal#OOM_ADJ_REASON_NONE}. - * @return {@code true} if it performed oomAdjUpdate. */ - private boolean bumpServiceExecutingLocked( + private void bumpServiceExecutingLocked( ServiceRecord r, boolean fg, String why, @OomAdjReason int oomAdjReason, boolean skipTimeoutIfPossible) { if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING " @@ -4972,19 +4979,17 @@ public final class ActiveServices { } } } - boolean oomAdjusted = false; if (oomAdjReason != OOM_ADJ_REASON_NONE && r.app != null && r.app.mState.getCurProcState() > ActivityManager.PROCESS_STATE_SERVICE) { // Force an immediate oomAdjUpdate, so the client app could be in the correct process // state before doing any service related transactions mAm.enqueueOomAdjTargetLocked(r.app); mAm.updateOomAdjPendingTargetsLocked(oomAdjReason); - oomAdjusted = true; + r.mOomAdjBumpedInExec = true; } r.executeFg |= fg; r.executeNesting++; r.executingStart = SystemClock.uptimeMillis(); - return oomAdjusted; } private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i, @@ -5001,7 +5006,7 @@ public final class ActiveServices { & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_BIND) != 0; if ((!i.requested || rebind) && i.apps.size() > 0) { try { - i.mSkippedOomAdj = !bumpServiceExecutingLocked(r, execInFg, "bind", + bumpServiceExecutingLocked(r, execInFg, "bind", skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_BIND_SERVICE, skipOomAdj /* skipTimeoutIfPossible */); if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { @@ -5020,14 +5025,16 @@ public final class ActiveServices { if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r, e); final boolean inDestroying = mDestroyingServices.contains(r); serviceDoneExecutingLocked(r, inDestroying, inDestroying, false, - skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE); + !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec + ? OOM_ADJ_REASON_UNBIND_SERVICE : OOM_ADJ_REASON_NONE); throw e; } catch (RemoteException e) { if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r); // Keep the executeNesting count accurate. final boolean inDestroying = mDestroyingServices.contains(r); serviceDoneExecutingLocked(r, inDestroying, inDestroying, false, - skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE); + !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec + ? OOM_ADJ_REASON_UNBIND_SERVICE : OOM_ADJ_REASON_NONE); return false; } } @@ -5823,6 +5830,7 @@ public final class ActiveServices { // process state before doing any service related transactions mAm.enqueueOomAdjTargetLocked(app); mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE); + r.mOomAdjBumpedInExec = true; } else { // Since we skipped the oom adj update, the Service#onCreate() might be running in // the cached state, if the service process drops into the cached state after the call. @@ -5863,7 +5871,8 @@ public final class ActiveServices { // Keep the executeNesting count accurate. final boolean inDestroying = mDestroyingServices.contains(r); serviceDoneExecutingLocked(r, inDestroying, inDestroying, false, - skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_STOP_SERVICE); + !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec + ? OOM_ADJ_REASON_STOP_SERVICE : OOM_ADJ_REASON_NONE); // Cleanup. if (newService) { @@ -5898,7 +5907,7 @@ public final class ActiveServices { null, null, 0, null, null, ActivityManager.PROCESS_STATE_UNKNOWN)); } - sendServiceArgsLocked(r, execInFg, true); + sendServiceArgsLocked(r, execInFg, r.mOomAdjBumpedInExec); if (r.delayed) { if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "REM FR DELAY LIST (new proc): " + r); @@ -6085,7 +6094,8 @@ public final class ActiveServices { } } - boolean oomAdjusted = false; + boolean oomAdjusted = Flags.serviceBindingOomAdjPolicy() && r.mOomAdjBumpedInExec; + // Tell the service that it has been unbound. if (r.app != null && r.app.isThreadReady()) { for (int i = r.bindings.size() - 1; i >= 0; i--) { @@ -6094,9 +6104,10 @@ public final class ActiveServices { + ": hasBound=" + ibr.hasBound); if (ibr.hasBound) { try { - oomAdjusted |= bumpServiceExecutingLocked(r, false, "bring down unbind", - OOM_ADJ_REASON_UNBIND_SERVICE, - false /* skipTimeoutIfPossible */); + bumpServiceExecutingLocked(r, false, "bring down unbind", + oomAdjusted ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE, + oomAdjusted /* skipTimeoutIfPossible */); + oomAdjusted |= r.mOomAdjBumpedInExec; ibr.hasBound = false; ibr.requested = false; r.app.getThread().scheduleUnbindService(r, @@ -6252,10 +6263,11 @@ public final class ActiveServices { } } else { try { - oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy", - oomAdjusted ? 0 : OOM_ADJ_REASON_STOP_SERVICE, - false /* skipTimeoutIfPossible */); + bumpServiceExecutingLocked(r, false, "destroy", + oomAdjusted ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE, + oomAdjusted /* skipTimeoutIfPossible */); mDestroyingServices.add(r); + oomAdjusted |= r.mOomAdjBumpedInExec; r.destroying = true; r.app.getThread().scheduleStopService(r); } catch (Exception e) { @@ -6411,7 +6423,7 @@ public final class ActiveServices { final boolean skipOomAdj = (serviceBindingOomAdjPolicy & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT) != 0; try { - b.intent.mSkippedOomAdj = !bumpServiceExecutingLocked(s, false, "unbind", + bumpServiceExecutingLocked(s, false, "unbind", skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE, skipOomAdj /* skipTimeoutIfPossible */); if (b.client != s.app && c.notHasFlag(Context.BIND_WAIVE_PRIORITY) @@ -6461,6 +6473,7 @@ public final class ActiveServices { boolean inDestroying = mDestroyingServices.contains(r); if (r != null) { boolean skipOomAdj = false; + boolean needOomAdj = false; if (type == ActivityThread.SERVICE_DONE_EXECUTING_START) { // This is a call from a service start... take care of // book-keeping. @@ -6536,15 +6549,13 @@ public final class ActiveServices { // Fake it to keep from ANR due to orphaned entry. r.executeNesting = 1; } - } else if (type == ActivityThread.SERVICE_DONE_EXECUTING_REBIND - || type == ActivityThread.SERVICE_DONE_EXECUTING_UNBIND) { - final Intent.FilterComparison filter = new Intent.FilterComparison(intent); - final IntentBindRecord b = r.bindings.get(filter); - skipOomAdj = Flags.serviceBindingOomAdjPolicy() && b != null && b.mSkippedOomAdj; + // The service is done, force an oom adj update. + needOomAdj = true; } final long origId = mAm.mInjector.clearCallingIdentity(); serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj, - skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_EXECUTING_SERVICE); + !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec || needOomAdj + ? OOM_ADJ_REASON_EXECUTING_SERVICE : OOM_ADJ_REASON_NONE); mAm.mInjector.restoreCallingIdentity(origId); } else { Slog.w(TAG, "Done executing unknown service from pid " @@ -6600,18 +6611,16 @@ public final class ActiveServices { mDestroyingServices.remove(r); r.bindings.clear(); } - boolean oomAdjusted = false; if (oomAdjReason != OOM_ADJ_REASON_NONE) { if (enqueueOomAdj) { mAm.enqueueOomAdjTargetLocked(r.app); } else { mAm.updateOomAdjLocked(r.app, oomAdjReason); } - oomAdjusted = true; } else { // Skip oom adj if it wasn't bumped during the bumpServiceExecutingLocked() - oomAdjusted = false; } + r.mOomAdjBumpedInExec = false; } r.executeFg = false; if (r.tracker != null) { @@ -6995,6 +7004,7 @@ public final class ActiveServices { sr.setProcess(null, null, 0, null); sr.isolationHostProc = null; sr.executeNesting = 0; + sr.mOomAdjBumpedInExec = false; synchronized (mAm.mProcessStats.mLock) { sr.forceClearTracker(); } @@ -8525,10 +8535,31 @@ public final class ActiveServices { } } + // The flag being enabled isn't enough to deny background start: we need to also check + // if there is a system alert UI present. if (ret == REASON_DENIED) { - if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid, - callingPackage)) { - ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION; + // Flag check: are we disabling SAW FGS background starts? + final boolean shouldDisableSaw = Flags.fgsDisableSaw() + && CompatChanges.isChangeEnabled(FGS_BOOT_COMPLETED_RESTRICTIONS, callingUid); + if (shouldDisableSaw) { + final ProcessRecord processRecord = mAm + .getProcessRecordLocked(targetService.processName, + targetService.appInfo.uid); + if (processRecord != null) { + if (processRecord.mState.hasOverlayUi()) { + if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid, + callingPackage)) { + ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION; + } + } + } else { + Slog.e(TAG, "Could not find process record for SAW check"); + } + } else { + if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid, + callingPackage)) { + ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION; + } } } diff --git a/services/core/java/com/android/server/am/IntentBindRecord.java b/services/core/java/com/android/server/am/IntentBindRecord.java index db47e3f26a6d..1a3652e49d8e 100644 --- a/services/core/java/com/android/server/am/IntentBindRecord.java +++ b/services/core/java/com/android/server/am/IntentBindRecord.java @@ -49,14 +49,6 @@ final class IntentBindRecord { String stringName; // caching of toString - /** - * Mark if we've skipped oom adj update before calling into the {@link Service#onBind()} - * or {@link Service#onUnbind()}. - * - * <p>If it's true, we'll skip the oom adj update too during the serviceDoneExecuting. - */ - boolean mSkippedOomAdj; - void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("service="); pw.println(service); dumpInService(pw, prefix); diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 3c8d7fc833dc..e3aac0251141 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -267,6 +267,11 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN int mAllowStart_byBindings = REASON_DENIED; /** + * Whether or not we've bumped its oom adj scores during its execution. + */ + boolean mOomAdjBumpedInExec; + + /** * Whether to use the new "while-in-use permission" logic for FGS start */ private boolean useNewWiuLogic_forStart() { diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index 16dbe18f1555..e955b00566b8 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -23,6 +23,13 @@ flag { } flag { + name: "fgs_disable_saw" + namespace: "backstage_power" + description: "Disable System Alert Window FGS start" + bug: "296558535" +} + +flag { name: "bfgs_managed_network_access" namespace: "backstage_power" description: "Restrict network access for certain applications in BFGS process state" diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index cb6d26f61314..19dd7b7ea2f6 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -12528,6 +12528,16 @@ public class AudioService extends IAudioService.Stub if (app == null) { return AudioManager.ERROR; } + if (android.media.audiopolicy.Flags.audioMixOwnership()) { + for (AudioMix mix : policyConfig.getMixes()) { + if (!app.getMixes().contains(mix)) { + Slog.e(TAG, + "removeMixForPolicy attempted to unregister AudioMix(es) not " + + "belonging to the AudioPolicy"); + return AudioManager.ERROR; + } + } + } return app.removeMixes(policyConfig.getMixes()) == AudioSystem.SUCCESS ? AudioManager.SUCCESS : AudioManager.ERROR; } @@ -13306,7 +13316,13 @@ public class AudioService extends IAudioService.Stub } final long identity = Binder.clearCallingIdentity(); try { - mAudioSystem.registerPolicyMixes(mMixes, false); + if (android.media.audiopolicy.Flags.audioMixOwnership()) { + synchronized (mMixes) { + removeMixes(new ArrayList(mMixes)); + } + } else { + mAudioSystem.registerPolicyMixes(mMixes, false); + } } finally { Binder.restoreCallingIdentity(identity); } @@ -13350,6 +13366,17 @@ public class AudioService extends IAudioService.Stub int addMixes(@NonNull ArrayList<AudioMix> mixes) { synchronized (mMixes) { + if (android.media.audiopolicy.Flags.audioMixOwnership()) { + for (AudioMix mix : mixes) { + setMixRegistration(mix); + } + + int result = mAudioSystem.registerPolicyMixes(mixes, true); + if (result == AudioSystem.SUCCESS) { + this.add(mixes); + } + return result; + } this.add(mixes); return mAudioSystem.registerPolicyMixes(mixes, true); } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index dbe85ea0fa04..23ca81468294 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -1474,6 +1474,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call && mFlags.isDisplayOffloadEnabled() && mPowerRequest.policy == POLICY_DOZE && mDisplayOffloadSession != null + && mAutomaticBrightnessController != null && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) { rawBrightnessState = mAutomaticBrightnessController .getAutomaticScreenBrightnessBasedOnLastObservedLux(mTempBrightnessEvent); diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index d2aff254890d..c6d66db3f8cd 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -67,6 +67,7 @@ import android.view.Display; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLoggerImpl; import com.android.internal.util.DumpUtils; @@ -112,7 +113,7 @@ public final class DreamManagerService extends SystemService { private final Object mLock = new Object(); private final Context mContext; - private final DreamHandler mHandler; + private final Handler mHandler; private final DreamController mController; private final PowerManager mPowerManager; private final PowerManagerInternal mPowerManagerInternal; @@ -211,9 +212,14 @@ public final class DreamManagerService extends SystemService { } public DreamManagerService(Context context) { + this(context, new DreamHandler(FgThread.get().getLooper())); + } + + @VisibleForTesting + DreamManagerService(Context context, Handler handler) { super(context); mContext = context; - mHandler = new DreamHandler(FgThread.get().getLooper()); + mHandler = handler; mController = new DreamController(context, mHandler, mControllerListener); mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); @@ -244,7 +250,6 @@ public final class DreamManagerService extends SystemService { com.android.internal.R.bool.config_keepDreamingWhenUnplugging); mDreamsDisabledByAmbientModeSuppressionConfig = mContext.getResources().getBoolean( com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig); - } @Override diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 3f9e989a5bba..9b0fec2c757b 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -22,8 +22,8 @@ import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.AppOpsManager.OP_ARCHIVE_ICON_OVERLAY; import static android.app.AppOpsManager.OP_UNARCHIVAL_CONFIRMATION; -import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; -import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.app.PendingIntent.FLAG_MUTABLE; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index 2b20bfd7a7c2..ef8453dcee67 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -21,7 +21,7 @@ import static android.app.ActivityManager.START_CLASS_NOT_FOUND; import static android.app.ActivityManager.START_PERMISSION_DENIED; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_IGNORED; -import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.content.pm.ArchivedActivityInfo.bytesFromBitmap; import static android.content.pm.ArchivedActivityInfo.drawableToBitmap; import static android.content.pm.PackageInstaller.EXTRA_UNARCHIVE_STATUS; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 69f790637d80..fe8030b656b0 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1541,6 +1541,19 @@ public class PackageManagerService implements PackageSender, TestUtilityService return; } + // Initialize all necessary settings for archival installation. + pkgSetting + // No package. + .setPkg(null) + // Mark for later restore. + .setPendingRestore(true); + for (int userId : userIds) { + // Unmark "installed" for all users. + pkgSetting + .modifyUserState(userId) + .setInstalled(false); + } + String responsibleInstallerPackage = PackageArchiver.getResponsibleInstallerPackage( pkgSetting); // TODO(b/278553670) Check if responsibleInstallerPackage supports unarchival. @@ -1551,16 +1564,11 @@ public class PackageManagerService implements PackageSender, TestUtilityService for (int userId : userIds) { var archiveState = mInstallerService.mPackageArchiver.createArchiveState( archivePackage, userId, responsibleInstallerPackage); - if (archiveState == null) { - continue; - } - pkgSetting - .setPkg(null) - // This package was installed as archived. Need to mark it for later restore. - .setPendingRestore(true) + if (archiveState != null) { + pkgSetting .modifyUserState(userId) - .setInstalled(false) .setArchiveState(archiveState); + } } } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java index 37f38252698e..601c7f450d4f 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java @@ -357,15 +357,30 @@ public class WallpaperCropper { * Given some suggested crops, find cropHints for all orientations of the default display. */ SparseArray<Rect> getDefaultCrops(SparseArray<Rect> suggestedCrops, Point bitmapSize) { - SparseArray<Rect> result = new SparseArray<>(); - // add missing cropHints for all orientation of the default display + SparseArray<Point> defaultDisplaySizes = mWallpaperDisplayHelper.getDefaultDisplaySizes(); boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL; + + // adjust existing entries for the default display + SparseArray<Rect> adjustedSuggestedCrops = new SparseArray<>(); + for (int i = 0; i < defaultDisplaySizes.size(); i++) { + int orientation = defaultDisplaySizes.keyAt(i); + Point displaySize = defaultDisplaySizes.valueAt(i); + Rect suggestedCrop = suggestedCrops.get(orientation); + if (suggestedCrop != null) { + adjustedSuggestedCrops.put(orientation, + getCrop(displaySize, bitmapSize, suggestedCrops, rtl)); + } + } + + // add missing cropHints for all orientation of the default display + SparseArray<Rect> result = adjustedSuggestedCrops.clone(); for (int i = 0; i < defaultDisplaySizes.size(); i++) { int orientation = defaultDisplaySizes.keyAt(i); + if (result.contains(orientation)) continue; Point displaySize = defaultDisplaySizes.valueAt(i); - Rect newCrop = getCrop(displaySize, bitmapSize, suggestedCrops, rtl); + Rect newCrop = getCrop(displaySize, bitmapSize, adjustedSuggestedCrops, rtl); result.put(orientation, newCrop); } return result; diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java index 5f6ffd988c84..8742ab1dd95b 100644 --- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java +++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java @@ -21,8 +21,8 @@ import static android.provider.DeviceConfig.NAMESPACE_WEARABLE_SENSING; import android.Manifest; import android.annotation.NonNull; import android.annotation.UserIdInt; +import android.app.ActivityOptions; import android.app.BroadcastOptions; -import android.app.ComponentOptions; import android.app.PendingIntent; import android.app.ambientcontext.AmbientContextEvent; import android.app.wearable.IWearableSensingManager; @@ -353,7 +353,7 @@ public class WearableSensingManagerService extends dataRequest); BroadcastOptions options = BroadcastOptions.makeBasic(); options.setPendingIntentBackgroundActivityStartMode( - ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED); + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED); mDataRequestRateLimiter.noteEvent( userId, RATE_LIMITER_PACKAGE_NAME, RATE_LIMITER_TAG); final long previousCallingIdentity = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index ed5df5fab017..981c4c078dd1 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -506,7 +506,8 @@ class ActivityClientController extends IActivityClientController.Stub { // keep backwards compatibility we remove the task from recents when finishing // task with root activity. mTaskSupervisor.removeTask(tr, false /*killProcess*/, - finishWithRootActivity, "finish-activity", r.getUid(), r.info.name); + finishWithRootActivity, "finish-activity", r.getUid(), r.getPid(), + r.info.name); res = true; // Explicitly dismissing the activity so reset its relaunch flag. r.mRelaunchReason = RELAUNCH_REASON_NONE; diff --git a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java index 01d077a5bc55..4149bd9c6c56 100644 --- a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java +++ b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java @@ -41,7 +41,7 @@ class ActivitySecurityModelFeatureFlags { static final String DOC_LINK = "go/android-asm"; /** Used to determine which version of the ASM logic was used in logs while we iterate */ - static final int ASM_VERSION = 9; + static final int ASM_VERSION = 10; private static final String NAMESPACE = NAMESPACE_WINDOW_MANAGER; private static final String KEY_ASM_PREFIX = "ActivitySecurity__"; diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index c137c54949e9..6ad056f5a902 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -2089,7 +2089,7 @@ class ActivityStarter { if (!mSupervisor.getBackgroundActivityLaunchController().checkActivityAllowedToStart( mSourceRecord, r, newTask, avoidMoveToFront(), targetTask, mLaunchFlags, mBalCode, - mCallingUid, mRealCallingUid)) { + mCallingUid, mRealCallingUid, mPreferredTaskDisplayArea)) { return START_ABORTED; } diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index aefa777a1db7..d1d498dbac46 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -42,6 +42,7 @@ import static android.content.pm.PackageManager.NOTIFY_PACKAGE_USE_ACTIVITY; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.PowerManager.PARTIAL_WAKE_LOCK; +import static android.os.Process.INVALID_PID; import static android.os.Process.INVALID_UID; import static android.os.Process.SYSTEM_UID; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; @@ -1652,11 +1653,11 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { * @return Returns true if the given task was found and removed. */ boolean removeTaskById(int taskId, boolean killProcess, boolean removeFromRecents, - String reason, int callingUid) { + String reason, int callingUid, int callingPid) { final Task task = mRootWindowContainer.anyTaskForId(taskId, MATCH_ATTACHED_TASK_OR_RECENT_TASKS); if (task != null) { - removeTask(task, killProcess, removeFromRecents, reason, callingUid, null); + removeTask(task, killProcess, removeFromRecents, reason, callingUid, callingPid, null); return true; } Slog.w(TAG, "Request to remove task ignored for non-existent task " + taskId); @@ -1664,11 +1665,11 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } void removeTask(Task task, boolean killProcess, boolean removeFromRecents, String reason) { - removeTask(task, killProcess, removeFromRecents, reason, SYSTEM_UID, null); + removeTask(task, killProcess, removeFromRecents, reason, SYSTEM_UID, INVALID_PID, null); } void removeTask(Task task, boolean killProcess, boolean removeFromRecents, String reason, - int callingUid, String callerActivityClassName) { + int callingUid, int callingPid, String callerActivityClassName) { if (task.mInRemoveTask) { // Prevent recursion. return; @@ -1705,8 +1706,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { if (task.isPersistable) { mService.notifyTaskPersisterLocked(null, true); } - mBalController - .checkActivityAllowedToClearTask(task, callingUid, callerActivityClassName); + mBalController.checkActivityAllowedToClearTask( + task, callingUid, callingPid, callerActivityClassName); } finally { task.mInRemoveTask = false; } @@ -1874,7 +1875,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // Task was trimmed from the recent tasks list -- remove the active task record as well // since the user won't really be able to go back to it removeTaskById(task.mTaskId, killProcess, false /* removeFromRecents */, - "recent-task-trimmed", SYSTEM_UID); + "recent-task-trimmed", SYSTEM_UID, INVALID_PID); } task.removedFromRecents(); } diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java index 50de0b08f3b0..d699af872c7f 100644 --- a/services/core/java/com/android/server/wm/AppTaskImpl.java +++ b/services/core/java/com/android/server/wm/AppTaskImpl.java @@ -77,11 +77,13 @@ class AppTaskImpl extends IAppTask.Stub { synchronized (mService.mGlobalLock) { int origCallingUid = Binder.getCallingUid(); + int origCallingPid = Binder.getCallingPid(); final long callingIdentity = Binder.clearCallingIdentity(); try { // We remove the task from recents to preserve backwards if (!mService.mTaskSupervisor.removeTaskById(mTaskId, false, - REMOVE_FROM_RECENTS, "finish-and-remove-task", origCallingUid)) { + REMOVE_FROM_RECENTS, "finish-and-remove-task", origCallingUid, + origCallingPid)) { throw new IllegalArgumentException("Unable to find task ID " + mTaskId); } } finally { diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index fdae53f59ad4..071f40342461 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -21,13 +21,16 @@ import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; -import static android.app.ComponentOptions.BackgroundActivityStartMode; +import static android.app.ActivityOptions.BackgroundActivityStartMode; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; +import static android.os.Process.INVALID_PID; +import static android.os.Process.INVALID_UID; import static android.os.Process.SYSTEM_UID; import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER; +import static com.android.server.wm.ActivityStarter.ASM_RESTRICTIONS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -67,6 +70,7 @@ import android.util.DebugUtils; import android.util.Slog; import android.widget.Toast; +import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; @@ -75,6 +79,7 @@ import com.android.server.UiThread; import com.android.server.am.PendingIntentRecord; import java.lang.annotation.Retention; +import java.util.ArrayList; import java.util.HashMap; import java.util.StringJoiner; import java.util.function.Consumer; @@ -1022,7 +1027,7 @@ public class BackgroundActivityStartController { } /** - * Log activity starts which violate one of the following rules of the + * Check activity starts which violate one of the following rules of the * activity security model (ASM): * See go/activity-security for rationale behind the rules. * 1. Within a task, only an activity matching a top UID of the task can start activities @@ -1032,7 +1037,7 @@ public class BackgroundActivityStartController { boolean checkActivityAllowedToStart(@Nullable ActivityRecord sourceRecord, @NonNull ActivityRecord targetRecord, boolean newTask, boolean avoidMoveTaskToFront, @Nullable Task targetTask, int launchFlags, int balCode, int callingUid, - int realCallingUid) { + int realCallingUid, TaskDisplayArea preferredTaskDisplayArea) { // BAL Exception allowed in all cases if (balCode == BAL_ALLOW_ALLOWLISTED_UID) { return true; @@ -1055,68 +1060,46 @@ public class BackgroundActivityStartController { } } - if (balCode == BAL_ALLOW_GRACE_PERIOD) { - // Allow if launching into new task, and caller matches most recently finished activity - if (taskToFront && mTopFinishedActivity != null - && mTopFinishedActivity.mUid == callingUid) { - return true; - } - - // Launching into existing task - allow if matches most recently finished activity - // within the task. - // We can reach here multiple ways: - // 1. activity in fg fires intent (taskToFront = false, sourceRecord is available) - // 2. activity in bg fires intent (taskToFront = false, sourceRecord is available) - // 3. activity in bg fires intent with NEW_FLAG (taskToFront = true, - // avoidMoveTaskToFront = true, sourceRecord is available) - // 4. activity in bg fires PI (taskToFront = true, avoidMoveTaskToFront = true, - // sourceRecord is not available, targetTask may be available) - if (!taskToFront || avoidMoveTaskToFront) { - if (targetTask != null) { - FinishedActivityEntry finishedEntry = - mTaskIdToFinishedActivity.get(targetTask.mTaskId); - if (finishedEntry != null && finishedEntry.mUid == callingUid) { - return true; - } - } - - if (sourceRecord != null) { - FinishedActivityEntry finishedEntry = - mTaskIdToFinishedActivity.get(sourceRecord.getTask().mTaskId); - if (finishedEntry != null && finishedEntry.mUid == callingUid) { - return true; - } - } - } - } - - BlockActivityStart bas = null; + BlockActivityStart bas = new BlockActivityStart(); if (sourceRecord != null) { - boolean passesAsmChecks = true; Task sourceTask = sourceRecord.getTask(); + Task taskToCheck = taskToFront ? sourceTask : targetTask; + bas = checkTopActivityForAsm(taskToCheck, sourceRecord.getUid(), + sourceRecord, bas); + // Allow launching into a new task (or a task matching the launched activity's // affinity) only if the current task is foreground or mutating its own task. // The latter can happen eg. if caller uses NEW_TASK flag and the activity being // launched matches affinity of source task. - if (taskToFront) { - passesAsmChecks = sourceTask != null - && (sourceTask.isVisible() || sourceTask == targetTask); - } - - if (passesAsmChecks) { - Task taskToCheck = taskToFront ? sourceTask : targetTask; - bas = isTopActivityMatchingUidAbsentForAsm(taskToCheck, sourceRecord.getUid(), - sourceRecord); + if (taskToFront && bas.mTopActivityMatchesSource) { + bas.mTopActivityMatchesSource = (sourceTask != null + && (sourceTask.isVisible() || sourceTask == targetTask)); } } else if (targetTask != null && (!taskToFront || avoidMoveTaskToFront)) { // We don't have a sourceRecord, and we're launching into an existing task. // Allow if callingUid is top of stack. - bas = isTopActivityMatchingUidAbsentForAsm(targetTask, callingUid, - /*sourceRecord*/null); + bas = checkTopActivityForAsm(targetTask, callingUid, + /*sourceRecord*/null, bas); + } else { + // We're launching from a non-visible activity. Has any visible app opted in? + TaskDisplayArea displayArea = targetTask != null && targetTask.getDisplayArea() != null + ? targetTask.getDisplayArea() + : preferredTaskDisplayArea; + if (displayArea != null) { + ArrayList<Task> visibleTasks = displayArea.getVisibleTasks(); + for (int i = 0; i < visibleTasks.size(); i++) { + Task task = visibleTasks.get(i); + if (visibleTasks.size() == 1 && task.isActivityTypeHomeOrRecents()) { + bas.optedIn(task.getTopMostActivity()); + } else { + bas = checkTopActivityForAsm(task, callingUid, /*sourceRecord*/null, bas); + } + } + } } - if (bas != null && !bas.mWouldBlockActivityStartIgnoringFlag) { + if (bas.mTopActivityMatchesSource) { return true; } @@ -1140,13 +1123,16 @@ public class BackgroundActivityStartController { ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_SAME_TASK : FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_DIFFERENT_TASK); - boolean blockActivityStartAndFeatureEnabled = ActivitySecurityModelFeatureFlags - .shouldRestrictActivitySwitch(callingUid) - && (bas == null || bas.mBlockActivityStartIfFlagEnabled); + boolean enforceBlock = bas.mTopActivityOptedIn + && ActivitySecurityModelFeatureFlags.shouldRestrictActivitySwitch(callingUid); + + boolean allowedByGracePeriod = allowedByAsmGracePeriod(callingUid, sourceRecord, targetTask, + balCode, taskToFront, avoidMoveTaskToFront); String asmDebugInfo = getDebugInfoForActivitySecurity("Launch", sourceRecord, targetRecord, targetTask, targetTopActivity, realCallingUid, balCode, - blockActivityStartAndFeatureEnabled, taskToFront, avoidMoveTaskToFront); + enforceBlock, taskToFront, avoidMoveTaskToFront, allowedByGracePeriod, + bas.mActivityOptedIn); FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED, /* caller_uid */ @@ -1184,7 +1170,7 @@ public class BackgroundActivityStartController { String launchedFromPackageName = targetRecord.launchedFromPackage; if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)) { String toastText = ActivitySecurityModelFeatureFlags.DOC_LINK - + (blockActivityStartAndFeatureEnabled ? " blocked " : " would block ") + + (enforceBlock ? " blocked " : " would block ") + getApplicationLabel(mService.mContext.getPackageManager(), launchedFromPackageName); showToast(toastText); @@ -1192,7 +1178,7 @@ public class BackgroundActivityStartController { Slog.i(TAG, asmDebugInfo); } - if (blockActivityStartAndFeatureEnabled) { + if (enforceBlock) { Slog.e(TAG, "[ASM] Abort Launching r: " + targetRecord + " as source: " + (sourceRecord != null ? sourceRecord : launchedFromPackageName) @@ -1251,18 +1237,18 @@ public class BackgroundActivityStartController { // Find the first activity which matches a safe UID and is not finishing. Clear everything // above it + int[] finishCount = new int[1]; boolean shouldBlockActivityStart = ActivitySecurityModelFeatureFlags .shouldRestrictActivitySwitch(callingUid); - int[] finishCount = new int[0]; - if (shouldBlockActivityStart - && blockCrossUidActivitySwitchFromBelowForActivity(targetTaskTop)) { + BlockActivityStart bas = checkCrossUidActivitySwitchFromBelow( + targetTaskTop, callingUid, new BlockActivityStart()); + if (shouldBlockActivityStart && bas.mTopActivityOptedIn) { ActivityRecord activity = targetTask.getActivity(isLaunchingOrLaunched); if (activity == null) { // mStartActivity is not in task, so clear everything activity = targetRecord; } - finishCount = new int[1]; targetTask.performClearTop(activity, launchFlags, finishCount); if (finishCount[0] > 0) { Slog.w(TAG, "Cleared top n: " + finishCount[0] + " activities from task t: " @@ -1279,7 +1265,8 @@ public class BackgroundActivityStartController { Slog.i(TAG, getDebugInfoForActivitySecurity("Clear Top", sourceRecord, targetRecord, targetTask, targetTaskTop, realCallingUid, balCode, shouldBlockActivityStart, - /* taskToFront */ true, /* avoidMoveTaskToFront */ false)); + /* taskToFront */ true, /* avoidMoveTaskToFront */ false, + /* allowedByAsmGracePeriod */ false, bas.mActivityOptedIn)); } } @@ -1287,7 +1274,7 @@ public class BackgroundActivityStartController { * Returns home if the passed in callingUid is not top of the stack, rather than returning to * previous task. */ - void checkActivityAllowedToClearTask(@NonNull Task task, int callingUid, + void checkActivityAllowedToClearTask(@NonNull Task task, int callingUid, int callingPid, @NonNull String callerActivityClassName) { // We may have already checked that the callingUid has additional clearTask privileges, and // cleared the calling identify. If so, we infer we do not need further restrictions here. @@ -1295,14 +1282,28 @@ public class BackgroundActivityStartController { return; } + String packageName = mService.mContext.getPackageManager().getNameForUid(callingUid); + BalState state = new BalState(callingUid, callingPid, packageName, INVALID_UID, + INVALID_PID, null, null, null, null, null, ActivityOptions.makeBasic()); + @BalCode int balCode = checkBackgroundActivityStartAllowedByCaller(state).mCode; + if (balCode == BAL_ALLOW_ALLOWLISTED_UID + || balCode == BAL_ALLOW_ALLOWLISTED_COMPONENT + || balCode == BAL_ALLOW_PERMISSION + || balCode == BAL_ALLOW_SAW_PERMISSION + || balCode == BAL_ALLOW_VISIBLE_WINDOW + || balCode == BAL_ALLOW_NON_APP_VISIBLE_WINDOW) { + return; + } + TaskDisplayArea displayArea = task.getTaskDisplayArea(); if (displayArea == null) { // If there is no associated display area, we can not return home. return; } - BlockActivityStart bas = isTopActivityMatchingUidAbsentForAsm(task, callingUid, null); - if (!bas.mWouldBlockActivityStartIgnoringFlag) { + BlockActivityStart bas = checkTopActivityForAsm(task, callingUid, null, + new BlockActivityStart()); + if (bas.mTopActivityMatchesSource) { return; } @@ -1339,8 +1340,7 @@ public class BackgroundActivityStartController { ); boolean restrictActivitySwitch = ActivitySecurityModelFeatureFlags - .shouldRestrictActivitySwitch(callingUid) - && bas.mBlockActivityStartIfFlagEnabled; + .shouldRestrictActivitySwitch(callingUid) && bas.mTopActivityOptedIn; PackageManager pm = mService.mContext.getPackageManager(); String callingPackage = pm.getNameForUid(callingUid); @@ -1381,32 +1381,30 @@ public class BackgroundActivityStartController { * <p> * The 'sourceRecord' can be considered top even if it is 'finishing' * <p> - * Returns a class where the elements are: - * <pre> - * shouldBlockActivityStart: {@code true} if we should actually block the transition (takes into - * consideration feature flag and targetSdk). - * wouldBlockActivityStartIgnoringFlags: {@code true} if we should warn about the transition via - * toasts. This happens if the transition would be blocked in case both the app was targeting V+ - * and the feature was enabled. - * </pre> */ - private BlockActivityStart isTopActivityMatchingUidAbsentForAsm(@NonNull Task task, - int uid, @Nullable ActivityRecord sourceRecord) { + private BlockActivityStart checkTopActivityForAsm(@NonNull Task task, + int uid, @Nullable ActivityRecord sourceRecord, BlockActivityStart bas) { // If the source is visible, consider it 'top'. if (sourceRecord != null && sourceRecord.isVisibleRequested()) { - return BlockActivityStart.ACTIVITY_START_ALLOWED; + return bas.matchesSource(); } - // Always allow actual top activity to clear task + // Always allow actual top activity ActivityRecord topActivity = task.getTopMostActivity(); - if (topActivity != null && topActivity.isUid(uid)) { - return BlockActivityStart.ACTIVITY_START_ALLOWED; + if (topActivity == null) { + Slog.wtf(TAG, "Activities for task: " + task + " not found."); + return bas.optedIn(topActivity); + } + + bas = checkCrossUidActivitySwitchFromBelow(topActivity, uid, bas); + if (bas.mTopActivityMatchesSource) { + return bas; } // If UID is visible in target task, allow launch if (task.forAllActivities((Predicate<ActivityRecord>) ar -> ar.isUid(uid) && ar.isVisibleRequested())) { - return BlockActivityStart.ACTIVITY_START_ALLOWED; + return bas.matchesSource(); } // Consider the source activity, whether or not it is finishing. Do not consider any other @@ -1417,82 +1415,91 @@ public class BackgroundActivityStartController { // Check top of stack (or the first task fragment for embedding). topActivity = task.getActivity(topOfStackPredicate); if (topActivity == null) { - return new BlockActivityStart(true, true); + return bas; } - BlockActivityStart pair = blockCrossUidActivitySwitchFromBelow(topActivity, uid); - if (!pair.mBlockActivityStartIfFlagEnabled) { - return pair; + bas = checkCrossUidActivitySwitchFromBelow(topActivity, uid, bas); + if (bas.mTopActivityMatchesSource) { + return bas; } // Even if the top activity is not a match, we may be in an embedded activity scenario with // an adjacent task fragment. Get the second fragment. TaskFragment taskFragment = topActivity.getTaskFragment(); if (taskFragment == null) { - return pair; + return bas; } TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment(); if (adjacentTaskFragment == null) { - return pair; + return bas; } // Check the second fragment. topActivity = adjacentTaskFragment.getActivity(topOfStackPredicate); if (topActivity == null) { - return new BlockActivityStart(true, true); + return bas; } - return blockCrossUidActivitySwitchFromBelow(topActivity, uid); + return checkCrossUidActivitySwitchFromBelow(topActivity, uid, bas); } /** * Determines if a source is allowed to add or remove activities from the task, * if the current ActivityRecord is above it in the stack * <p> - * A transition is blocked ({@code false} returned) if all of the following are met: + * A transition is blocked if all of the following are met: * <pre> * 1. The source activity and the current activity record belong to different apps * (i.e, have different UIDs). - * 2. Both the source activity and the current activity target U+ - * 3. The current activity has not set + * 2. The current activity target V+ + * 3. The current app has set + * {@link R.styleable#AndroidManifestApplication_allowCrossUidActivitySwitchFromBelow} + * to {@code false} + * 4. The current activity has not set * {@link ActivityRecord#setAllowCrossUidActivitySwitchFromBelow(boolean)} to {@code true} * </pre> * - * Returns a class where the elements are: - * <pre> - * shouldBlockActivityStart: {@code true} if we should actually block the transition (takes into - * consideration feature flag and targetSdk). - * wouldBlockActivityStartIgnoringFlags: {@code true} if we should warn about the transition via - * toasts. This happens if the transition would be blocked in case both the app was targeting V+ - * and the feature was enabled. - * </pre> * * @param sourceUid The source (s) activity performing the state change */ - private BlockActivityStart blockCrossUidActivitySwitchFromBelow(ActivityRecord ar, - int sourceUid) { + private BlockActivityStart checkCrossUidActivitySwitchFromBelow(ActivityRecord ar, + int sourceUid, BlockActivityStart bas) { if (ar.isUid(sourceUid)) { - return BlockActivityStart.ACTIVITY_START_ALLOWED; + return bas.matchesSource(); } - if (!blockCrossUidActivitySwitchFromBelowForActivity(ar)) { - return BlockActivityStart.ACTIVITY_START_ALLOWED; + // We don't need to check package level if activity has opted out. + if (ar.mAllowCrossUidActivitySwitchFromBelow) { + bas.mTopActivityOptedIn = false; + return bas.matchesSource(); } - // At this point, we would block if the feature is launched and both apps were V+ - // Since we have a feature flag, we need to check that too - // TODO(b/258792202) Replace with CompatChanges and replace Pair with boolean once feature - // flag is removed - boolean restrictActivitySwitch = - ActivitySecurityModelFeatureFlags.shouldRestrictActivitySwitch(ar.getUid()) - && ActivitySecurityModelFeatureFlags - .shouldRestrictActivitySwitch(sourceUid); - if (restrictActivitySwitch) { - return BlockActivityStart.BLOCK; - } else { - return BlockActivityStart.LOG_ONLY; + if (!CompatChanges.isChangeEnabled(ASM_RESTRICTIONS, ar.getUid())) { + return bas; } + + if (ar.isUid(SYSTEM_UID)) { + return bas.optedIn(ar); + } + + String packageName = ar.packageName; + if (packageName == null) { + Slog.wtf(TAG, "Package name: " + ar + " not found."); + return bas.optedIn(ar); + } + + PackageManager pm = mService.mContext.getPackageManager(); + ApplicationInfo applicationInfo; + + try { + applicationInfo = pm.getApplicationInfo(packageName, 0); + } catch (PackageManager.NameNotFoundException e) { + Slog.wtf(TAG, "Package name: " + packageName + " not found."); + return bas.optedIn(ar); + } + + return applicationInfo.allowCrossUidActivitySwitchFromBelow ? bas : bas.optedIn(ar); } /** @@ -1502,8 +1509,9 @@ public class BackgroundActivityStartController { @Nullable ActivityRecord sourceRecord, @NonNull ActivityRecord targetRecord, @Nullable Task targetTask, @Nullable ActivityRecord targetTopActivity, int realCallingUid, @BalCode int balCode, - boolean blockActivityStartAndFeatureEnabled, boolean taskToFront, - boolean avoidMoveTaskToFront) { + boolean enforceBlock, boolean taskToFront, + boolean avoidMoveTaskToFront, boolean allowedByGracePeriod, + ActivityRecord activityOptedIn) { final String prefix = "[ASM] "; Function<ActivityRecord, String> recordToString = (ar) -> { if (ar == null) { @@ -1519,9 +1527,16 @@ public class BackgroundActivityStartController { StringJoiner joiner = new StringJoiner("\n"); joiner.add(prefix + "------ Activity Security " + action + " Debug Logging Start ------"); - joiner.add(prefix + "Block Enabled: " + blockActivityStartAndFeatureEnabled); + joiner.add(prefix + "Block Enabled: " + enforceBlock); + if (!enforceBlock) { + joiner.add(prefix + "Feature Flag Enabled: " + android.security + .Flags.asmRestrictionsEnabled()); + joiner.add(prefix + "Mendel Override: " + ActivitySecurityModelFeatureFlags + .asmRestrictionsEnabledForAll()); + } joiner.add(prefix + "ASM Version: " + ActivitySecurityModelFeatureFlags.ASM_VERSION); joiner.add(prefix + "System Time: " + SystemClock.uptimeMillis()); + joiner.add(prefix + "Activity Opted In: " + recordToString.apply(activityOptedIn)); boolean targetTaskMatchesSourceTask = targetTask != null && sourceRecord != null && sourceRecord.getTask() == targetTask; @@ -1561,6 +1576,7 @@ public class BackgroundActivityStartController { joiner.add(prefix + "TaskToFront: " + taskToFront); joiner.add(prefix + "AvoidMoveToFront: " + avoidMoveTaskToFront); joiner.add(prefix + "BalCode: " + balCodeToString(balCode)); + joiner.add(prefix + "Allowed By Grace Period: " + allowedByGracePeriod); joiner.add(prefix + "LastResumedActivity: " + recordToString.apply(mService.mLastResumedActivity)); @@ -1588,6 +1604,44 @@ public class BackgroundActivityStartController { return joiner.toString(); } + private boolean allowedByAsmGracePeriod(int callingUid, @Nullable ActivityRecord sourceRecord, + @Nullable Task targetTask, @BalCode int balCode, boolean taskToFront, + boolean avoidMoveTaskToFront) { + if (balCode == BAL_ALLOW_GRACE_PERIOD) { + // Allow if launching into new task, and caller matches most recently finished activity + if (taskToFront && mTopFinishedActivity != null + && mTopFinishedActivity.mUid == callingUid) { + return true; + } + + // Launching into existing task - allow if matches most recently finished activity + // within the task. + // We can reach here multiple ways: + // 1. activity in fg fires intent (taskToFront = false, sourceRecord is available) + // 2. activity in bg fires intent (taskToFront = false, sourceRecord is available) + // 3. activity in bg fires intent with NEW_FLAG (taskToFront = true, + // avoidMoveTaskToFront = true, sourceRecord is available) + // 4. activity in bg fires PI (taskToFront = true, avoidMoveTaskToFront = true, + // sourceRecord is not available, targetTask may be available) + if (!taskToFront || avoidMoveTaskToFront) { + if (targetTask != null) { + FinishedActivityEntry finishedEntry = + mTaskIdToFinishedActivity.get(targetTask.mTaskId); + if (finishedEntry != null && finishedEntry.mUid == callingUid) { + return true; + } + } + + if (sourceRecord != null) { + FinishedActivityEntry finishedEntry = + mTaskIdToFinishedActivity.get(sourceRecord.getTask().mTaskId); + return finishedEntry != null && finishedEntry.mUid == callingUid; + } + } + } + return false; + } + private static boolean isSystemExemptFlagEnabled() { return DeviceConfig.getBoolean( NAMESPACE_WINDOW_MANAGER, @@ -1698,55 +1752,22 @@ public class BackgroundActivityStartController { } } - /** - * Activity level allowCrossUidActivitySwitchFromBelow defaults to false. - * Package level defaults to true. - * We block the launch if dev has explicitly set package level to false, and activity level has - * not opted out - */ - private boolean blockCrossUidActivitySwitchFromBelowForActivity(@NonNull ActivityRecord ar) { - // We don't need to check package level if activity has opted out. - if (ar.mAllowCrossUidActivitySwitchFromBelow) { - return false; - } - - if (ActivitySecurityModelFeatureFlags.asmRestrictionsEnabledForAll()) { - return true; - } - - String packageName = ar.packageName; - if (packageName == null) { - return false; - } - - PackageManager pm = mService.mContext.getPackageManager(); - ApplicationInfo applicationInfo; - - try { - applicationInfo = pm.getApplicationInfo(packageName, 0); - } catch (PackageManager.NameNotFoundException e) { - Slog.wtf(TAG, "Package name: " + packageName + " not found."); - return false; + private static class BlockActivityStart { + private boolean mTopActivityOptedIn; + private boolean mTopActivityMatchesSource; + private ActivityRecord mActivityOptedIn; + + BlockActivityStart optedIn(ActivityRecord activity) { + mTopActivityOptedIn = true; + if (mActivityOptedIn == null) { + mActivityOptedIn = activity; + } + return this; } - return !applicationInfo.allowCrossUidActivitySwitchFromBelow; - } - - private static class BlockActivityStart { - private static final BlockActivityStart ACTIVITY_START_ALLOWED = - new BlockActivityStart(false, false); - private static final BlockActivityStart LOG_ONLY = new BlockActivityStart(false, true); - private static final BlockActivityStart BLOCK = new BlockActivityStart(true, true); - // We should block if feature flag is enabled - private final boolean mBlockActivityStartIfFlagEnabled; - // Used for logging/toasts. Would we block if target sdk was V and feature was - // enabled? - private final boolean mWouldBlockActivityStartIgnoringFlag; - - private BlockActivityStart(boolean shouldBlockActivityStart, - boolean wouldBlockActivityStartIgnoringFlags) { - this.mBlockActivityStartIfFlagEnabled = shouldBlockActivityStart; - this.mWouldBlockActivityStartIgnoringFlag = wouldBlockActivityStartIgnoringFlags; + BlockActivityStart matchesSource() { + mTopActivityMatchesSource = true; + return this; } } diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index 76038b945288..b266caa82056 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -216,8 +216,7 @@ class DragState { mIsClosing = true; // Unregister the input interceptor. if (mInputInterceptor != null) { - if (DEBUG_DRAG) - Slog.d(TAG_WM, "unregistering drag input channel"); + if (DEBUG_DRAG) Slog.d(TAG_WM, "Unregistering drag input channel"); // Input channel should be disposed on the thread where the input is being handled. mDragDropController.sendHandlerMessage( @@ -227,9 +226,7 @@ class DragState { // Send drag end broadcast if drag start has been sent. if (mDragInProgress) { - if (DEBUG_DRAG) { - Slog.d(TAG_WM, "broadcasting DRAG_ENDED"); - } + if (DEBUG_DRAG) Slog.d(TAG_WM, "Broadcasting DRAG_ENDED"); for (WindowState ws : mNotifiedWindows) { float x = 0; float y = 0; @@ -248,6 +245,7 @@ class DragState { x, y, mThumbOffsetX, mThumbOffsetY, null, null, null, dragSurface, null, mDragResult); try { + if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DRAG_ENDED to " + ws); ws.mClient.dispatchDragEvent(event); } catch (RemoteException e) { Slog.w(TAG_WM, "Unable to drag-end window " + ws); @@ -364,7 +362,7 @@ class DragState { return false; } - if (DEBUG_DRAG) Slog.d(TAG_WM, "sending DROP to " + touchedWin); + if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DROP to " + touchedWin); final IBinder clientToken = touchedWin.mClient.asBinder(); final DragEvent event = createDropEvent(x, y, touchedWin, false /* includePrivateInfo */); @@ -460,7 +458,7 @@ class DragState { */ CompletableFuture<Void> register(Display display) { display.getRealSize(mDisplaySize); - if (DEBUG_DRAG) Slog.d(TAG_WM, "registering drag input channel"); + if (DEBUG_DRAG) Slog.d(TAG_WM, "Registering drag input channel"); if (mInputInterceptor != null) { Slog.e(TAG_WM, "Duplicate register of drag input channel"); return completedFuture(null); @@ -489,7 +487,7 @@ class DragState { mSourceUserId, UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE); if (DEBUG_DRAG) { - Slog.d(TAG_WM, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")"); + Slog.d(TAG_WM, "Broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")"); } final boolean containsAppExtras = containsApplicationExtras(mDataDescription); diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index acddc9d22953..fcee70fd0702 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -1694,6 +1694,39 @@ public final class DisplayPowerControllerTest { /* ignoreAnimationLimits= */ anyBoolean()); } + @Test + public void testInitialDozeBrightness_AbcIsNull() { + when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true); + when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true); + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false); + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true, + /* isAutoBrightnessAvailable= */ false); + mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); + float brightness = 0.277f; + when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); + when(mHolder.automaticBrightnessController + .getAutomaticScreenBrightnessBasedOnLastObservedLux(any(BrightnessEvent.class))) + .thenReturn(brightness); + when(mHolder.hbmController.getCurrentBrightnessMax()) + .thenReturn(PowerManager.BRIGHTNESS_MAX); + when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE); + + DisplayPowerRequest dpr = new DisplayPowerRequest(); + dpr.policy = DisplayPowerRequest.POLICY_DOZE; + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + // Automatic Brightness Controller is null so no initial doze brightness should be set and + // we should not crash + verify(mHolder.animator, never()).animateTo(eq(brightness), + /* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(), + /* ignoreAnimationLimits= */ anyBoolean()); + } + /** * Creates a mock and registers it to {@link LocalServices}. */ @@ -1778,6 +1811,12 @@ public final class DisplayPowerControllerTest { private DisplayPowerControllerHolder createDisplayPowerController(int displayId, String uniqueId, boolean isEnabled) { + return createDisplayPowerController(displayId, uniqueId, isEnabled, + /* isAutoBrightnessAvailable= */ true); + } + + private DisplayPowerControllerHolder createDisplayPowerController(int displayId, + String uniqueId, boolean isEnabled, boolean isAutoBrightnessAvailable) { final DisplayPowerState displayPowerState = mock(DisplayPowerState.class); final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class); final AutomaticBrightnessController automaticBrightnessController = @@ -1812,6 +1851,7 @@ public final class DisplayPowerControllerTest { final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class); setUpDisplay(displayId, uniqueId, display, device, config, isEnabled); + when(config.isAutoBrightnessAvailable()).thenReturn(isAutoBrightnessAvailable); final DisplayPowerController dpc = new DisplayPowerController( mContext, injector, mDisplayPowerCallbacksMock, mHandler, diff --git a/services/tests/dreamservicetests/Android.bp b/services/tests/dreamservicetests/Android.bp index 5aa5d619854f..86b3a6cc2b35 100644 --- a/services/tests/dreamservicetests/Android.bp +++ b/services/tests/dreamservicetests/Android.bp @@ -18,6 +18,11 @@ android_test { "mockito-target-minus-junit4", "services.core", "mockingservicestests-utils-mockito", + "servicestests-utils", + ], + + defaults: [ + "modules-utils-testable-device-config-defaults", ], platform_apis: true, diff --git a/services/tests/dreamservicetests/AndroidManifest.xml b/services/tests/dreamservicetests/AndroidManifest.xml index f4b88d9c18e9..6092ef6f9427 100644 --- a/services/tests/dreamservicetests/AndroidManifest.xml +++ b/services/tests/dreamservicetests/AndroidManifest.xml @@ -21,6 +21,7 @@ Insert permissions here. eg: <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> --> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <application android:debuggable="true" android:testOnly="true"> diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java index 32d4e756d589..992b8534accc 100644 --- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java +++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java @@ -36,13 +36,14 @@ import android.os.UserManager; import android.provider.Settings; import androidx.test.InstrumentationRegistry; -import androidx.test.filters.FlakyTest; -import com.android.server.LocalServices; +import com.android.internal.util.test.LocalServiceKeeperRule; import com.android.server.SystemService; +import com.android.server.testutils.TestHandler; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -65,23 +66,24 @@ public class DreamManagerServiceMockingTest { @Mock private UserManager mUserManagerMock; - private MockitoSession mMockitoSession; + @Rule + public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule(); - private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { - LocalServices.removeServiceForTest(clazz); - LocalServices.addService(clazz, mock); - } + private TestHandler mTestHandler; + private MockitoSession mMockitoSession; @Before public void setUp() throws Exception { + mTestHandler = new TestHandler(/* callback= */ null); MockitoAnnotations.initMocks(this); - mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext())); mResourcesSpy = spy(mContextSpy.getResources()); when(mContextSpy.getResources()).thenReturn(mResourcesSpy); - addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock); - addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock); + mLocalServiceKeeperRule.overrideLocalService( + ActivityManagerInternal.class, mActivityManagerInternalMock); + mLocalServiceKeeperRule.overrideLocalService( + PowerManagerInternal.class, mPowerManagerInternalMock); when(mContextSpy.getSystemService(UserManager.class)).thenReturn(mUserManagerMock); mMockitoSession = mockitoSession() @@ -94,26 +96,20 @@ public class DreamManagerServiceMockingTest { @After public void tearDown() throws Exception { mMockitoSession.finishMocking(); - LocalServices.removeServiceForTest(ActivityManagerInternal.class); - LocalServices.removeServiceForTest(PowerManagerInternal.class); } private DreamManagerService createService() { - return new DreamManagerService(mContextSpy); + return new DreamManagerService(mContextSpy, mTestHandler); } @Test - @FlakyTest(bugId = 293443309) public void testSettingsQueryUserChange() { final DreamManagerService service = createService(); - final SystemService.TargetUser from = new SystemService.TargetUser(mock(UserInfo.class)); final SystemService.TargetUser to = new SystemService.TargetUser(mock(UserInfo.class)); - service.onUserSwitching(from, to); - verify(() -> Settings.Secure.getIntForUser(any(), eq(Settings.Secure.SCREENSAVER_ENABLED), anyInt(), diff --git a/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java b/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java index 01159b1f901c..bcb4877f64c7 100644 --- a/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java @@ -32,7 +32,6 @@ import android.testing.AndroidTestingRunner; import androidx.test.platform.app.InstrumentationRegistry; -import com.android.server.companion.PackageUtils; import com.android.server.pm.pkg.AndroidPackage; import org.junit.Test; diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 6132ee3c89c4..173a1b693d87 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -74,7 +74,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -86,9 +85,9 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.notNull; import android.app.ActivityOptions; +import android.app.ActivityOptions.BackgroundActivityStartMode; import android.app.AppOpsManager; import android.app.BackgroundStartPrivileges; -import android.app.ComponentOptions.BackgroundActivityStartMode; import android.app.IApplicationThread; import android.app.PictureInPictureParams; import android.content.ComponentName; diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index 82ed340e8a0c..58488d1900fe 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -573,7 +573,7 @@ public class SubscriptionInfo implements Parcelable { * Returns the number of this subscription. * * Starting with API level 30, returns the number of this subscription if the calling app meets - * one of the following requirements: + * at least one of the following requirements: * <ul> * <li>If the calling app's target SDK is API level 29 or lower and the app has been granted * the READ_PHONE_STATE permission. @@ -584,8 +584,8 @@ public class SubscriptionInfo implements Parcelable { * <li>If the calling app is the default SMS role holder. * </ul> * - * @return the number of this subscription, or an empty string if one of these requirements is - * not met + * @return the number of this subscription, or an empty string if none of the requirements + * are met. * @deprecated use {@link SubscriptionManager#getPhoneNumber(int)} instead, which takes a * {@link #getSubscriptionId() subscription ID}. */ diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java index 9789082e1460..3f02ae9e13df 100644 --- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java +++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java @@ -549,13 +549,19 @@ public class MmTelFeature extends ImsFeature { public static final int CAPABILITY_TYPE_SMS = 1 << 3; /** - * This MmTelFeature supports Call Composer (section 2.4 of RC.20) + * This MmTelFeature supports Call Composer (section 2.4 of RC.20). This is the superset + * Call Composer, meaning that all subset types of Call Composers must be enabled when this + * capability is enabled */ public static final int CAPABILITY_TYPE_CALL_COMPOSER = 1 << 4; /** - * This MmTelFeature supports Business-only Call Composer + * This MmTelFeature supports Business-only Call Composer. This is a subset of + * {@code CAPABILITY_TYPE_CALL_COMPOSER} that only supports business related + * information for calling (e.g. information to signal if the call is a business call) in + * the SIP header. When enabling {@code CAPABILITY_TYPE_CALL_COMPOSER}, the + * {@code CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY} capability must also be enabled. */ @FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER) public static final int CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY = 1 << 5; diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py index cee29dcd1d59..1dec6ab092cb 100755 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py @@ -46,6 +46,7 @@ def check_one_file(filename): class TestWithGoldenOutput(unittest.TestCase): # Test to check the generated jar files to the golden output. + @unittest.skip("Disabled until JDK 21 is merged and the golden files updated") def test_compare_to_golden(self): files = os.listdir(GOLDEN_DIR) files.sort() |